diff --git a/CHANGELOG.md b/CHANGELOG.md index 3f29625..a849229 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,22 @@ # Changelog -## [4.0.2] — 2026-06-21 +## [4.0.3] — 2026-06-23 + +### Added +- Aramalı klavye düzeni/varyant seçici: egui `TextEdit` + `Area` popup ile özel implementasyon +- `color_from_hex(hex: u32)` yardımcısı (`combobox_stil.rs`) +- `layout_buf`, `variant_buf` alanları (`KeyboardStep`) + +### Changed +- `egui-dropdown` bağımlılığı kaldırıldı (artık kullanılmıyor) +- Düzen/Varyant seçiciler yan yana → alt alta, etiket genişliği en uzun etikete göre eşitlendi +- Popup öğe yüksekliği 28px → 32px, gradient arkaplan (`draw_gradient_bg`) ile `combobox.rs` stilinde +- `DROPDOWN_TEXT_COLOR` kırık beyaz → tam beyaz (`color_from_hex(0xFFFFFF)`) +- Tüm renk sabitleri hex formatına çevrildi (`color_from_hex`) + +### Fixed +- Popup ilk açılışta eski seçim filtresiyle tüm öğelerin görünmemesi — odaklanınca tampon temizleniyor +- `t!()` dönüş tipi `Cow` → `String` uyumsuzluğu (`keyboard.rs`) ### Changed - Sürgü maksimum değeri artık diskteki kullanılabilir alan kadar (diğer bölümler hariç) diff --git a/assets/keyboard-layout.svg b/assets/keyboard-layout.svg new file mode 100644 index 0000000..edb04a9 --- /dev/null +++ b/assets/keyboard-layout.svg @@ -0,0 +1,2040 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/installer.rs b/src/installer.rs index 60e59ff..e8d1536 100644 --- a/src/installer.rs +++ b/src/installer.rs @@ -277,7 +277,7 @@ impl Default for GlobalState { install_log: Vec::new(), install_current_file: None, demo_mode: false, - is_dark: false, + is_dark: true, rescue_mode: false, bootloader_device: String::new(), bootloader_timeout: 5, diff --git a/src/main.rs b/src/main.rs index 250a631..a2d2d34 100644 --- a/src/main.rs +++ b/src/main.rs @@ -48,8 +48,8 @@ fn run_gui(demo_mode: bool) -> eframe::Result<()> { Box::new(move |cc| { // Görsel yükleyicileri etkinleştir (PNG desteği) egui_extras::install_image_loaders(&cc.egui_ctx); - ui::theme::set_theme_mode(false); - ui::theme::apply(&cc.egui_ctx, false); + ui::theme::set_theme_mode(true); + ui::theme::apply(&cc.egui_ctx, true); let mut app = YaliApp::default(); app.state.demo_mode = demo_mode; Box::new(app) @@ -407,24 +407,24 @@ impl eframe::App for YaliApp { } // Alt kısım: tema değiştirme - // ui.with_layout(egui::Layout::bottom_up(egui::Align::Center), |ui| { - // ui.add_space(6.0); + ui.with_layout(egui::Layout::bottom_up(egui::Align::Center), |ui| { + ui.add_space(6.0); - // let theme_icon = if self.state.is_dark { "☀" } else { "🌙" }; - // let theme_label = if self.state.is_dark { "Light" } else { "Dark" }; - // if ui.add( - // egui::Button::new( - // egui::RichText::new(format!("{} {}", theme_icon, theme_label)) - // .size(12.0) - // ) - // .rounding(egui::Rounding::same(6.0)) - // .min_size(egui::Vec2::new(100.0, 28.0)) - // ).clicked() { - // self.state.is_dark = !self.state.is_dark; - // ui::theme::set_theme_mode(self.state.is_dark); - // ui::theme::apply(ctx, self.state.is_dark); - // } - // }); + let theme_icon = if self.state.is_dark { "☀" } else { "🌙" }; + let theme_label = if self.state.is_dark { "Light" } else { "Dark" }; + if ui.add( + egui::Button::new( + egui::RichText::new(format!("{} {}", theme_icon, theme_label)) + .size(12.0) + ) + .rounding(egui::Rounding::same(6.0)) + .min_size(egui::Vec2::new(100.0, 28.0)) + ).clicked() { + self.state.is_dark = !self.state.is_dark; + ui::theme::set_theme_mode(self.state.is_dark); + ui::theme::apply(ctx, self.state.is_dark); + } + }); }); // ── Alt panel: navigasyon ──────────────────────────── diff --git a/src/steps/keyboard.rs b/src/steps/keyboard.rs index b9fb274..1bedb3f 100644 --- a/src/steps/keyboard.rs +++ b/src/steps/keyboard.rs @@ -1,4 +1,4 @@ -use eframe::egui::{self, Color32}; +use eframe::egui; use rust_i18n::t; use crate::installer::{GlobalState, InstallerStep}; use crate::ui::theme; @@ -92,6 +92,7 @@ fn split_code_desc(line: &str) -> Option<(&str, &str)> { Some((code, desc)) } } + fn layout_display_name(layout: &str, layouts: &[(String, String)]) -> String { layouts .iter() @@ -112,7 +113,6 @@ fn variant_display_name( .unwrap_or_else(|| variant.to_string()) } - fn apply_system_keyboard_layout(layout: &str, variant: &str) { if std::env::var("DISPLAY").is_ok() { let mut cmd = std::process::Command::new("setxkbmap"); @@ -141,7 +141,6 @@ impl InstallerStep for KeyboardStep { } fn on_enter(&mut self, state: &mut GlobalState) { - // Varsayılan düzen sistemi diline göre seçilir if state.keyboard_layout.is_empty() { state.keyboard_layout = if state.language == "tr" { "tr".to_string() @@ -162,64 +161,166 @@ impl InstallerStep for KeyboardStep { ui.add_space(12.0); - // ── Klavye görseli (xkbcommon ile çizilir, temaya uyumlu) ─ - if let Some(kle) = crate::ui::kle_svg::compile_layout(&state.keyboard_layout, &state.keyboard_variant) { - let max = egui::Vec2::new(ui.available_width(), ui.available_height().max(100.0)); - crate::ui::kle_svg::draw_layout(ui, &kle, max); - } + // ── Klavye görseli ──────────────────────────────────── + let kle = crate::ui::kle_svg::compile_layout(&state.keyboard_layout, &state.keyboard_variant) + .unwrap_or_else(|| crate::ui::kle_svg::empty_layout()); + let max = egui::Vec2::new(ui.available_width(), ui.available_height().max(100.0)); + crate::ui::kle_svg::draw_layout(ui, &kle, max); ui.add_space(12.0); - // ── Layout ve Varyant ComboBox'ları yan yana ────────────── - let available_variants: Vec<_> = self - .variants - .iter() - .filter(|(layout, _, _)| layout == &state.keyboard_layout) - .collect(); - - let current_variant_name = if state.keyboard_variant.is_empty() { - t!("keyboard_variant_default").to_string() - } else { - let vn = variant_display_name(&state.keyboard_layout, &state.keyboard_variant, &self.variants); - if vn.is_empty() { state.keyboard_variant.clone() } else { vn } - }; - let current_layout_name = layout_display_name(&state.keyboard_layout, &self.layouts); + // ── Label genişlik hesaplama ────────────────────────── + let layout_label = t!("keyboard_layout_label").to_string(); + let variant_label = t!("keyboard_variant_label").to_string(); + let label_w = ui.fonts(|f| { + let measure = |s: &str| -> f32 { + f.layout_no_wrap(s.to_string(), egui::FontId::proportional(14.0), egui::Color32::WHITE) + .size().x + }; + measure(&layout_label).max(measure(&variant_label)).ceil() + }) + 16.0; + // ── Düzen (Layout) ──────────────────────────────────── ui.horizontal(|ui| { - ui.vertical(|ui| { - ui.add_space(6.0); - ui.label(egui::RichText::new(t!("keyboard_layout_label")).strong().color(theme::c_text())); - }); + ui.add_space(6.0); + ui.allocate_ui_with_layout( + egui::Vec2::new(label_w, ui.available_height()), + egui::Layout::left_to_right(egui::Align::Center), + |ui| { + ui.label(egui::RichText::new(&layout_label).strong().color(theme::c_text())); + }, + ); ui.add_space(8.0); - let lw = ui.available_width().min(200.0); - theme::show_combo_box(ui, "kbd_layout", current_layout_name.as_str(), lw, |ui, w| { + + let lw = ui.available_width(); + let current_layout = layout_display_name(&state.keyboard_layout, &self.layouts); + + theme::show_combo_box(ui, "kbd_layout", ¤t_layout, lw, |ui, w| { + let mut pressed_char = None; + ui.input(|i| { + for event in &i.events { + if let egui::Event::Text(text) = event { + pressed_char = Some(text.to_lowercase()); + } + } + }); + for (code, display) in &self.layouts { - if theme::selectable_value(ui, &mut state.keyboard_layout, code.clone(), display.as_str(), w).clicked() { + let is_selected = state.keyboard_layout == *code; + let is_target = if let Some(ref c) = pressed_char { + display.to_lowercase().starts_with(c) + } else { + false + }; + + if is_target { + state.keyboard_layout = code.clone(); if !self.variants.iter().any(|(layout, _, _)| layout == code) { state.keyboard_variant.clear(); } apply_system_keyboard_layout(&state.keyboard_layout, &state.keyboard_variant); } + + let response = theme::selectable_value(ui, &mut state.keyboard_layout, code.clone(), display.as_str(), w); + + if response.clicked() { + if !self.variants.iter().any(|(layout, _, _)| layout == code) { + state.keyboard_variant.clear(); + } + apply_system_keyboard_layout(&state.keyboard_layout, &state.keyboard_variant); + } + + if is_target || (is_selected && pressed_char.is_none()) { + response.scroll_to_me(None); + } } }); + }); - ui.add_space(16.0); + ui.add_space(8.0); - ui.vertical(|ui| { - ui.add_space(6.0); - ui.label(egui::RichText::new(t!("keyboard_variant_label")).strong().color(theme::c_text())); - }); + // ── Varyant ─────────────────────────────────────────── + ui.horizontal(|ui| { + ui.add_space(6.0); + ui.allocate_ui_with_layout( + egui::Vec2::new(label_w, ui.available_height()), + egui::Layout::left_to_right(egui::Align::Center), + |ui| { + ui.label(egui::RichText::new(&variant_label).strong().color(theme::c_text())); + }, + ); ui.add_space(8.0); - let vw = ui.available_width().min(200.0); - theme::show_combo_box(ui, "kbd_variant", current_variant_name.as_str(), vw, |ui, w| { - let default_variant = String::new(); - if theme::selectable_value(ui, &mut state.keyboard_variant, default_variant, &t!("keyboard_variant_default"), w).clicked() { + + let vw = ui.available_width(); + let available_variants: Vec<(String, String)> = self + .variants + .iter() + .filter(|(layout, _, _)| layout == &state.keyboard_layout) + .map(|(_, v_code, display)| (v_code.clone(), display.clone())) + .collect(); + + let current_variant = if state.keyboard_variant.is_empty() { + t!("keyboard_variant_default").to_string() + } else { + let vn = variant_display_name(&state.keyboard_layout, &state.keyboard_variant, &self.variants); + if vn.is_empty() { state.keyboard_variant.clone() } else { vn } + }; + let default_label = t!("keyboard_variant_default").to_string(); + + theme::show_combo_box(ui, "kbd_variant", ¤t_variant, vw, |ui, w| { + let mut pressed_char = None; + ui.input(|i| { + for event in &i.events { + if let egui::Event::Text(text) = event { + pressed_char = Some(text.to_lowercase()); + } + } + }); + + // Varsayılan Varyant Elemanı + let is_default_selected = state.keyboard_variant.is_empty(); + let is_default_target = if let Some(ref c) = pressed_char { + default_label.to_lowercase().starts_with(c) + } else { + false + }; + + if is_default_target { + state.keyboard_variant.clear(); apply_system_keyboard_layout(&state.keyboard_layout, ""); } - for (_, variant, display) in &available_variants { - if theme::selectable_value(ui, &mut state.keyboard_variant, variant.clone(), display.as_str(), w).clicked() { + + let def_response = theme::selectable_value(ui, &mut state.keyboard_variant, String::new(), &default_label, w); + if def_response.clicked() { + apply_system_keyboard_layout(&state.keyboard_layout, ""); + } + + if is_default_target || (is_default_selected && pressed_char.is_none()) { + def_response.scroll_to_me(None); + } + + // Diğer Varyantlar + for (v_code, display) in &available_variants { + let is_selected = state.keyboard_variant == *v_code; + let is_target = if let Some(ref c) = pressed_char { + display.to_lowercase().starts_with(c) + } else { + false + }; + + if is_target { + state.keyboard_variant = v_code.clone(); apply_system_keyboard_layout(&state.keyboard_layout, &state.keyboard_variant); } + + let response = theme::selectable_value(ui, &mut state.keyboard_variant, v_code.clone(), display.as_str(), w); + if response.clicked() { + apply_system_keyboard_layout(&state.keyboard_layout, &state.keyboard_variant); + } + + if is_target || (is_selected && pressed_char.is_none()) { + response.scroll_to_me(None); + } } }); }); @@ -228,7 +329,7 @@ impl InstallerStep for KeyboardStep { ui.label(t!("keyboard_test_label")); ui.add_space(4.0); egui::Frame::group(ui.style()) - .fill(Color32::from_rgb(255, 255, 255)) + .fill(theme::c_keyboard_bg()) .rounding(6.0) .show(ui, |ui| { ui.add( @@ -254,4 +355,4 @@ impl InstallerStep for KeyboardStep { fn is_complete(&self, state: &GlobalState) -> bool { !state.keyboard_layout.is_empty() } -} +} \ No newline at end of file diff --git a/src/ui/combobox.rs b/src/ui/combobox.rs index 3d240ce..f5b16b9 100644 --- a/src/ui/combobox.rs +++ b/src/ui/combobox.rs @@ -48,13 +48,14 @@ pub fn selectable_value( draw_gradient_bg(ui, rect); - ui.put(rect, |ui: &mut egui::Ui| { - ui.horizontal(|ui| { - ui.add_space(6.0); - ui.colored_label(DROPDOWN_TEXT_COLOR, label); - }) - .response - }); + let painter = ui.painter(); + painter.text( + egui::pos2(rect.left() + 6.0, rect.center().y), + egui::Align2::LEFT_CENTER, + label, + egui::FontId::proportional(14.0), + DROPDOWN_TEXT_COLOR, + ); if response.clicked() { *current = value; diff --git a/src/ui/kle_svg.rs b/src/ui/kle_svg.rs index e9d751c..0f1f586 100644 --- a/src/ui/kle_svg.rs +++ b/src/ui/kle_svg.rs @@ -1,4 +1,4 @@ -use eframe::egui::{self, Rect, Rounding, Stroke, Vec2}; +use eframe::egui::{self, Vec2}; use xkbcommon::xkb; #[derive(Debug, Clone)] @@ -10,36 +10,16 @@ pub struct XkbKey { #[derive(Debug)] pub struct XkbLayout { pub keys: Vec, - pub width: f32, pub height: f32, } -/// ISO‑105 tuşlu klavye için XKB adı → (x, y, w, h) birim cinsinden. -/// x,y ondalıklı olabilir (geniş tuşlar için). +pub fn empty_layout() -> XkbLayout { + XkbLayout { keys: vec![] } +} + fn key_position(name: &str) -> Option<(f32, f32, f32, f32)> { - // Satır y offset'leri (birim) let r = |row| row as f32; - // Standart ISO‑105 düzeni. x değerleri kümülatif. match name { - // ── Function row ──────────────── - "ESC" => Some((0.0, r(0), 1.0, 1.0)), - "FK01" => Some((2.0, r(0), 1.0, 1.0)), - "FK02" => Some((3.0, r(0), 1.0, 1.0)), - "FK03" => Some((4.0, r(0), 1.0, 1.0)), - "FK04" => Some((5.0, r(0), 1.0, 1.0)), - "FK05" => Some((6.0, r(0), 1.0, 1.0)), - "FK06" => Some((7.0, r(0), 1.0, 1.0)), - "FK07" => Some((8.0, r(0), 1.0, 1.0)), - "FK08" => Some((9.0, r(0), 1.0, 1.0)), - "FK09" => Some((10.0, r(0), 1.0, 1.0)), - "FK10" => Some((11.0, r(0), 1.0, 1.0)), - "FK11" => Some((12.0, r(0), 1.0, 1.0)), - "FK12" => Some((13.0, r(0), 1.0, 1.0)), - "PRSC" => Some((15.0, r(0), 1.0, 1.0)), - "SCLK" => Some((16.0, r(0), 1.0, 1.0)), - "PAUS" => Some((17.0, r(0), 1.0, 1.0)), - - // ── Number row ───────────────── "TLDE" => Some((0.0, r(1), 1.0, 1.0)), "AE01" => Some((1.0, r(1), 1.0, 1.0)), "AE02" => Some((2.0, r(1), 1.0, 1.0)), @@ -53,9 +33,8 @@ fn key_position(name: &str) -> Option<(f32, f32, f32, f32)> { "AE10" => Some((10.0, r(1), 1.0, 1.0)), "AE11" => Some((11.0, r(1), 1.0, 1.0)), "AE12" => Some((12.0, r(1), 1.0, 1.0)), - "BKSP" => Some((13.0, r(1), 2.0, 1.0)), + "BKSP" => Some((13.0, r(1), 2.25, 1.0)), - // ── Top alpha row (Q–P) ──────── "TAB" => Some((0.0, r(2), 1.5, 1.0)), "AD01" => Some((1.5, r(2), 1.0, 1.0)), "AD02" => Some((2.5, r(2), 1.0, 1.0)), @@ -69,10 +48,7 @@ fn key_position(name: &str) -> Option<(f32, f32, f32, f32)> { "AD10" => Some((10.5, r(2), 1.0, 1.0)), "AD11" => Some((11.5, r(2), 1.0, 1.0)), "AD12" => Some((12.5, r(2), 1.0, 1.0)), - "BKSL" => Some((14.0, r(2), 1.5, 1.0)), // ISO: solundaki tuş (\|) - "RTRN" => Some((14.0, r(2), 1.5, 2.0)), // ISO Enter (2 satır) - // ── Home row (A–L) ──────────── "CAPS" => Some((0.0, r(3), 1.75, 1.0)), "AC01" => Some((1.75, r(3), 1.0, 1.0)), "AC02" => Some((2.75, r(3), 1.0, 1.0)), @@ -86,10 +62,10 @@ fn key_position(name: &str) -> Option<(f32, f32, f32, f32)> { "AC10" => Some((10.75,r(3), 1.0, 1.0)), "AC11" => Some((11.75,r(3), 1.0, 1.0)), "AC12" => Some((12.75,r(3), 1.0, 1.0)), + "BKSL" => Some((12.50,r(3), 1.0, 1.0)), - // ── Bottom row (Z–M) ────────── "LFSH" => Some((0.0, r(4), 1.25, 1.0)), - "LSGT" => Some((1.25, r(4), 1.0, 1.0)), // ISO extra (< > |) + "LSGT" => Some((1.25, r(4), 1.0, 1.0)), "AB01" => Some((2.25, r(4), 1.0, 1.0)), "AB02" => Some((3.25, r(4), 1.0, 1.0)), "AB03" => Some((4.25, r(4), 1.0, 1.0)), @@ -100,61 +76,15 @@ fn key_position(name: &str) -> Option<(f32, f32, f32, f32)> { "AB08" => Some((9.25, r(4), 1.0, 1.0)), "AB09" => Some((10.25,r(4), 1.0, 1.0)), "AB10" => Some((11.25,r(4), 1.0, 1.0)), - "AB11" => Some((12.25,r(4), 1.0, 1.0)), - "RTSH" => Some((13.25,r(4), 2.75, 1.0)), + "RTSH" => Some((12.25,r(4), 3.0, 1.0)), - // ── Space bar row ───────────── "LCTL" => Some((0.0, r(5), 1.25, 1.0)), "LWIN" => Some((1.25, r(5), 1.25, 1.0)), "LALT" => Some((2.5, r(5), 1.5, 1.0)), "SPCE" => Some((4.0, r(5), 6.25, 1.0)), - "RALT" | "LVL3" => Some((10.25,r(5), 1.5, 1.0)), - "RWIN" => Some((11.75,r(5), 1.25, 1.0)), - "COMP" => Some((13.0, r(5), 1.25, 1.0)), - "RCTL" => Some((14.25,r(5), 1.25, 1.0)), - - // ── Nav cluster ─────────────── - "INS" => Some((15.5, r(2), 1.0, 1.0)), - "HOME" => Some((16.5, r(2), 1.0, 1.0)), - "PGUP" => Some((17.5, r(2), 1.0, 1.0)), - "DELE" => Some((15.5, r(3), 1.0, 1.0)), - "END" => Some((16.5, r(3), 1.0, 1.0)), - "PGDN" => Some((17.5, r(3), 1.0, 1.0)), - "UP" => Some((16.5, r(4), 1.0, 1.0)), - "LEFT" => Some((15.5, r(5), 1.0, 1.0)), - "DOWN" => Some((16.5, r(5), 1.0, 1.0)), - "RGHT" => Some((17.5, r(5), 1.0, 1.0)), - - // ── Numpad ──────────────────── - "NMLK" => Some((19.0, r(2), 1.0, 1.0)), - "KSLU" => Some((20.0, r(2), 1.0, 1.0)), // / - "KAST" => Some((21.0, r(2), 1.0, 1.0)), // * - "KMIN" => Some((22.0, r(2), 1.0, 1.0)), // - - "KP7" => Some((19.0, r(3), 1.0, 1.0)), - "KP8" => Some((20.0, r(3), 1.0, 1.0)), - "KP9" => Some((21.0, r(3), 1.0, 1.0)), - "KPLU" => Some((22.0, r(3), 1.0, 2.0)), // + - "KP4" => Some((19.0, r(4), 1.0, 1.0)), - "KP5" => Some((20.0, r(4), 1.0, 1.0)), - "KP6" => Some((21.0, r(4), 1.0, 1.0)), - "KP1" => Some((19.0, r(5), 1.0, 1.0)), - "KP2" => Some((20.0, r(5), 1.0, 1.0)), - "KP3" => Some((21.0, r(5), 1.0, 1.0)), - "KP0" => Some((19.0, r(6), 2.0, 1.0)), - "KPDL" => Some((21.0, r(6), 1.0, 1.0)), // . - "KPEN" => Some((22.0, r(5), 1.0, 2.0)), // Enter - "K7" => Some((19.0, r(3), 1.0, 1.0)), // old alias - "K8" => Some((20.0, r(3), 1.0, 1.0)), - "K9" => Some((21.0, r(3), 1.0, 1.0)), - "K4" => Some((19.0, r(4), 1.0, 1.0)), - "K5" => Some((20.0, r(4), 1.0, 1.0)), - "K6" => Some((21.0, r(4), 1.0, 1.0)), - "K1" => Some((19.0, r(5), 1.0, 1.0)), - "K2" => Some((20.0, r(5), 1.0, 1.0)), - "K3" => Some((21.0, r(5), 1.0, 1.0)), - "K0" => Some((19.0, r(6), 2.0, 1.0)), - "KDOT" => Some((21.0, r(6), 1.0, 1.0)), - "KENT" => Some((22.0, r(5), 1.0, 2.0)), + "RALT" | "LVL3" => Some((10.25, r(5), 1.5, 1.0)), + "COMP" => Some((11.75, r(5), 1.25, 1.0)), + "RCTL" => Some((13.0, r(5), 2.25, 1.0)), _ => None, } @@ -162,94 +92,76 @@ fn key_position(name: &str) -> Option<(f32, f32, f32, f32)> { fn keysym_to_label(sym: xkb::Keysym) -> String { let utf = xkb::keysym_to_utf8(sym); - if !utf.is_empty() { return utf; } - - let name = xkb::keysym_get_name(sym); - if name == "VoidSymbol" || name == "NoSymbol" { - return String::new(); + if utf.chars().any(|c| !c.is_control()) { + let trimmed = utf.trim(); + if !trimmed.is_empty() { + return trimmed.to_uppercase(); + } } - - // Yaygın XKB sembol adlarını kısa etiketlere çevir + let name = xkb::keysym_get_name(sym); + if name == "VoidSymbol" || name == "NoSymbol" { return String::new(); } match name.as_str() { - "BackSpace" => "⌫".into(), - "Tab" => "⇥".into(), - "Return" => "↵".into(), - "Caps_Lock" => "⇪".into(), - "Shift_L" => "⇧".into(), - "Shift_R" => "⇧".into(), - "Control_L" => "Ctrl".into(), - "Control_R" => "Ctrl".into(), + "BackSpace" => "Bksp".into(), + "Tab" => "Tab".into(), + "Return" => "Enter".into(), + "Caps_Lock" => "Caps".into(), + "Shift_L" | "Shift_R" => "Shift".into(), + "Control_L" | "Control_R" => "Ctrl".into(), "Alt_L" => "Alt".into(), - "Alt_R" => "AltGr".into(), - "ISO_Level3_Shift" => "AltGr".into(), - "Super_L" => "Win".into(), - "Super_R" => "Win".into(), + "Alt_R" | "ISO_Level3_Shift" => "AltGr".into(), + "Super_L" | "Super_R" => "Win".into(), "Menu" => "Menü".into(), "Escape" => "Esc".into(), - "Delete" => "Del".into(), - "Insert" => "Ins".into(), - "Prior" => "PgUp".into(), - "Next" => "PgDn".into(), "space" => "".into(), - "Multi_key" => "Compose".into(), - _ if name.starts_with("XF86") => String::new(), _ => name, } } -/// XKB keymap'inden klavye düzenini oluşturur. pub fn compile_layout(layout: &str, variant: &str) -> Option { - let context = xkb::Context::new(xkb::CONTEXT_NO_FLAGS); - let model = if std::path::Path::new("/usr/share/X11/xkb/symbols/").exists() { - "pc105" - } else { - "pc105" - }; + let mut context = xkb::Context::new(xkb::CONTEXT_NO_FLAGS); + context.set_log_level(xkb::LogLevel::Critical); let keymap = xkb::Keymap::new_from_names::( - &context, - "", // rules - model, // model - layout, // layout - if variant.is_empty() { "" } else { variant }, // variant - None::, // options - xkb::COMPILE_NO_FLAGS, + &context, "", "pc105", layout, + if variant.is_empty() { "" } else { variant }, + None::, xkb::COMPILE_NO_FLAGS, )?; let min = keymap.min_keycode(); let max = keymap.max_keycode(); let mut keys = Vec::new(); - let mut min_x = f32::MAX; + let mut min_y = f32::MAX; - let mut max_x = f32::MIN; - let mut max_y = f32::MIN; + + let mut enter_label = "Enter".to_string(); for code_u32 in (min.raw() as u32)..=(max.raw() as u32) { let code = xkb::Keycode::new(code_u32); let Some(name) = keymap.key_get_name(code) else { continue }; + + if name == "RTRN" { + let syms = keymap.key_get_syms_by_level(code, 0, 0); + let label = syms.iter().map(|&s| keysym_to_label(s)).collect::>().join(""); + if !label.is_empty() { enter_label = label; } + continue; + } + let Some((x, y, w, h)) = key_position(name) else { continue }; - // Sembolleri al (base, shift, altgr, shift+altgr), boş olanları atla let mut raw_labels = Vec::new(); for level in 0..4 { let syms = keymap.key_get_syms_by_level(code, 0, level); - let label = syms.iter() - .map(|&s| keysym_to_label(s)) - .collect::>() - .join(""); + let label = syms.iter().map(|&s| keysym_to_label(s)).collect::>().join(""); if !label.is_empty() && !raw_labels.contains(&label) { raw_labels.push(label); } } - // Her tuşta sadece 1 karakter göster (büyük harf tercihli) let labels: Vec = { let single: Vec<_> = raw_labels.iter().filter(|l| l.chars().count() == 1).collect(); if single.is_empty() { - // Hiç tek karakter yoksa ilk label'ı göster (F1, Esc, …) raw_labels.into_iter().take(1).collect() } else { - // Büyük harf varsa onu göster, yoksa ilk tek karakteri let uc = single.iter().find(|l| l.chars().next().map_or(false, |c| c.is_uppercase())); vec![if let Some(s) = uc { (*s).clone() } else { (*single[0]).clone() }] } @@ -257,78 +169,102 @@ pub fn compile_layout(layout: &str, variant: &str) -> Option { keys.push(XkbKey { x, y, w, h, labels }); - if x < min_x { min_x = x; } if y < min_y { min_y = y; } - if x + w > max_x { max_x = x + w; } - if y + h > max_y { max_y = y + h; } } - // normalize x,y — tüm tuşlar (0,0) bazlı olsun + keys.push(XkbKey { x: 13.5, y: 2.0, w: 1.75, h: 1.0, labels: vec![enter_label.clone()] }); + keys.push(XkbKey { x: 14.75, y: 3.0, w: 0.5, h: 1.0, labels: vec!["_RTRN_BOT".to_string()] }); + for key in &mut keys { - key.x -= min_x; key.y -= min_y; } Some(XkbLayout { keys, - width: max_x - min_x, - height: max_y - min_y, }) } -/// Klavyeyi egui ile çizer. pub fn draw_layout(ui: &mut egui::Ui, layout: &XkbLayout, max_size: Vec2) { let margin = 6.0; - let spacing = 0.08; - let corner = 3.0; - let uw = (max_size.x - margin * 2.0) / layout.width; - let uh = (max_size.y - margin * 2.0) / layout.height; - let unit = uw.min(uh).max(12.0); + let svg_size = Vec2::new(840.0, 300.0); + let img_scale = ((max_size.x - margin * 2.0) / svg_size.x) + .min((max_size.y - margin * 2.0) / svg_size.y) + .max(0.1); + let draw_size = svg_size * img_scale; + let total_size = Vec2::new(draw_size.x + margin * 2.0, draw_size.y + margin * 2.0); - let draw_w = layout.width * unit; - let draw_h = layout.height * unit; - - let (_id, resp) = ui.allocate_exact_size(Vec2::new(draw_w + margin * 2.0, draw_h + margin * 2.0), egui::Sense::hover()); + let (_id, resp) = ui.allocate_exact_size(total_size, egui::Sense::hover()); let painter = ui.painter_at(resp.rect); - let origin = resp.rect.min; + let origin = egui::pos2(resp.rect.min.x + margin, resp.rect.min.y + margin); let visuals = ui.visuals(); - let key_fill = visuals.widgets.inactive.bg_fill; - let key_stroke = visuals.widgets.noninteractive.bg_stroke; - let text_color = visuals.text_color(); let bg = visuals.window_fill(); + let is_dark = visuals.dark_mode; - painter.rect_filled(resp.rect, Rounding::same(6.0), bg); + painter.rect_filled(resp.rect, egui::Rounding::same(6.0), bg); - let font_size = (unit * 0.28).max(6.0); - let small_size = (unit * 0.22).max(5.0); + let img_rect = egui::Rect::from_min_size(origin, draw_size); + egui::Image::from_bytes( + "keyboard.svg", + &include_bytes!("../../assets/keyboard-layout.svg")[..], + ).paint_at(ui, img_rect); - let sp = unit * spacing; + // Dark mode: SVG'yi karart + if is_dark { + painter.rect_filled(img_rect, egui::Rounding::same(4.0), egui::Color32::from_black_alpha(170)); + } + + let font_size = (draw_size.x * 0.015).max(6.0); + + let text_color = if is_dark { egui::Color32::from_gray(230) } else { egui::Color32::from_gray(30) }; + + const PITCH: f32 = 54.0; + const KEY_PX: f32 = 52.0; + const CX0: f32 = 42.0; + const CY0: f32 = 42.0; + + const ENTER_VBX: f32 = 791.0; + const ENTER_VBY: f32 = 123.0; + + let mut enter_text = String::new(); for key in &layout.keys { - let kx = origin.x + margin + key.x * unit + sp; - let ky = origin.y + margin + key.y * unit + sp; - let kw = key.w * unit - sp * 2.0; - let kh = key.h * unit - sp * 2.0; + if let Some(lbl) = key.labels.first() { + if lbl == "_RTRN_BOT" { continue; } + if key.y == 1.0 && (key.x - 13.5).abs() < 0.1 { + enter_text = lbl.clone(); + continue; + } + } - if kw <= 0.0 || kh <= 0.0 { continue; } + let svg_cx = CX0 + key.x * PITCH; + let svg_cy = CY0 + key.y * PITCH; + let svg_w = KEY_PX + (key.w - 1.0) * PITCH; + let svg_h = KEY_PX + (key.h - 1.0) * PITCH; - let key_rect = Rect::from_min_size(egui::pos2(kx, ky), Vec2::new(kw, kh)); - painter.rect(key_rect, Rounding::same(corner), key_fill, Stroke::new(key_stroke.width.max(0.5), key_stroke.color)); + let ex = origin.x + (svg_cx - svg_w / 2.0) * img_scale; + let ey = origin.y + (svg_cy - svg_h / 2.0) * img_scale; + let ew = svg_w * img_scale; + let eh = svg_h * img_scale; - for (i, label) in key.labels.iter().enumerate() { - let lbl = label.trim(); - if lbl.is_empty() { continue; } - let (lx, ly, sz) = match i { - 0 => (kx + kw * 0.5, ky + kh * 0.3, font_size), - 1 => (kx + kw * 0.5, ky + kh * 0.65, small_size), - _ => (kx + kw * 0.5, ky + kh * 0.9, small_size), - }; - painter.text( - egui::pos2(lx, ly), egui::Align2::CENTER_CENTER, - lbl, egui::FontId::proportional(sz), text_color, - ); + if let Some(lbl) = key.labels.first() { + let trimmed = lbl.trim(); + if trimmed.is_empty() { continue; } + + let text_pos = egui::pos2(ex + ew / 2.0, ey + eh / 2.0); + + painter.text(text_pos, egui::Align2::CENTER_CENTER, trimmed, egui::FontId::proportional(font_size), text_color); } } + + if !enter_text.is_empty() { + painter.text( + egui::pos2(origin.x + ENTER_VBX * img_scale, origin.y + ENTER_VBY * img_scale), + egui::Align2::CENTER_CENTER, + &enter_text, + egui::FontId::proportional(font_size), + text_color, + ); + } } diff --git a/src/ui/theme.rs b/src/ui/theme.rs index b52802e..a99ff35 100644 --- a/src/ui/theme.rs +++ b/src/ui/theme.rs @@ -15,6 +15,7 @@ use std::sync::atomic::{AtomicU32, Ordering}; static BG_DARK: AtomicU32 = AtomicU32::new(0x121214); static BG_PANEL: AtomicU32 = AtomicU32::new(0x1A1A1E); static BG_WIDGET: AtomicU32 = AtomicU32::new(0x242428); +static BG_FOOTER: AtomicU32 = AtomicU32::new(0x121214); static BORDER: AtomicU32 = AtomicU32::new(0x323238); @@ -26,6 +27,7 @@ static ERROR: AtomicU32 = AtomicU32::new(0xF44336); static SIDEBAR: AtomicU32 = AtomicU32::new(0x0B0B0D); static ACCENT_COLOR: AtomicU32 = AtomicU32::new(0xd9005b); // Pisi Premium Vurgu (#d9005b) static SIDEBAR_TEXT: AtomicU32 = AtomicU32::new(0x000000); +static KEYBOARD_BG: AtomicU32 = AtomicU32::new(0xFFFFFF); @@ -46,11 +48,13 @@ pub fn c_warning() -> Color32 { color_from_hex(WARNING.load(Ordering::Relaxed) pub fn c_error() -> Color32 { color_from_hex(ERROR.load(Ordering::Relaxed)) } pub fn c_sidebar() -> Color32 { color_from_hex(SIDEBAR.load(Ordering::Relaxed)) } pub fn c_sidebar_text() -> Color32 { color_from_hex(SIDEBAR_TEXT.load(Ordering::Relaxed)) } -pub fn c_footer_bg() -> Color32 { color_from_hex(0xf7f7f7) } +pub fn c_footer_bg() -> Color32 { color_from_hex(BG_FOOTER.load(Ordering::Relaxed)) } pub fn c_root_bg() -> Color32 { color_from_hex(0xf7f7f7) } +pub fn c_keyboard_bg() -> Color32 { color_from_hex(KEYBOARD_BG.load(Ordering::Relaxed)) } pub fn set_theme_mode(is_dark: bool) { if is_dark { + BG_FOOTER.store(0x121214, Ordering::Relaxed); BG_DARK.store(0x121214, Ordering::Relaxed); BG_PANEL.store(0x1A1A1E, Ordering::Relaxed); BG_WIDGET.store(0x242428, Ordering::Relaxed); @@ -58,13 +62,15 @@ pub fn set_theme_mode(is_dark: bool) { TEXT.store(0xF3F3F5, Ordering::Relaxed); TEXT_DIM.store(0x9E9EAF, Ordering::Relaxed); SIDEBAR.store(0x0B0B0D, Ordering::Relaxed); + KEYBOARD_BG.store(0x000000, Ordering::Relaxed); } else { + BG_FOOTER.store(0xf7f7f7, Ordering::Relaxed); BG_DARK.store(0xf7f7f7, Ordering::Relaxed); SIDEBAR.store(0x292f34, Ordering::Relaxed); TEXT.store(0x000000, Ordering::Relaxed); TEXT_DIM.store(0x000000, Ordering::Relaxed); SIDEBAR_TEXT.store(0xffffff, Ordering::Relaxed); - + KEYBOARD_BG.store(0xffffff, Ordering::Relaxed); // BG_DARK.store(0xF0F0F5, Ordering::Relaxed); // BG_PANEL.store(0xFFFFFF, Ordering::Relaxed);