mirror of
https://gitlab.com/erkanisik/yali-rs.git
synced 2026-07-31 03:09:00 +00:00
Merge branch 'main' into 'main'
feat: keyboard layout/variant search with custom combobox, hex color constants, various UI fixes See merge request ayhanyalcinsoy/yali-rs!8
This commit is contained in:
+46
-2
@@ -1,6 +1,50 @@
|
|||||||
# Changelog
|
# Changelog — [English](CHANGELOG_en.md)
|
||||||
|
|
||||||
## [4.0.2] — 2026-06-21
|
## [4.0.4] — 2026-06-25
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- Kullanıcı adı doğrulama fonksiyonu `validate_username` (detaylı hata mesajları döndürür)
|
||||||
|
- Yasaklı sistem kullanıcı adları listesi (root, daemon, bin, sys, vb.)
|
||||||
|
- Türkçe karakter dönüşümleri büyük harfleri de kapsayacak şekilde genişletildi (Ş, Ğ, İ, Ö, Ü, Ç)
|
||||||
|
- Locale dosyalarına yeni kullanıcı adı doğrulama anahtarları (username_too_long, username_forbidden, username_invalid_start, username_invalid_chars)
|
||||||
|
- UsersStep struct'ına `autologin` alanı eklendi
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Kullanıcı adı, şifre doğrulama ve hostname input'ları için check/fail iconları input'un yanına alındı
|
||||||
|
- Hata mesajları inputların altında gösteriliyor (username doğrulama, şifre eşleşme)
|
||||||
|
- Önyükleyici (bootloader) sayfası form düzeni users sayfasındaki gibi güncellendi (Frame, form_label, form_input_text/password)
|
||||||
|
- Display manager adımı adım listesinden kaldırıldı (ISO'lar tek masaüstü ile geliyor)
|
||||||
|
- Autologin özelliği display manager adımından users adımına taşındı
|
||||||
|
- Display manager yapılandırması otomatik hale getirildi (desktop_environment otomatik tespit ediliyor)
|
||||||
|
- SDDM/GDM yapılandırmasında masaüstü ortamı seçimi kullanılıyor (xfce.desktop, gnome.desktop vb.)
|
||||||
|
- Autologin varsayılan değeri false olarak değiştirildi
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Dark modda sidebar metin renginin kaybolması sorunu (SIDEBAR_TEXT beyaz olarak ayarlandı)
|
||||||
|
- derive_username fonksiyonunda yanlış karakter (ā → ğ) düzeltildi
|
||||||
|
- unused_mut warning (name değişkeni)
|
||||||
|
- unused variable warning (err değişkeni)
|
||||||
|
- unused import warning (DisplayManagerStep)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## [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<str>` → `String` uyumsuzluğu (`keyboard.rs`)
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
- Sürgü maksimum değeri artık diskteki kullanılabilir alan kadar (diğer bölümler hariç)
|
- Sürgü maksimum değeri artık diskteki kullanılabilir alan kadar (diğer bölümler hariç)
|
||||||
|
|||||||
+176
@@ -0,0 +1,176 @@
|
|||||||
|
# Changelog — [Türkçe](CHANGELOG.md)
|
||||||
|
|
||||||
|
## [4.0.4] — 2026-06-25
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- Username validation function `validate_username` (returns detailed error messages)
|
||||||
|
- Forbidden system usernames list (root, daemon, bin, sys, etc.)
|
||||||
|
- Turkish character conversions extended to include uppercase letters (Ş, Ğ, İ, Ö, Ü, Ç)
|
||||||
|
- New username validation keys in locale files (username_too_long, username_forbidden, username_invalid_start, username_invalid_chars)
|
||||||
|
- `autologin` field added to UsersStep struct
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Check/fail icons for username, password validation, and hostname inputs moved next to input fields
|
||||||
|
- Error messages shown below inputs (username validation, password mismatch)
|
||||||
|
- Bootloader page form layout updated to match users page (Frame, form_label, form_input_text/password)
|
||||||
|
- Display manager step removed from step list (ISOs ship with single desktop environment)
|
||||||
|
- Autologin feature moved from display manager step to users step
|
||||||
|
- Display manager configuration made automatic (desktop_environment auto-detected)
|
||||||
|
- Desktop environment selection used in SDDM/GDM configuration (xfce.desktop, gnome.desktop, etc.)
|
||||||
|
- Autologin default value changed to false
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Sidebar text color disappearing in dark mode (SIDEBAR_TEXT set to white)
|
||||||
|
- Incorrect character in derive_username function (ā → ğ)
|
||||||
|
- unused_mut warning (name variable)
|
||||||
|
- unused variable warning (err variable)
|
||||||
|
- unused import warning (DisplayManagerStep)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## [4.0.3] — 2026-06-23
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- Searchable keyboard layout/variant selector: egui `TextEdit` + `Area` popup custom implementation
|
||||||
|
- `color_from_hex(hex: u32)` helper (`combobox_stil.rs`)
|
||||||
|
- `layout_buf`, `variant_buf` fields (`KeyboardStep`)
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- `egui-dropdown` dependency removed (no longer used)
|
||||||
|
- Layout/Variant selectors side-by-side → stacked, label width equalized to longest label
|
||||||
|
- Popup item height 28px → 32px, gradient background (`draw_gradient_bg`) in `combobox.rs` style
|
||||||
|
- `DROPDOWN_TEXT_COLOR` off-white → pure white (`color_from_hex(0xFFFFFF)`)
|
||||||
|
- All color constants converted to hex format (`color_from_hex`)
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Popup not showing all items on first open due to old selection filter — buffer cleared on focus
|
||||||
|
- `t!()` return type `Cow<str>` → `String` mismatch (`keyboard.rs`)
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Slider maximum value now equals available disk space (excluding other partitions)
|
||||||
|
- Selected partition capacity included in slider range in edit mode
|
||||||
|
- `form_input_text` size field directly connected to `add_size_str`, formatted MB display added
|
||||||
|
- Size label now in `"123.456 MB"` format instead of `"123 MB"` (for parseable values)
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Value written to size input field not written back to `add_size_str` — text edit remained read-only
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## [4.0.1] — 2026-06-19
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- `c_root_bg()` theme function
|
||||||
|
- `show_root_password`, `show_root_password_confirm` fields (`UsersStep`)
|
||||||
|
- `form_input_text` / `form_input_password` wrapped with `Frame` and `.frame(false)`
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- `form_input_text`: Wrapped in Frame, added stroke/fill/rounding, height 32→10
|
||||||
|
- `form_input_password`: SVG eye icons (instead of emoji), Frame wrapped, `override_text_color`
|
||||||
|
- Disk grid selection colors `theme::c_*()` → constant `(237,237,224,1)`
|
||||||
|
- Disk list header and column headers: `Align::Center` → `Align::LEFT`, `vertical_centered` removed
|
||||||
|
- Root password fields raw `TextEdit` → `form_input_password` (with eye icon + frame)
|
||||||
|
- User form Frame color `theme::c_bg_widget()` → `(240,243,198)`
|
||||||
|
- Manual disk list brand/model label ordering changed
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- `users.rs:154` — `Color32` import error (missing `egui::` prefix)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## [4.0.0] — 2026-06-18
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- `CHANGELOG.md`
|
||||||
|
- `src/ui/combobox.rs` — ComboBox helpers (`show_combo_box`, `selectable_value`, `selectable_value_custom`, `draw_gradient_bg`)
|
||||||
|
- `src/ui/combobox_stil.rs` — ComboBox visual constants and `apply_visuals()`
|
||||||
|
- `src/steps/location.rs` — Region/timezone selection step
|
||||||
|
- `src/steps/network.rs` — Network configuration step
|
||||||
|
- `src/steps/rescue.rs` — Rescue mode
|
||||||
|
- `xkbcommon` dependency
|
||||||
|
- Keyboard visual: XKB keymap → egui drawing (`compile_layout`, `draw_layout`, `keysym_to_label`)
|
||||||
|
- ISO‑105 key position mapping (function keys, numpad, navigation included)
|
||||||
|
- Single character display per key (uppercase preferred)
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Keyboard visual: KLE JSON download/parse → direct keymap compilation with xkbcommon
|
||||||
|
- `Cargo.toml`: `ureq`, `serde_json` removed; `xkbcommon` added
|
||||||
|
- ComboBox styles moved from `theme.rs` → `combobox_stil.rs`
|
||||||
|
- Comment lines added to `secondary_button` gradient colors
|
||||||
|
- `draw_gradient_bg` moved to `combobox_stil.rs`
|
||||||
|
- All ComboBox usages updated with `theme::show_combo_box` / `theme::selectable_value`
|
||||||
|
- Keyboard step (`keyboard.rs`): `ScrollArea`+`selectable_label` → ComboBox
|
||||||
|
- `TextEdit::multiline` background made with per-widget `Frame::group`
|
||||||
|
- `locale/` files updated
|
||||||
|
- Font sizes increased (label 9→12, secondary 7→9)
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
- `ureq`, `serde_json` dependencies
|
||||||
|
- `assets/keyboards/KLE_*.json` cache files
|
||||||
|
- `assets/keyboards/KB_*.svg` files
|
||||||
|
- Old keyboard SVG loading code
|
||||||
|
- KLE JSON download/cache logic
|
||||||
|
- Old button examples in demo page
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- `set_style`/`set_visuals` order: `set_style` called first
|
||||||
|
- Region/timezone labels vertically aligned
|
||||||
|
- Keyboard step `on_enter` not called on first render — layout also initialized in `show()`
|
||||||
|
- `xkbcommon::Keycode` iteration (conversion with `raw()`)
|
||||||
|
- `None::<&str>` → `None::<String>` type mismatch
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## [4.0.0] — 2026-06-06
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- Rescue mode (rescue step)
|
||||||
|
- Network configuration step (NetworkManager/Wicd)
|
||||||
|
- Netinstall step
|
||||||
|
- LVM and LUKS encryption support
|
||||||
|
- Demo mode (`--demo`)
|
||||||
|
- Automatic installation (`--auto-install answer.toml`)
|
||||||
|
- Multi-language support with `rust-i18n` (Turkish, English)
|
||||||
|
- Gradient buttons (`gradient_button`, `secondary_button`)
|
||||||
|
- Language selector (flag + label)
|
||||||
|
- SVG loading support (`egui_extras` + `image`)
|
||||||
|
- Slideshow (configurable with branding.toml)
|
||||||
|
- Step list in sidebar (processed steps green ✔️)
|
||||||
|
- Real-time installation file tracking
|
||||||
|
- Advanced partitioning UI (usage bar, LUKS indicator, mount-point selector)
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- `pspec.xml`/`actions.py` → Cargo-based build
|
||||||
|
- `comar`/`mudur` calls → `zbus` D-Bus and direct command execution
|
||||||
|
- Shell-based password management → secure stdin piping (LUKS/GRUB)
|
||||||
|
- Automatic username derivation (real name → system username)
|
||||||
|
- Hostname validation/sanitization
|
||||||
|
- Cleanup operations: user deletion, pool management, boot directory cleanup
|
||||||
|
- Rsync command with dynamic exclusion
|
||||||
|
- Version 4.0.1 → 4.0.0 (Cargo.toml compatibility)
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- GRUB password writing security
|
||||||
|
- Dracut/Mkinitcpio LVM module addition
|
||||||
|
- EFI partition detection and mounting
|
||||||
|
- Password strength indicator colors
|
||||||
|
- Automatic partitioning (LVM/LUKS ordering)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## [0.1.0] — 2026-05-20
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- First stable release
|
||||||
|
- Project skeleton (egui + wgpu)
|
||||||
|
- Step-based installation wizard
|
||||||
|
- Automatic disk partitioning (GPT/MBR)
|
||||||
|
- User creation (with password strength check)
|
||||||
|
- Bootloader configuration (GRUB)
|
||||||
|
- Display manager selection
|
||||||
|
- Summary screen
|
||||||
|
- Basic color theme (light/dark)
|
||||||
|
- Customizable branding (branding.toml)
|
||||||
|
- Error screen for rescue purposes
|
||||||
|
- Job queue (JobQueue / async task management)
|
||||||
|
- YALI serialization (installer state save/load)
|
||||||
@@ -2,9 +2,12 @@
|
|||||||
|
|
||||||
Rust ve egui ile yeniden yazılmış **Pisi GNU/Linux** kurulum aracı. YALI, canlı ortamdan sistemi kurar, paketleri kurar ve temel yapılandırmayı gerçekleştirir. Orijinal Python/Qt sürümünün modern, hızlı ve bakımı kolay bir yeniden yazımıdır.
|
Rust ve egui ile yeniden yazılmış **Pisi GNU/Linux** kurulum aracı. YALI, canlı ortamdan sistemi kurar, paketleri kurar ve temel yapılandırmayı gerçekleştirir. Orijinal Python/Qt sürümünün modern, hızlı ve bakımı kolay bir yeniden yazımıdır.
|
||||||
|
|
||||||
|
- [Changelog](CHANGELOG.md)
|
||||||
|
- [English README](README_en.md)
|
||||||
|
|
||||||
## Özellikler
|
## Özellikler
|
||||||
|
|
||||||
- **11 adımlı kurulum sihirbazı**: Hoş Geldiniz → Konum → Klavye → Ağ → Disk Bölümleme → Kullanıcılar → Bootloader → Görüntü Yöneticisi → Özet → Kurulum → Bitir
|
- **10 adımlı kurulum sihirbazı**: Hoş Geldiniz → Konum → Klavye → Ağ → Disk Bölümleme → Kullanıcılar → Bootloader → Özet → Kurulum → Bitir
|
||||||
- **İki mod**: Grafik arayüz (varsayılan) ve cevap dosyasıyla otomatik kurulum (`--auto-install answer.toml`)
|
- **İki mod**: Grafik arayüz (varsayılan) ve cevap dosyasıyla otomatik kurulum (`--auto-install answer.toml`)
|
||||||
- **Demo modu**: Sistem kontrollerini atlar, kurulumu simüle eder (`--demo`)
|
- **Demo modu**: Sistem kontrollerini atlar, kurulumu simüle eder (`--demo`)
|
||||||
- **Disk bölümleme**: Otomatik (GPT/MBR) veya manuel, LVM ve LUKS şifreleme desteği
|
- **Disk bölümleme**: Otomatik (GPT/MBR) veya manuel, LVM ve LUKS şifreleme desteği
|
||||||
@@ -56,7 +59,6 @@ sudo cargo run --release -- --auto-install answer.toml
|
|||||||
│ │ ├── partition.rs # Disk bölümleme (otomatik + manuel + LVM)
|
│ │ ├── partition.rs # Disk bölümleme (otomatik + manuel + LVM)
|
||||||
│ │ ├── users.rs # Kullanıcı hesabı oluşturma
|
│ │ ├── users.rs # Kullanıcı hesabı oluşturma
|
||||||
│ │ ├── bootloader.rs # GRUB yapılandırması
|
│ │ ├── bootloader.rs # GRUB yapılandırması
|
||||||
│ │ ├── display_manager.rs # Görüntü yöneticisi ve masaüstü seçimi
|
|
||||||
│ │ ├── summary.rs # Kurulum özeti
|
│ │ ├── summary.rs # Kurulum özeti
|
||||||
│ │ ├── execution.rs # Kurulum yürütme (ilerleme çubuğu, log)
|
│ │ ├── execution.rs # Kurulum yürütme (ilerleme çubuğu, log)
|
||||||
│ │ ├── finish.rs # Tamamlanma ekranı
|
│ │ ├── finish.rs # Tamamlanma ekranı
|
||||||
@@ -86,12 +88,11 @@ sudo cargo run --release -- --auto-install answer.toml
|
|||||||
3. **Klavye** — Düzen/varyant seçimi, arama, canlı önizleme (setxkbmap)
|
3. **Klavye** — Düzen/varyant seçimi, arama, canlı önizleme (setxkbmap)
|
||||||
4. **Ağ** — WiFi tarama, bağlantı, proxy
|
4. **Ağ** — WiFi tarama, bağlantı, proxy
|
||||||
5. **Disk Bölümleme** — Otomatik (rehberli) veya manuel, LVM, LUKS
|
5. **Disk Bölümleme** — Otomatik (rehberli) veya manuel, LVM, LUKS
|
||||||
6. **Kullanıcılar** — Hesap oluşturma, parola gücü göstergesi, root parolası
|
6. **Kullanıcılar** — Hesap oluşturma, parola gücü göstergesi, root parolası, otomatik oturum açma
|
||||||
7. **Bootloader** — GRUB aygıtı, zaman aşımı, parola, çekirdek parametreleri
|
7. **Bootloader** — GRUB aygıtı, zaman aşımı, parola, çekirdek parametreleri
|
||||||
8. **Görüntü Yöneticisi** — SDDM/LightDM/GDM, masaüstü ortamı, otomatik oturum
|
8. **Özet** — Tüm seçimlerin gözden geçirilmesi
|
||||||
9. **Özet** — Tüm seçimlerin gözden geçirilmesi
|
9. **Kurulum** — Slayt + ilerleme çubuğu + canlı log
|
||||||
10. **Kurulum** — Slayt + ilerleme çubuğu + canlı log
|
10. **Bitir** — Başarılı kurulum, yeniden başlat veya canlı masaüstüne dön
|
||||||
11. **Bitir** — Başarılı kurulum, yeniden başlat veya canlı masaüstüne dön
|
|
||||||
|
|
||||||
## Bağımlılıklar
|
## Bağımlılıklar
|
||||||
|
|
||||||
|
|||||||
+114
@@ -0,0 +1,114 @@
|
|||||||
|
# YALI (Yet Another Linux Installer) — Rust / egui
|
||||||
|
|
||||||
|
Rust ve egui ile yeniden yazılmış **Pisi GNU/Linux** kurulum aracı. YALI, canlı ortamdan sistemi kurar, paketleri kurar ve temel yapılandırmayı gerçekleştirir. Orijinal Python/Qt sürümünün modern, hızlı ve bakımı kolay bir yeniden yazımıdır.
|
||||||
|
|
||||||
|
## Özellikler
|
||||||
|
|
||||||
|
- **11 adımlı kurulum sihirbazı**: Hoş Geldiniz → Konum → Klavye → Ağ → Disk Bölümleme → Kullanıcılar → Bootloader → Görüntü Yöneticisi → Özet → Kurulum → Bitir
|
||||||
|
- **İki mod**: Grafik arayüz (varsayılan) ve cevap dosyasıyla otomatik kurulum (`--auto-install answer.toml`)
|
||||||
|
- **Demo modu**: Sistem kontrollerini atlar, kurulumu simüle eder (`--demo`)
|
||||||
|
- **Disk bölümleme**: Otomatik (GPT/MBR) veya manuel, LVM ve LUKS şifreleme desteği
|
||||||
|
- **Ağ yapılandırması**: WiFi tarama (nmcli), proxy ayarları
|
||||||
|
- **Önyükleyici**: GRUB aygıt/parola/zaman aşımı ayarları, çekirdek parametreleri
|
||||||
|
- **Kurtarma modu**: Chroot, GRUB yeniden kurulumu, Pisi geçmişe dönme
|
||||||
|
- **OEM modu**: Üretici ön kurulumu için ön tanımlı ayarlar
|
||||||
|
- **Uluslararasılaştırma**: Türkçe ve İngilizce (rust-i18n ile TOML locale dosyaları)
|
||||||
|
- **Markalama sistemi**: Çalışma zamanında yüklenebilir `branding.toml` ile logo, slayt, renk ayarları
|
||||||
|
- **Tema**: Koyu/açık tema, atomik renk yönetimi, özel gradient butonlar
|
||||||
|
- **Asenkron iş kuyruğu**: Tüm kurulum adımları ayrı `tokio` runtime'ında çalışır, `mpsc` kanalı ile UI güncellenir
|
||||||
|
- **Sistem kontrolleri**: RAM, disk alanı, internet, UEFI, CPU, NVMe gibi 8 ön kontrol
|
||||||
|
- **Slayt gösterisi**: 9 masaüstü ortamı için özel markalı slaytlar
|
||||||
|
- **Demo oturum temizliği**: Canlı ortam artifact'lerini kaldırır, üretim deposu ekler
|
||||||
|
|
||||||
|
## Kullanım
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Normal kurulum (root yetkisi gerekir)
|
||||||
|
sudo cargo run --release
|
||||||
|
|
||||||
|
# Demo modu (root gerekmez)
|
||||||
|
cargo run --release -- --demo
|
||||||
|
|
||||||
|
# Otomatik kurulum (cevap dosyası ile)
|
||||||
|
sudo cargo run --release -- --auto-install answer.toml
|
||||||
|
```
|
||||||
|
|
||||||
|
## Proje Yapısı
|
||||||
|
|
||||||
|
```
|
||||||
|
├── src/
|
||||||
|
│ ├── main.rs # Giriş noktası, eframe App, GUI düzeni
|
||||||
|
│ ├── installer.rs # InstallerStep trait'i, GlobalState, WelcomeStep
|
||||||
|
│ ├── funct.rs # Paylaşılan UI yardımcıları (form alanları, dil seçici)
|
||||||
|
│ ├── branding.rs # Branding.toml yükleyici (logo, slayt, tema, OEM)
|
||||||
|
│ ├── ui/
|
||||||
|
│ │ ├── theme.rs # Renkler, fontlar, Visuals, section_heading, error/warning/success kutuları
|
||||||
|
│ │ ├── buttons.rs # Gradient butonlar (primary, secondary, danger, vb.)
|
||||||
|
│ │ ├── combobox.rs # ComboBox yardımcıları (show_combo_box, selectable_value)
|
||||||
|
│ │ ├── combobox_stil.rs # ComboBox görsel sabitleri (weak_bg, border, text renkleri)
|
||||||
|
│ │ ├── slideshow.rs # Otomatik ilerleyen slayt gösterisi
|
||||||
|
│ │ └── error_screen.rs # Hata ekranı (log görüntüleyici, kopyala, yeniden dene)
|
||||||
|
│ ├── steps/
|
||||||
|
│ │ ├── location.rs # Konum/saat dilimi + tarih/saat
|
||||||
|
│ │ ├── license.rs # Lisans sözleşmesi
|
||||||
|
│ │ ├── keyboard.rs # Klavye düzeni/varyant seçimi
|
||||||
|
│ │ ├── network.rs # Ağ yapılandırması (WiFi, proxy)
|
||||||
|
│ │ ├── partition.rs # Disk bölümleme (otomatik + manuel + LVM)
|
||||||
|
│ │ ├── users.rs # Kullanıcı hesabı oluşturma
|
||||||
|
│ │ ├── bootloader.rs # GRUB yapılandırması
|
||||||
|
│ │ ├── display_manager.rs # Görüntü yöneticisi ve masaüstü seçimi
|
||||||
|
│ │ ├── summary.rs # Kurulum özeti
|
||||||
|
│ │ ├── execution.rs # Kurulum yürütme (ilerleme çubuğu, log)
|
||||||
|
│ │ ├── finish.rs # Tamamlanma ekranı
|
||||||
|
│ │ ├── netinstall.rs # Ek paket grubu seçimi
|
||||||
|
│ │ └── rescue.rs # Kurtarma modu (chroot, GRUB, Pisi takeback)
|
||||||
|
│ ├── jobs/mod.rs # Asenkron iş kuyruğu ve tüm kurulum işleri
|
||||||
|
│ └── autoinstall/
|
||||||
|
│ ├── mod.rs # Cevap dosyası yükleme ve state uygulama
|
||||||
|
│ └── checker.rs # Sistem ön kontrolleri
|
||||||
|
├── locales/
|
||||||
|
│ ├── tr.toml # Türkçe çeviriler
|
||||||
|
│ └── en.toml # İngilizce çeviriler
|
||||||
|
├── assets/
|
||||||
|
│ ├── icons/ # UI ikonları (YALI_icon_LIGHT.svg, language-icon.svg, vb.)
|
||||||
|
│ ├── flags/ # Ülke bayrakları (~250 SVG)
|
||||||
|
│ └── *.png # Slayt ve logo görselleri
|
||||||
|
├── branding.toml # Varsayılan markalama yapılandırması
|
||||||
|
├── branding_en.toml # İngilizce markalama
|
||||||
|
├── branding_tr.toml # Türkçe markalama
|
||||||
|
└── answer-example.toml # Örnek cevap dosyası
|
||||||
|
```
|
||||||
|
|
||||||
|
## Kurulum İş Akışı
|
||||||
|
|
||||||
|
1. **Hoş Geldiniz** — Dil seçimi, sistem kontrolleri, kurtarma modu
|
||||||
|
2. **Konum / Zaman** — Saat dilimi seçimi (bölge/alan çift ComboBox), GeoIP, NTP veya manuel tarih/saat
|
||||||
|
3. **Klavye** — Düzen/varyant seçimi, arama, canlı önizleme (setxkbmap)
|
||||||
|
4. **Ağ** — WiFi tarama, bağlantı, proxy
|
||||||
|
5. **Disk Bölümleme** — Otomatik (rehberli) veya manuel, LVM, LUKS
|
||||||
|
6. **Kullanıcılar** — Hesap oluşturma, parola gücü göstergesi, root parolası
|
||||||
|
7. **Bootloader** — GRUB aygıtı, zaman aşımı, parola, çekirdek parametreleri
|
||||||
|
8. **Görüntü Yöneticisi** — SDDM/LightDM/GDM, masaüstü ortamı, otomatik oturum
|
||||||
|
9. **Özet** — Tüm seçimlerin gözden geçirilmesi
|
||||||
|
10. **Kurulum** — Slayt + ilerleme çubuğu + canlı log
|
||||||
|
11. **Bitir** — Başarılı kurulum, yeniden başlat veya canlı masaüstüne dön
|
||||||
|
|
||||||
|
## Bağımlılıklar
|
||||||
|
|
||||||
|
| Bağımlılık | Sürüm | Amaç |
|
||||||
|
|---|---|---|
|
||||||
|
| `eframe` | 0.27 (wgpu) | egui çatısı, WebGPU çıktısı |
|
||||||
|
| `egui_extras` | 0.27 (image, file, svg) | Görsel yükleme, dosya diyalogları, SVG |
|
||||||
|
| `image` | 0.24 | PNG kod çözme |
|
||||||
|
| `serde` / `toml` | 1.0 / 0.8 | Branding ve cevap dosyası serileştirme |
|
||||||
|
| `zbus` | 4.0 | D-Bus iletişimi (COMAR) |
|
||||||
|
| `tokio` | 1.0 (full) | Asenkron iş kuyruğu |
|
||||||
|
| `sysinfo` | 0.30 | Sistem bilgisi (RAM, disk, OS) |
|
||||||
|
| `rust-i18n` | 3.1 | Uluslararasılaştırma |
|
||||||
|
| `chrono` | 0.4 | Tarih/saat işlemleri |
|
||||||
|
|
||||||
|
## Lisans
|
||||||
|
|
||||||
|
GNU General Public License v3.0
|
||||||
|
|
||||||
|
Bu proje Pisi GNU/Linux ekibi tarafından geliştirilmektedir.
|
||||||
+112
@@ -0,0 +1,112 @@
|
|||||||
|
# YALI (Yet Another Linux Installer) — Rust / egui
|
||||||
|
|
||||||
|
A rewritten **Pisi GNU/Linux** installation tool in Rust and egui. YALI installs the system from the live environment, installs packages, and performs basic configuration. It is a modern, fast, and maintainable rewrite of the original Python/Qt version.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- **10-step installation wizard**: Welcome → Location → Keyboard → Network → Disk Partitioning → Users → Bootloader → Summary → Installation → Finish
|
||||||
|
- **Two modes**: Graphical interface (default) and automatic installation with answer file (`--auto-install answer.toml`)
|
||||||
|
- **Demo mode**: Skips system checks, simulates installation (`--demo`)
|
||||||
|
- **Disk partitioning**: Automatic (GPT/MBR) or manual, LVM and LUKS encryption support
|
||||||
|
- **Network configuration**: WiFi scanning (nmcli), proxy settings
|
||||||
|
- **Bootloader**: GRUB device/password/timeout settings, kernel parameters
|
||||||
|
- **Rescue mode**: Chroot, GRUB reinstallation, Pisi rollback
|
||||||
|
- **OEM mode**: Predefined settings for manufacturer pre-installation
|
||||||
|
- **Internationalization**: Turkish and English (with rust-i18n via TOML locale files)
|
||||||
|
- **Branding system**: Runtime-loadable `branding.toml` for logo, slides, color settings
|
||||||
|
- **Theme**: Dark/light theme, atomic color management, custom gradient buttons
|
||||||
|
- **Async job queue**: All installation steps run in separate `tokio` runtime, UI updated via `mpsc` channel
|
||||||
|
- **System checks**: 8 pre-checks including RAM, disk space, internet, UEFI, CPU, NVMe
|
||||||
|
- **Slideshow**: Custom branded slides for 9 desktop environments
|
||||||
|
- **Demo session cleanup**: Removes live environment artifacts, adds production repository
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Normal installation (requires root)
|
||||||
|
sudo cargo run --release
|
||||||
|
|
||||||
|
# Demo mode (no root required)
|
||||||
|
cargo run --release -- --demo
|
||||||
|
|
||||||
|
# Automatic installation (with answer file)
|
||||||
|
sudo cargo run --release -- --auto-install answer.toml
|
||||||
|
```
|
||||||
|
|
||||||
|
## Project Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
├── src/
|
||||||
|
│ ├── main.rs # Entry point, eframe App, GUI layout
|
||||||
|
│ ├── installer.rs # InstallerStep trait, GlobalState, WelcomeStep
|
||||||
|
│ ├── funct.rs # Shared UI helpers (form fields, language selector)
|
||||||
|
│ ├── branding.rs # Branding.toml loader (logo, slides, theme, OEM)
|
||||||
|
│ ├── ui/
|
||||||
|
│ │ ├── theme.rs # Colors, fonts, Visuals, section_heading, error/warning/success boxes
|
||||||
|
│ │ ├── buttons.rs # Gradient buttons (primary, secondary, danger, etc.)
|
||||||
|
│ │ ├── combobox.rs # ComboBox helpers (show_combo_box, selectable_value)
|
||||||
|
│ │ ├── combobox_stil.rs # ComboBox visual constants (weak_bg, border, text colors)
|
||||||
|
│ │ ├── slideshow.rs # Auto-advancing slideshow
|
||||||
|
│ │ └── error_screen.rs # Error screen (log viewer, copy, retry)
|
||||||
|
│ ├── steps/
|
||||||
|
│ │ ├── location.rs # Location/timezone + date/time
|
||||||
|
│ │ ├── license.rs # License agreement
|
||||||
|
│ │ ├── keyboard.rs # Keyboard layout/variant selection
|
||||||
|
│ │ ├── network.rs # Network configuration (WiFi, proxy)
|
||||||
|
│ │ ├── partition.rs # Disk partitioning (automatic + manual + LVM)
|
||||||
|
│ │ ├── users.rs # User account creation
|
||||||
|
│ │ ├── bootloader.rs # GRUB configuration
|
||||||
|
│ │ ├── summary.rs # Installation summary
|
||||||
|
│ │ ├── execution.rs # Installation execution (progress bar, log)
|
||||||
|
│ │ ├── finish.rs # Completion screen
|
||||||
|
│ │ ├── netinstall.rs # Extra package group selection
|
||||||
|
│ │ └── rescue.rs # Rescue mode (chroot, GRUB, Pisi takeback)
|
||||||
|
│ ├── jobs/mod.rs # Async job queue and all installation jobs
|
||||||
|
│ └── autoinstall/
|
||||||
|
│ ├── mod.rs # Answer file loading and state application
|
||||||
|
│ └── checker.rs # System pre-checks
|
||||||
|
├── locales/
|
||||||
|
│ ├── tr.toml # Turkish translations
|
||||||
|
│ └── en.toml # English translations
|
||||||
|
├── assets/
|
||||||
|
│ ├── icons/ # UI icons (YALI_icon_LIGHT.svg, language-icon.svg, etc.)
|
||||||
|
│ ├── flags/ # Country flags (~250 SVG)
|
||||||
|
│ └── *.png # Slide and logo images
|
||||||
|
├── branding.toml # Default branding configuration
|
||||||
|
├── branding_en.toml # English branding
|
||||||
|
├── branding_tr.toml # Turkish branding
|
||||||
|
└── answer-example.toml # Example answer file
|
||||||
|
```
|
||||||
|
|
||||||
|
## Installation Workflow
|
||||||
|
|
||||||
|
1. **Welcome** — Language selection, system checks, rescue mode
|
||||||
|
2. **Location / Time** — Timezone selection (region/area dual ComboBox), GeoIP, NTP or manual date/time
|
||||||
|
3. **Keyboard** — Layout/variant selection, search, live preview (setxkbmap)
|
||||||
|
4. **Network** — WiFi scanning, connection, proxy
|
||||||
|
5. **Disk Partitioning** — Automatic (guided) or manual, LVM, LUKS
|
||||||
|
6. **Users** — Account creation, password strength indicator, root password, autologin
|
||||||
|
7. **Bootloader** — GRUB device, timeout, password, kernel parameters
|
||||||
|
8. **Summary** — Review all selections
|
||||||
|
9. **Installation** — Slideshow + progress bar + live log
|
||||||
|
10. **Finish** — Successful installation, reboot or return to live desktop
|
||||||
|
|
||||||
|
## Dependencies
|
||||||
|
|
||||||
|
| Dependency | Version | Purpose |
|
||||||
|
|---|---|---|
|
||||||
|
| `eframe` | 0.27 (wgpu) | egui framework, WebGPU backend |
|
||||||
|
| `egui_extras` | 0.27 (image, file, svg) | Image loading, file dialogs, SVG |
|
||||||
|
| `image` | 0.24 | PNG decoding |
|
||||||
|
| `serde` / `toml` | 1.0 / 0.8 | Branding and answer file serialization |
|
||||||
|
| `zbus` | 4.0 | D-Bus communication (COMAR) |
|
||||||
|
| `tokio` | 1.0 (full) | Async job queue |
|
||||||
|
| `sysinfo` | 0.30 | System information (RAM, disk, OS) |
|
||||||
|
| `rust-i18n` | 3.1 | Internationalization |
|
||||||
|
| `chrono` | 0.4 | Date/time operations |
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
GNU General Public License v3.0
|
||||||
|
|
||||||
|
This project is developed by the Pisi GNU/Linux team.
|
||||||
File diff suppressed because it is too large
Load Diff
|
After Width: | Height: | Size: 40 KiB |
@@ -61,6 +61,10 @@ password_label = "Password (at least 8 characters...)"
|
|||||||
password_confirm_label = "Confirm password"
|
password_confirm_label = "Confirm password"
|
||||||
hostname_label = "Computer name"
|
hostname_label = "Computer name"
|
||||||
username_invalid = "Invalid username. Must start with a lowercase letter or underscore, max 32 characters."
|
username_invalid = "Invalid username. Must start with a lowercase letter or underscore, max 32 characters."
|
||||||
|
username_too_long = "Username too long (maximum 32 characters)"
|
||||||
|
username_forbidden = "This username is reserved by the system and is forbidden"
|
||||||
|
username_invalid_start = "Username must start with a lowercase letter or underscore"
|
||||||
|
username_invalid_chars = "Only lowercase letters, digits, underscore and hyphen are allowed"
|
||||||
hostname_invalid = "Invalid hostname. Only letters, digits, and hyphens are allowed."
|
hostname_invalid = "Invalid hostname. Only letters, digits, and hyphens are allowed."
|
||||||
passwords_no_match = "Passwords do not match"
|
passwords_no_match = "Passwords do not match"
|
||||||
|
|
||||||
|
|||||||
@@ -61,6 +61,10 @@ password_label = "Parola (en az 8 karakter olmalıdır...)"
|
|||||||
password_confirm_label = "Parola (tekrar)"
|
password_confirm_label = "Parola (tekrar)"
|
||||||
hostname_label = "Bilgisayar adı"
|
hostname_label = "Bilgisayar adı"
|
||||||
username_invalid = "Geçersiz kullanıcı adı. Küçük harf veya alt çizgi ile başlamalı, en fazla 32 karakter."
|
username_invalid = "Geçersiz kullanıcı adı. Küçük harf veya alt çizgi ile başlamalı, en fazla 32 karakter."
|
||||||
|
username_too_long = "Kullanıcı adı çok uzun (maksimum 32 karakter)"
|
||||||
|
username_forbidden = "Bu kullanıcı adı sistem tarafından kullanılıyor ve yasaklı"
|
||||||
|
username_invalid_start = "Kullanıcı adı küçük harf veya alt çizgi ile başlamalı"
|
||||||
|
username_invalid_chars = "Sadece küçük harf, rakam, alt çizgi ve tire kullanılabilir"
|
||||||
hostname_invalid = "Geçersiz bilgisayar adı. Yalnızca harf, rakam ve tire içerebilir."
|
hostname_invalid = "Geçersiz bilgisayar adı. Yalnızca harf, rakam ve tire içerebilir."
|
||||||
passwords_no_match = "Parolalar eşleşmiyor"
|
passwords_no_match = "Parolalar eşleşmiyor"
|
||||||
|
|
||||||
|
|||||||
@@ -127,6 +127,7 @@ pub fn language_selector_ui(ui: &mut eframe::egui::Ui, state: &mut crate::instal
|
|||||||
ui.add(
|
ui.add(
|
||||||
eframe::egui::Image::new(eframe::egui::include_image!("../assets/icons/language-icon.svg"))
|
eframe::egui::Image::new(eframe::egui::include_image!("../assets/icons/language-icon.svg"))
|
||||||
.max_width(40.0)
|
.max_width(40.0)
|
||||||
|
.tint(crate::ui::theme::c_text())
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
+4
-2
@@ -207,6 +207,7 @@ pub struct GlobalState {
|
|||||||
pub current_step: usize,
|
pub current_step: usize,
|
||||||
pub available_disks: Vec<DiskInfo>,
|
pub available_disks: Vec<DiskInfo>,
|
||||||
pub install_progress: f32,
|
pub install_progress: f32,
|
||||||
|
pub install_sub_progress: f32,
|
||||||
pub install_log: Vec<String>,
|
pub install_log: Vec<String>,
|
||||||
pub install_current_file: Option<String>,
|
pub install_current_file: Option<String>,
|
||||||
pub demo_mode: bool,
|
pub demo_mode: bool,
|
||||||
@@ -274,10 +275,11 @@ impl Default for GlobalState {
|
|||||||
current_step: 0,
|
current_step: 0,
|
||||||
available_disks: Vec::new(),
|
available_disks: Vec::new(),
|
||||||
install_progress: 0.0,
|
install_progress: 0.0,
|
||||||
|
install_sub_progress: 0.0,
|
||||||
install_log: Vec::new(),
|
install_log: Vec::new(),
|
||||||
install_current_file: None,
|
install_current_file: None,
|
||||||
demo_mode: false,
|
demo_mode: false,
|
||||||
is_dark: false,
|
is_dark: true,
|
||||||
rescue_mode: false,
|
rescue_mode: false,
|
||||||
bootloader_device: String::new(),
|
bootloader_device: String::new(),
|
||||||
bootloader_timeout: 5,
|
bootloader_timeout: 5,
|
||||||
@@ -301,7 +303,7 @@ impl Default for GlobalState {
|
|||||||
oem_password_changed: false,
|
oem_password_changed: false,
|
||||||
selected_package_groups: Vec::new(),
|
selected_package_groups: Vec::new(),
|
||||||
display_manager: "sddm".to_string(),
|
display_manager: "sddm".to_string(),
|
||||||
autologin: true,
|
autologin: false,
|
||||||
desktop_environment: detect_desktop_environment(),
|
desktop_environment: detect_desktop_environment(),
|
||||||
branding_icons: std::collections::HashMap::new(),
|
branding_icons: std::collections::HashMap::new(),
|
||||||
branding_pisi_dark: String::new(),
|
branding_pisi_dark: String::new(),
|
||||||
|
|||||||
+109
-6
@@ -13,6 +13,7 @@
|
|||||||
|
|
||||||
use std::sync::atomic::{AtomicBool, Ordering};
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
use std::sync::mpsc;
|
use std::sync::mpsc;
|
||||||
|
use tokio::io::{AsyncBufReadExt, BufReader};
|
||||||
use tokio::process::Command;
|
use tokio::process::Command;
|
||||||
use rust_i18n::t;
|
use rust_i18n::t;
|
||||||
|
|
||||||
@@ -27,6 +28,10 @@ pub enum InstallMessage {
|
|||||||
Log(String),
|
Log(String),
|
||||||
/// Genel ilerleme; 0.0–1.0 arasında.
|
/// Genel ilerleme; 0.0–1.0 arasında.
|
||||||
Progress(f32),
|
Progress(f32),
|
||||||
|
/// Alt ilerleme (ör: rsync içindeki dosya kopyalama); 0.0–1.0 arasında.
|
||||||
|
SubProgress(f32),
|
||||||
|
/// Kopyalanmakta olan dosya adı.
|
||||||
|
CurrentFile(String),
|
||||||
/// Tüm işler başarıyla tamamlandı.
|
/// Tüm işler başarıyla tamamlandı.
|
||||||
Done,
|
Done,
|
||||||
/// Bir iş başarısız oldu; kurulum durdu.
|
/// Bir iş başarısız oldu; kurulum durdu.
|
||||||
@@ -54,6 +59,14 @@ impl UiSender {
|
|||||||
pub fn log(&self, s: impl Into<String>) {
|
pub fn log(&self, s: impl Into<String>) {
|
||||||
self.send(InstallMessage::Log(s.into()));
|
self.send(InstallMessage::Log(s.into()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn sub_progress(&self, p: f32) {
|
||||||
|
self.send(InstallMessage::SubProgress(p));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn current_file(&self, f: impl Into<String>) {
|
||||||
|
self.send(InstallMessage::CurrentFile(f.into()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─────────────────────────────────────────────
|
// ─────────────────────────────────────────────
|
||||||
@@ -179,8 +192,74 @@ impl Job for CopyFilesJob {
|
|||||||
args.push(self.source.clone());
|
args.push(self.source.clone());
|
||||||
args.push(self.target.clone());
|
args.push(self.target.clone());
|
||||||
|
|
||||||
let arg_refs: Vec<&str> = args.iter().map(|s| s.as_str()).collect();
|
if DEMO_MODE.load(Ordering::Relaxed) {
|
||||||
run_cmd("rsync", &arg_refs).await?;
|
ui.log("rsync /run/livecd/squashfs-root/ → /mnt (DEMO)");
|
||||||
|
let steps = 20;
|
||||||
|
for i in 0..steps {
|
||||||
|
let p = (i + 1) as f32 / steps as f32;
|
||||||
|
ui.current_file(format!("/usr/share/doc/{}/README.md", (i * 137) % 50));
|
||||||
|
ui.sub_progress(p);
|
||||||
|
tokio::time::sleep(std::time::Duration::from_millis(150)).await;
|
||||||
|
}
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut child = Command::new("rsync")
|
||||||
|
.args(&args)
|
||||||
|
.stdout(std::process::Stdio::piped())
|
||||||
|
.stderr(std::process::Stdio::piped())
|
||||||
|
.spawn()
|
||||||
|
.map_err(|e| format!("rsync çalıştırılamadı: {}", e))?;
|
||||||
|
|
||||||
|
let stderr = child.stderr.take()
|
||||||
|
.ok_or_else(|| "rsync stderr alınamadı".to_string())?;
|
||||||
|
let reader = BufReader::new(stderr);
|
||||||
|
let mut lines = reader.lines();
|
||||||
|
|
||||||
|
while let Some(line) = lines.next_line().await
|
||||||
|
.map_err(|e| format!("rsync çıktısı okunamadı: {}", e))?
|
||||||
|
{
|
||||||
|
let trimmed = line.trim();
|
||||||
|
// rsync --info=progress2 format (newlines when piped):
|
||||||
|
// 1,024 100% 512.00kB/s 0:00:00 (xfr#5, to-chk=99/105)
|
||||||
|
// Extract xfr# and to-chk for sub-progress
|
||||||
|
if let Some(xfr_pos) = trimmed.find("xfr#") {
|
||||||
|
let after_xfr = &trimmed[xfr_pos + 4..];
|
||||||
|
let xfr_end = after_xfr.find(|c: char| !c.is_ascii_digit()).unwrap_or(after_xfr.len());
|
||||||
|
let xfr: u64 = after_xfr[..xfr_end].parse().unwrap_or(0);
|
||||||
|
|
||||||
|
if let Some(to_chk_pos) = trimmed.find("to-chk=") {
|
||||||
|
let after_to = &trimmed[to_chk_pos + 7..];
|
||||||
|
let slash_pos = after_to.find('/').unwrap_or(after_to.len());
|
||||||
|
let total_end = after_to[slash_pos + 1..]
|
||||||
|
.find(|c: char| !c.is_ascii_digit())
|
||||||
|
.unwrap_or(after_to[slash_pos + 1..].len());
|
||||||
|
let total: u64 = after_to[slash_pos + 1..][..total_end].parse().unwrap_or(1);
|
||||||
|
|
||||||
|
let p = if total > 0 {
|
||||||
|
(xfr as f32) / (total as f32)
|
||||||
|
} else {
|
||||||
|
0.0
|
||||||
|
};
|
||||||
|
ui.sub_progress(p.min(1.0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract filename: lines starting with a path (not a number/space)
|
||||||
|
let first_char = trimmed.chars().next();
|
||||||
|
if let Some('/') = first_char {
|
||||||
|
ui.current_file(trimmed.to_string());
|
||||||
|
} else if trimmed.starts_with("./") || trimmed.starts_with("..") {
|
||||||
|
ui.current_file(trimmed.to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let status = child.wait().await
|
||||||
|
.map_err(|e| format!("rsync beklenemedi: {}", e))?;
|
||||||
|
|
||||||
|
if !status.success() {
|
||||||
|
return Err(format!("rsync çıkış kodu: {:?}", status.code()));
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -1186,10 +1265,22 @@ impl Job for ConfigureDisplayManagerJob {
|
|||||||
match dm.as_str() {
|
match dm.as_str() {
|
||||||
"sddm" => {
|
"sddm" => {
|
||||||
let conf = format!("{}/usr/lib/sddm/sddm.conf.d/sddm.conf", self.mount);
|
let conf = format!("{}/usr/lib/sddm/sddm.conf.d/sddm.conf", self.mount);
|
||||||
|
let session = match self.desktop_environment.as_str() {
|
||||||
|
"plasma" => "plasmax11.desktop",
|
||||||
|
"xfce" => "xfce.desktop",
|
||||||
|
"gnome" => "gnome.desktop",
|
||||||
|
"cinnamon" => "cinnamon.desktop",
|
||||||
|
"budgie" => "budgie-desktop.desktop",
|
||||||
|
"lxde" => "LXDE.desktop",
|
||||||
|
"lxqt" => "lxqt.desktop",
|
||||||
|
"mate" => "mate.desktop",
|
||||||
|
"lumina" => "lumina-desktop.desktop",
|
||||||
|
_ => "plasmax11.desktop",
|
||||||
|
};
|
||||||
let content = if self.autologin {
|
let content = if self.autologin {
|
||||||
format!(
|
format!(
|
||||||
"[Autologin]\nUser={}\nSession=plasmax11.desktop\n\n[Theme]\nCurrent=pisilinux24\n",
|
"[Autologin]\nUser={}\nSession={}\n\n[Theme]\nCurrent=pisilinux24\n",
|
||||||
self.username
|
self.username, session
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
"[Autologin]\nUser=\nSession=\n\n[Theme]\nCurrent=pisilinux24\n".to_string()
|
"[Autologin]\nUser=\nSession=\n\n[Theme]\nCurrent=pisilinux24\n".to_string()
|
||||||
@@ -1220,10 +1311,22 @@ impl Job for ConfigureDisplayManagerJob {
|
|||||||
let dir = format!("{}/etc/gdm", self.mount);
|
let dir = format!("{}/etc/gdm", self.mount);
|
||||||
let _ = tokio::fs::create_dir_all(&dir).await;
|
let _ = tokio::fs::create_dir_all(&dir).await;
|
||||||
let conf = format!("{}/etc/gdm/custom.conf", self.mount);
|
let conf = format!("{}/etc/gdm/custom.conf", self.mount);
|
||||||
|
let session = match self.desktop_environment.as_str() {
|
||||||
|
"plasma" => "plasmax11.desktop",
|
||||||
|
"xfce" => "xfce.desktop",
|
||||||
|
"gnome" => "gnome.desktop",
|
||||||
|
"cinnamon" => "cinnamon.desktop",
|
||||||
|
"budgie" => "budgie-desktop.desktop",
|
||||||
|
"lxde" => "LXDE.desktop",
|
||||||
|
"lxqt" => "lxqt.desktop",
|
||||||
|
"mate" => "mate.desktop",
|
||||||
|
"lumina" => "lumina-desktop.desktop",
|
||||||
|
_ => "gnome.desktop",
|
||||||
|
};
|
||||||
let content = if self.autologin {
|
let content = if self.autologin {
|
||||||
format!(
|
format!(
|
||||||
"[daemon]\nAutomaticLoginEnable=true\nAutomaticLogin={}\n",
|
"[daemon]\nAutomaticLoginEnable=true\nAutomaticLogin={}\nDefaultSession={}\n",
|
||||||
self.username
|
self.username, session
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
"[daemon]\n".to_string()
|
"[daemon]\n".to_string()
|
||||||
|
|||||||
+23
-26
@@ -7,7 +7,7 @@ mod branding;
|
|||||||
mod funct;
|
mod funct;
|
||||||
|
|
||||||
use installer::{GlobalState, InstallerStep, WelcomeStep};
|
use installer::{GlobalState, InstallerStep, WelcomeStep};
|
||||||
use steps::{LocationStep, KeyboardStep, UsersStep, SummaryStep, ExecutionStep, FinishStep, PartitionStep, BootloaderStep, NetworkStep, DisplayManagerStep};
|
use steps::{LocationStep, KeyboardStep, UsersStep, SummaryStep, ExecutionStep, FinishStep, PartitionStep, BootloaderStep, NetworkStep};
|
||||||
use eframe::egui;
|
use eframe::egui;
|
||||||
use rust_i18n::t;
|
use rust_i18n::t;
|
||||||
|
|
||||||
@@ -48,8 +48,8 @@ fn run_gui(demo_mode: bool) -> eframe::Result<()> {
|
|||||||
Box::new(move |cc| {
|
Box::new(move |cc| {
|
||||||
// Görsel yükleyicileri etkinleştir (PNG desteği)
|
// Görsel yükleyicileri etkinleştir (PNG desteği)
|
||||||
egui_extras::install_image_loaders(&cc.egui_ctx);
|
egui_extras::install_image_loaders(&cc.egui_ctx);
|
||||||
ui::theme::set_theme_mode(false);
|
ui::theme::set_theme_mode(true);
|
||||||
ui::theme::apply(&cc.egui_ctx, false);
|
ui::theme::apply(&cc.egui_ctx, true);
|
||||||
let mut app = YaliApp::default();
|
let mut app = YaliApp::default();
|
||||||
app.state.demo_mode = demo_mode;
|
app.state.demo_mode = demo_mode;
|
||||||
Box::new(app)
|
Box::new(app)
|
||||||
@@ -188,6 +188,8 @@ fn run_auto_install(path: &str, demo_mode: bool) -> eframe::Result<()> {
|
|||||||
print!("\r {}: [{:50}] {:.0}%", t!("progress"), "=".repeat((p * 50.0) as usize), p * 100.0);
|
print!("\r {}: [{:50}] {:.0}%", t!("progress"), "=".repeat((p * 50.0) as usize), p * 100.0);
|
||||||
let _ = std::io::Write::flush(&mut std::io::stdout());
|
let _ = std::io::Write::flush(&mut std::io::stdout());
|
||||||
}
|
}
|
||||||
|
Ok(InstallMessage::SubProgress(_)) => {}
|
||||||
|
Ok(InstallMessage::CurrentFile(_)) => {}
|
||||||
Ok(InstallMessage::Done) => {
|
Ok(InstallMessage::Done) => {
|
||||||
println!("\n\n{}: {}", t!("installation_done"), t!("installation_done_description"));
|
println!("\n\n{}: {}", t!("installation_done"), t!("installation_done_description"));
|
||||||
break;
|
break;
|
||||||
@@ -229,7 +231,6 @@ impl Default for YaliApp {
|
|||||||
Box::new(PartitionStep::default()),
|
Box::new(PartitionStep::default()),
|
||||||
Box::new(UsersStep::default()),
|
Box::new(UsersStep::default()),
|
||||||
Box::new(BootloaderStep::default()),
|
Box::new(BootloaderStep::default()),
|
||||||
Box::new(DisplayManagerStep::default()),
|
|
||||||
Box::new(SummaryStep),
|
Box::new(SummaryStep),
|
||||||
Box::new(ExecutionStep::default()),
|
Box::new(ExecutionStep::default()),
|
||||||
Box::new(FinishStep),
|
Box::new(FinishStep),
|
||||||
@@ -407,24 +408,24 @@ impl eframe::App for YaliApp {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Alt kısım: tema değiştirme
|
// Alt kısım: tema değiştirme
|
||||||
// ui.with_layout(egui::Layout::bottom_up(egui::Align::Center), |ui| {
|
ui.with_layout(egui::Layout::bottom_up(egui::Align::Center), |ui| {
|
||||||
// ui.add_space(6.0);
|
ui.add_space(6.0);
|
||||||
|
|
||||||
// let theme_icon = if self.state.is_dark { "☀" } else { "🌙" };
|
let theme_icon = if self.state.is_dark { "☀" } else { "🌙" };
|
||||||
// let theme_label = if self.state.is_dark { "Light" } else { "Dark" };
|
let theme_label = if self.state.is_dark { "Light" } else { "Dark" };
|
||||||
// if ui.add(
|
if ui.add(
|
||||||
// egui::Button::new(
|
egui::Button::new(
|
||||||
// egui::RichText::new(format!("{} {}", theme_icon, theme_label))
|
egui::RichText::new(format!("{} {}", theme_icon, theme_label))
|
||||||
// .size(12.0)
|
.size(12.0)
|
||||||
// )
|
)
|
||||||
// .rounding(egui::Rounding::same(6.0))
|
.rounding(egui::Rounding::same(6.0))
|
||||||
// .min_size(egui::Vec2::new(100.0, 28.0))
|
.min_size(egui::Vec2::new(100.0, 28.0))
|
||||||
// ).clicked() {
|
).clicked() {
|
||||||
// self.state.is_dark = !self.state.is_dark;
|
self.state.is_dark = !self.state.is_dark;
|
||||||
// ui::theme::set_theme_mode(self.state.is_dark);
|
ui::theme::set_theme_mode(self.state.is_dark);
|
||||||
// ui::theme::apply(ctx, self.state.is_dark);
|
ui::theme::apply(ctx, self.state.is_dark);
|
||||||
// }
|
}
|
||||||
// });
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// ── Alt panel: navigasyon ────────────────────────────
|
// ── Alt panel: navigasyon ────────────────────────────
|
||||||
@@ -455,11 +456,7 @@ impl eframe::App for YaliApp {
|
|||||||
// if !is_exec && !is_finish && ui::theme::danger_button(ui, &t!("cancel")).clicked() {
|
// if !is_exec && !is_finish && ui::theme::danger_button(ui, &t!("cancel")).clicked() {
|
||||||
// self.show_cancel_dialog = true;
|
// self.show_cancel_dialog = true;
|
||||||
// };
|
// };
|
||||||
let next_clicked = if is_valid {
|
let next_clicked = ui::theme::primary_button_enabled(ui, &next_label, is_valid).clicked();
|
||||||
ui::theme::primary_button(ui, &next_label).clicked()
|
|
||||||
} else {
|
|
||||||
ui::theme::secondary_button(ui, &next_label).clicked()
|
|
||||||
};
|
|
||||||
if next_clicked {
|
if next_clicked {
|
||||||
self.state.current_step += 1;
|
self.state.current_step += 1;
|
||||||
}
|
}
|
||||||
|
|||||||
+57
-54
@@ -2,6 +2,7 @@ use eframe::egui;
|
|||||||
use rust_i18n::t;
|
use rust_i18n::t;
|
||||||
use crate::installer::{GlobalState, InstallerStep};
|
use crate::installer::{GlobalState, InstallerStep};
|
||||||
use crate::ui::theme;
|
use crate::ui::theme;
|
||||||
|
use crate::funct::{form_label, form_input_text, form_input_password};
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct BootloaderStep {
|
pub struct BootloaderStep {
|
||||||
@@ -26,77 +27,79 @@ impl InstallerStep for BootloaderStep {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn show(&mut self, ui: &mut egui::Ui, state: &mut GlobalState) {
|
fn show(&mut self, ui: &mut egui::Ui, state: &mut GlobalState) {
|
||||||
ui.heading(t!("bootloader_title"));
|
theme::section_heading(ui, &t!("bootloader_title"));
|
||||||
ui.label(t!("bootloader_description"));
|
ui.label(
|
||||||
|
egui::RichText::new(t!("bootloader_description"))
|
||||||
|
.color(theme::c_text_dim())
|
||||||
|
.size(13.0),
|
||||||
|
);
|
||||||
ui.add_space(12.0);
|
ui.add_space(12.0);
|
||||||
|
|
||||||
egui::Grid::new("bootloader_grid")
|
// ─── Önyükleyici Formu ──────────────────────────────────
|
||||||
.num_columns(2)
|
egui::Frame::group(ui.style())
|
||||||
.spacing([12.0, 10.0])
|
.fill(theme::c_bg_widget())
|
||||||
|
.rounding(6.0)
|
||||||
|
.inner_margin(egui::Margin::symmetric(10.0, 10.0))
|
||||||
.show(ui, |ui| {
|
.show(ui, |ui| {
|
||||||
// Hedef aygıt
|
ui.set_width(ui.available_width());
|
||||||
ui.label(t!("bootloader_device_label"));
|
ui.vertical(|ui| {
|
||||||
ui.horizontal(|ui| {
|
ui.add_space(10.0);
|
||||||
let mut device_list: Vec<String> = state.available_disks.iter()
|
|
||||||
.map(|d| d.name.clone())
|
// Hedef aygıt
|
||||||
.collect();
|
form_label(ui, &t!("bootloader_device_label"));
|
||||||
if device_list.is_empty() {
|
ui.horizontal(|ui| {
|
||||||
device_list.push(state.bootloader_device.clone());
|
let mut device_list: Vec<String> = state.available_disks.iter()
|
||||||
}
|
.map(|d| d.name.clone())
|
||||||
let current = if state.bootloader_device.is_empty() {
|
.collect();
|
||||||
device_list[0].clone()
|
if device_list.is_empty() {
|
||||||
} else {
|
device_list.push(state.bootloader_device.clone());
|
||||||
state.bootloader_device.clone()
|
|
||||||
};
|
|
||||||
theme::show_combo_box(ui, "boot_device_select", current.as_str(), ui.available_width(), |ui, w| {
|
|
||||||
for dev in &device_list {
|
|
||||||
theme::selectable_value(ui, &mut state.bootloader_device, dev.clone(), dev.as_str(), w);
|
|
||||||
}
|
}
|
||||||
|
let current = if state.bootloader_device.is_empty() {
|
||||||
|
device_list[0].clone()
|
||||||
|
} else {
|
||||||
|
state.bootloader_device.clone()
|
||||||
|
};
|
||||||
|
theme::show_combo_box(ui, "boot_device_select", current.as_str(), ui.available_width(), |ui, w| {
|
||||||
|
for dev in &device_list {
|
||||||
|
theme::selectable_value(ui, &mut state.bootloader_device, dev.clone(), dev.as_str(), w);
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
ui.label(
|
ui.label(
|
||||||
egui::RichText::new(t!("bootloader_device_hint"))
|
egui::RichText::new(t!("bootloader_device_hint"))
|
||||||
.size(11.0)
|
.size(11.0)
|
||||||
.color(egui::Color32::from_rgb(120, 120, 140)),
|
.color(theme::c_text_dim()),
|
||||||
);
|
);
|
||||||
});
|
ui.add_space(6.0);
|
||||||
ui.end_row();
|
|
||||||
|
|
||||||
// Timeout
|
// Timeout
|
||||||
ui.label(t!("bootloader_timeout_label"));
|
form_label(ui, &t!("bootloader_timeout_label"));
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
ui.add(egui::Slider::new(&mut state.bootloader_timeout, 0..=60)
|
ui.add(egui::Slider::new(&mut state.bootloader_timeout, 0..=60)
|
||||||
.text(t!("bootloader_timeout_suffix")));
|
.text(t!("bootloader_timeout_suffix")));
|
||||||
});
|
});
|
||||||
ui.end_row();
|
ui.add_space(6.0);
|
||||||
|
|
||||||
// GRUB şifresi
|
// GRUB şifresi
|
||||||
ui.label(t!("bootloader_password_label"));
|
form_label(ui, &t!("bootloader_password_label"));
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
if self.show_password {
|
let show_pwd = &mut self.show_password;
|
||||||
ui.text_edit_singleline(&mut state.bootloader_password);
|
form_input_password(ui, &mut state.bootloader_password, show_pwd);
|
||||||
} else {
|
});
|
||||||
ui.add(egui::TextEdit::singleline(&mut state.bootloader_password).password(true));
|
ui.add_space(6.0);
|
||||||
}
|
|
||||||
if ui.small_button(if self.show_password { "🙈" } else { "👁" }).clicked() {
|
|
||||||
self.show_password = !self.show_password;
|
|
||||||
}
|
|
||||||
if !state.bootloader_password.is_empty() {
|
|
||||||
ui.colored_label(egui::Color32::from_rgb(200, 180, 40), "⚠");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
ui.end_row();
|
|
||||||
|
|
||||||
// Kernel parametreleri
|
// Kernel parametreleri
|
||||||
ui.label(t!("bootloader_kernel_label"));
|
form_label(ui, &t!("bootloader_kernel_label"));
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
ui.text_edit_singleline(&mut state.kernel_options);
|
form_input_text(ui, &mut state.kernel_options);
|
||||||
|
});
|
||||||
ui.label(
|
ui.label(
|
||||||
egui::RichText::new(t!("bootloader_kernel_hint"))
|
egui::RichText::new(t!("bootloader_kernel_hint"))
|
||||||
.size(11.0)
|
.size(11.0)
|
||||||
.color(egui::Color32::from_rgb(120, 120, 140)),
|
.color(theme::c_text_dim()),
|
||||||
);
|
);
|
||||||
|
|
||||||
});
|
});
|
||||||
ui.end_row();
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ impl InstallerStep for ExecutionStep {
|
|||||||
self.slideshow = Some(Slideshow::new(&desktop, state.branding_icons.clone(), state.branding_slides.clone()));
|
self.slideshow = Some(Slideshow::new(&desktop, state.branding_icons.clone(), state.branding_slides.clone()));
|
||||||
|
|
||||||
state.install_progress = 0.0;
|
state.install_progress = 0.0;
|
||||||
|
state.install_sub_progress = 0.0;
|
||||||
state.install_log.clear();
|
state.install_log.clear();
|
||||||
state.install_current_file = None;
|
state.install_current_file = None;
|
||||||
state.install_log.push(t!("install_starting").to_string());
|
state.install_log.push(t!("install_starting").to_string());
|
||||||
@@ -182,6 +183,12 @@ impl InstallerStep for ExecutionStep {
|
|||||||
Ok(InstallMessage::Progress(p)) => {
|
Ok(InstallMessage::Progress(p)) => {
|
||||||
state.install_progress = p;
|
state.install_progress = p;
|
||||||
}
|
}
|
||||||
|
Ok(InstallMessage::SubProgress(p)) => {
|
||||||
|
state.install_sub_progress = p;
|
||||||
|
}
|
||||||
|
Ok(InstallMessage::CurrentFile(f)) => {
|
||||||
|
state.install_current_file = Some(f);
|
||||||
|
}
|
||||||
Ok(InstallMessage::Done) => {
|
Ok(InstallMessage::Done) => {
|
||||||
state.install_progress = 1.0;
|
state.install_progress = 1.0;
|
||||||
self.finished = true;
|
self.finished = true;
|
||||||
@@ -305,6 +312,16 @@ impl InstallerStep for ExecutionStep {
|
|||||||
.animate(running)
|
.animate(running)
|
||||||
.desired_height(10.0),
|
.desired_height(10.0),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if state.install_sub_progress > 0.0 {
|
||||||
|
ui.add_space(4.0);
|
||||||
|
ui.add(
|
||||||
|
egui::ProgressBar::new(state.install_sub_progress)
|
||||||
|
.animate(running)
|
||||||
|
.desired_height(6.0)
|
||||||
|
.fill(theme::c_accent_dim()),
|
||||||
|
);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Durum etiketi
|
// Durum etiketi
|
||||||
|
|||||||
+142
-41
@@ -1,4 +1,4 @@
|
|||||||
use eframe::egui::{self, Color32};
|
use eframe::egui;
|
||||||
use rust_i18n::t;
|
use rust_i18n::t;
|
||||||
use crate::installer::{GlobalState, InstallerStep};
|
use crate::installer::{GlobalState, InstallerStep};
|
||||||
use crate::ui::theme;
|
use crate::ui::theme;
|
||||||
@@ -92,6 +92,7 @@ fn split_code_desc(line: &str) -> Option<(&str, &str)> {
|
|||||||
Some((code, desc))
|
Some((code, desc))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn layout_display_name(layout: &str, layouts: &[(String, String)]) -> String {
|
fn layout_display_name(layout: &str, layouts: &[(String, String)]) -> String {
|
||||||
layouts
|
layouts
|
||||||
.iter()
|
.iter()
|
||||||
@@ -112,7 +113,6 @@ fn variant_display_name(
|
|||||||
.unwrap_or_else(|| variant.to_string())
|
.unwrap_or_else(|| variant.to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fn apply_system_keyboard_layout(layout: &str, variant: &str) {
|
fn apply_system_keyboard_layout(layout: &str, variant: &str) {
|
||||||
if std::env::var("DISPLAY").is_ok() {
|
if std::env::var("DISPLAY").is_ok() {
|
||||||
let mut cmd = std::process::Command::new("setxkbmap");
|
let mut cmd = std::process::Command::new("setxkbmap");
|
||||||
@@ -141,7 +141,6 @@ impl InstallerStep for KeyboardStep {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn on_enter(&mut self, state: &mut GlobalState) {
|
fn on_enter(&mut self, state: &mut GlobalState) {
|
||||||
// Varsayılan düzen sistemi diline göre seçilir
|
|
||||||
if state.keyboard_layout.is_empty() {
|
if state.keyboard_layout.is_empty() {
|
||||||
state.keyboard_layout = if state.language == "tr" {
|
state.keyboard_layout = if state.language == "tr" {
|
||||||
"tr".to_string()
|
"tr".to_string()
|
||||||
@@ -162,64 +161,166 @@ impl InstallerStep for KeyboardStep {
|
|||||||
|
|
||||||
ui.add_space(12.0);
|
ui.add_space(12.0);
|
||||||
|
|
||||||
// ── Klavye görseli (xkbcommon ile çizilir, temaya uyumlu) ─
|
// ── Klavye görseli ────────────────────────────────────
|
||||||
if let Some(kle) = crate::ui::kle_svg::compile_layout(&state.keyboard_layout, &state.keyboard_variant) {
|
let 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));
|
.unwrap_or_else(|| crate::ui::kle_svg::empty_layout());
|
||||||
crate::ui::kle_svg::draw_layout(ui, &kle, max);
|
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);
|
ui.add_space(12.0);
|
||||||
|
|
||||||
// ── Layout ve Varyant ComboBox'ları yan yana ──────────────
|
// ── Label genişlik hesaplama ──────────────────────────
|
||||||
let available_variants: Vec<_> = self
|
let layout_label = t!("keyboard_layout_label").to_string();
|
||||||
.variants
|
let variant_label = t!("keyboard_variant_label").to_string();
|
||||||
.iter()
|
let label_w = ui.fonts(|f| {
|
||||||
.filter(|(layout, _, _)| layout == &state.keyboard_layout)
|
let measure = |s: &str| -> f32 {
|
||||||
.collect();
|
f.layout_no_wrap(s.to_string(), egui::FontId::proportional(14.0), egui::Color32::WHITE)
|
||||||
|
.size().x
|
||||||
let current_variant_name = if state.keyboard_variant.is_empty() {
|
};
|
||||||
t!("keyboard_variant_default").to_string()
|
measure(&layout_label).max(measure(&variant_label)).ceil()
|
||||||
} else {
|
}) + 16.0;
|
||||||
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);
|
|
||||||
|
|
||||||
|
// ── Düzen (Layout) ────────────────────────────────────
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
ui.vertical(|ui| {
|
ui.add_space(6.0);
|
||||||
ui.add_space(6.0);
|
ui.allocate_ui_with_layout(
|
||||||
ui.label(egui::RichText::new(t!("keyboard_layout_label")).strong().color(theme::c_text()));
|
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);
|
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 {
|
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) {
|
if !self.variants.iter().any(|(layout, _, _)| layout == code) {
|
||||||
state.keyboard_variant.clear();
|
state.keyboard_variant.clear();
|
||||||
}
|
}
|
||||||
apply_system_keyboard_layout(&state.keyboard_layout, &state.keyboard_variant);
|
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| {
|
// ── Varyant ───────────────────────────────────────────
|
||||||
ui.add_space(6.0);
|
ui.horizontal(|ui| {
|
||||||
ui.label(egui::RichText::new(t!("keyboard_variant_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(&variant_label).strong().color(theme::c_text()));
|
||||||
|
},
|
||||||
|
);
|
||||||
ui.add_space(8.0);
|
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 vw = ui.available_width();
|
||||||
let default_variant = String::new();
|
let available_variants: Vec<(String, String)> = self
|
||||||
if theme::selectable_value(ui, &mut state.keyboard_variant, default_variant, &t!("keyboard_variant_default"), w).clicked() {
|
.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, "");
|
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);
|
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.label(t!("keyboard_test_label"));
|
||||||
ui.add_space(4.0);
|
ui.add_space(4.0);
|
||||||
egui::Frame::group(ui.style())
|
egui::Frame::group(ui.style())
|
||||||
.fill(Color32::from_rgb(255, 255, 255))
|
.fill(theme::c_keyboard_bg())
|
||||||
.rounding(6.0)
|
.rounding(6.0)
|
||||||
.show(ui, |ui| {
|
.show(ui, |ui| {
|
||||||
ui.add(
|
ui.add(
|
||||||
|
|||||||
+1
-2
@@ -7,7 +7,7 @@ pub mod partition;
|
|||||||
pub mod bootloader;
|
pub mod bootloader;
|
||||||
pub mod network;
|
pub mod network;
|
||||||
pub mod rescue;
|
pub mod rescue;
|
||||||
pub mod display_manager;
|
//pub mod display_manager;
|
||||||
pub mod netinstall;
|
pub mod netinstall;
|
||||||
pub mod location;
|
pub mod location;
|
||||||
|
|
||||||
@@ -20,5 +20,4 @@ pub use partition::PartitionStep;
|
|||||||
pub use bootloader::BootloaderStep;
|
pub use bootloader::BootloaderStep;
|
||||||
pub use network::NetworkStep;
|
pub use network::NetworkStep;
|
||||||
pub use rescue::RescueStep;
|
pub use rescue::RescueStep;
|
||||||
pub use display_manager::DisplayManagerStep;
|
|
||||||
pub use location::LocationStep;
|
pub use location::LocationStep;
|
||||||
|
|||||||
+150
-41
@@ -6,16 +6,18 @@ use crate::funct::{ form_input_text, form_input_password, form_label, form_check
|
|||||||
/// Kullanıcı adı, şifre, hostname ve yönetici (root) şifresi ayarları adımı.
|
/// Kullanıcı adı, şifre, hostname ve yönetici (root) şifresi ayarları adımı.
|
||||||
pub struct UsersStep {
|
pub struct UsersStep {
|
||||||
use_same_for_admin: bool,
|
use_same_for_admin: bool,
|
||||||
|
autologin: bool,
|
||||||
show_password: bool,
|
show_password: bool,
|
||||||
show_password_confirm: bool,
|
show_password_confirm: bool,
|
||||||
show_root_password: bool, // <-- Eklendi
|
show_root_password: bool,
|
||||||
show_root_password_confirm: bool, // <-- Eklendi
|
show_root_password_confirm: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for UsersStep {
|
impl Default for UsersStep {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
use_same_for_admin: true,
|
use_same_for_admin: true,
|
||||||
|
autologin: false,
|
||||||
show_password: false,
|
show_password: false,
|
||||||
show_password_confirm: false,
|
show_password_confirm: false,
|
||||||
show_root_password: false,
|
show_root_password: false,
|
||||||
@@ -30,16 +32,22 @@ impl Default for UsersStep {
|
|||||||
/// - Boşlukları `-`'ye çevirir
|
/// - Boşlukları `-`'ye çevirir
|
||||||
/// - Geçersiz karakterleri temizler
|
/// - Geçersiz karakterleri temizler
|
||||||
fn derive_username(real_name: &str) -> String {
|
fn derive_username(real_name: &str) -> String {
|
||||||
let mut name = real_name.to_lowercase();
|
let name = real_name
|
||||||
name = name
|
|
||||||
.replace('ş', "s")
|
.replace('ş', "s")
|
||||||
|
.replace('Ş', "s")
|
||||||
.replace('ğ', "g")
|
.replace('ğ', "g")
|
||||||
|
.replace('Ğ', "g")
|
||||||
.replace('ı', "i")
|
.replace('ı', "i")
|
||||||
|
.replace('İ', "i")
|
||||||
.replace('ö', "o")
|
.replace('ö', "o")
|
||||||
|
.replace('Ö', "o")
|
||||||
.replace('ü', "u")
|
.replace('ü', "u")
|
||||||
|
.replace('Ü', "u")
|
||||||
.replace('ç', "c")
|
.replace('ç', "c")
|
||||||
|
.replace('Ç', "c")
|
||||||
.replace(' ', "-")
|
.replace(' ', "-")
|
||||||
.replace('_', "-");
|
.replace('_', "-")
|
||||||
|
.to_lowercase();
|
||||||
let valid: String = name.chars().filter(|c| c.is_ascii_alphanumeric() || *c == '-' || *c == '_').collect();
|
let valid: String = name.chars().filter(|c| c.is_ascii_alphanumeric() || *c == '-' || *c == '_').collect();
|
||||||
let trimmed = valid.trim_matches('-').to_string();
|
let trimmed = valid.trim_matches('-').to_string();
|
||||||
if trimmed.is_empty() {
|
if trimmed.is_empty() {
|
||||||
@@ -54,17 +62,44 @@ fn derive_username(real_name: &str) -> String {
|
|||||||
/// Kullanıcı adının geçerliliğini kontrol eder.
|
/// Kullanıcı adının geçerliliğini kontrol eder.
|
||||||
/// Linux kuralları: küçük harf veya alt çizgi ile başlamalı,
|
/// Linux kuralları: küçük harf veya alt çizgi ile başlamalı,
|
||||||
/// en fazla 32 karakter, yalnızca [a-z0-9_-] içermeli.
|
/// en fazla 32 karakter, yalnızca [a-z0-9_-] içermeli.
|
||||||
fn is_valid_username(s: &str) -> bool {
|
/// Ayrıca sistem kullanıcı adları yasaklıdır.
|
||||||
if s.is_empty() || s.len() > 32 {
|
/// Hata varsa hata mesajını döndürür, yoksa None döndürür.
|
||||||
return false;
|
fn validate_username(s: &str) -> Option<String> {
|
||||||
|
if s.is_empty() {
|
||||||
|
return None;
|
||||||
}
|
}
|
||||||
|
if s.len() > 32 {
|
||||||
|
return Some(t!("username_too_long").to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Yasaklı sistem kullanıcı adları
|
||||||
|
let forbidden = [
|
||||||
|
"root", "daemon", "bin", "sys", "sync", "games", "man", "lp",
|
||||||
|
"mail", "news", "uucp", "proxy", "www-data", "backup", "list",
|
||||||
|
"irc", "gnats", "nobody", "systemd-timesync", "systemd-network",
|
||||||
|
"systemd-resolve", "systemd-bus-proxy", "messagebus", "sshd",
|
||||||
|
"ftp", "apache", "nginx", "mysql", "postgres", "docker",
|
||||||
|
];
|
||||||
|
if forbidden.contains(&s) {
|
||||||
|
return Some(t!("username_forbidden").to_string());
|
||||||
|
}
|
||||||
|
|
||||||
let mut chars = s.chars();
|
let mut chars = s.chars();
|
||||||
// İlk karakter küçük harf veya alt çizgi olmalı
|
// İlk karakter küçük harf veya alt çizgi olmalı
|
||||||
match chars.next() {
|
match chars.next() {
|
||||||
Some(c) if c.is_ascii_lowercase() || c == '_' => {}
|
Some(c) if c.is_ascii_lowercase() || c == '_' => {}
|
||||||
_ => return false,
|
_ => return Some(t!("username_invalid_start").to_string()),
|
||||||
}
|
}
|
||||||
chars.all(|c| c.is_ascii_lowercase() || c.is_ascii_digit() || c == '_' || c == '-')
|
if !chars.all(|c| c.is_ascii_lowercase() || c.is_ascii_digit() || c == '_' || c == '-') {
|
||||||
|
return Some(t!("username_invalid_chars").to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Kullanıcı adının geçerliliğini kontrol eder (basit bool versiyonu).
|
||||||
|
fn is_valid_username(s: &str) -> bool {
|
||||||
|
validate_username(s).is_none()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Hostname kuralları: 1-63 karakter, yalnızca [a-zA-Z0-9-], tire ile başlayamaz/bitemez.
|
/// Hostname kuralları: 1-63 karakter, yalnızca [a-zA-Z0-9-], tire ile başlayamaz/bitemez.
|
||||||
@@ -121,6 +156,9 @@ impl InstallerStep for UsersStep {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn on_enter(&mut self, state: &mut GlobalState) {
|
fn on_enter(&mut self, state: &mut GlobalState) {
|
||||||
|
// Autologin senkronizasyonu
|
||||||
|
self.autologin = state.autologin;
|
||||||
|
|
||||||
// Gerçek ad varsa ama kullanıcı adı yoksa türet
|
// Gerçek ad varsa ama kullanıcı adı yoksa türet
|
||||||
if !state.real_name.is_empty() && state.username.is_empty() {
|
if !state.real_name.is_empty() && state.username.is_empty() {
|
||||||
state.username = derive_username(&state.real_name);
|
state.username = derive_username(&state.real_name);
|
||||||
@@ -155,7 +193,7 @@ impl InstallerStep for UsersStep {
|
|||||||
|
|
||||||
// ─── Kullanıcı Bilgileri Formu ──────────────────────────────────
|
// ─── Kullanıcı Bilgileri Formu ──────────────────────────────────
|
||||||
egui::Frame::group(ui.style())
|
egui::Frame::group(ui.style())
|
||||||
.fill(egui::Color32::from_rgb(240, 243, 198))
|
.fill(theme::c_bg_widget())
|
||||||
.rounding(6.0)
|
.rounding(6.0)
|
||||||
.inner_margin(egui::Margin::symmetric(10.0, 10.0))
|
.inner_margin(egui::Margin::symmetric(10.0, 10.0))
|
||||||
.show(ui, |ui| {
|
.show(ui, |ui| {
|
||||||
@@ -168,27 +206,54 @@ impl InstallerStep for UsersStep {
|
|||||||
form_label(ui, &t!("real_name_label"));
|
form_label(ui, &t!("real_name_label"));
|
||||||
{
|
{
|
||||||
let prev = state.real_name.clone();
|
let prev = state.real_name.clone();
|
||||||
form_input_text(ui, &mut state.real_name);
|
ui.horizontal(|ui| {
|
||||||
|
form_input_text(ui, &mut state.real_name);
|
||||||
|
|
||||||
|
|
||||||
|
if !state.real_name.is_empty() {
|
||||||
|
ui.add(
|
||||||
|
egui::Image::new(egui::include_image!("../../assets/icons/check.svg"))
|
||||||
|
.max_width(18.0).max_height(18.0)
|
||||||
|
.tint(theme::c_success())
|
||||||
|
);
|
||||||
|
}else{
|
||||||
|
ui.add(
|
||||||
|
egui::Image::new(egui::include_image!("../../assets/icons/fail-icon.svg"))
|
||||||
|
.max_width(18.0).max_height(18.0)
|
||||||
|
.tint(theme::c_error())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
if state.real_name != prev && !state.real_name.is_empty() {
|
if state.real_name != prev && !state.real_name.is_empty() {
|
||||||
state.username = derive_username(&state.real_name);
|
state.username = derive_username(&state.real_name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !state.real_name.is_empty() {
|
|
||||||
ui.colored_label(theme::c_success(), "✅");
|
|
||||||
} else {
|
|
||||||
ui.colored_label(theme::c_error(), "❌");
|
|
||||||
}
|
|
||||||
ui.add_space(6.0);
|
ui.add_space(6.0);
|
||||||
|
|
||||||
// Kullanıcı adı (otomatik türetilir, düzenlenebilir)
|
// Kullanıcı adı (otomatik türetilir, düzenlenebilir)
|
||||||
form_label(ui, &t!("username_label"));
|
form_label(ui, &t!("username_label"));
|
||||||
form_input_text(ui, &mut state.username);
|
|
||||||
if !state.username.is_empty() {
|
ui.horizontal(|ui| {
|
||||||
if is_valid_username(&state.username) {
|
form_input_text(ui, &mut state.username);
|
||||||
ui.colored_label(theme::c_success(), "✅");
|
if !state.username.is_empty() {
|
||||||
} else {
|
if let Some(_err) = validate_username(&state.username) {
|
||||||
ui.colored_label(theme::c_warning(), t!("username_invalid"));
|
ui.add(
|
||||||
|
egui::Image::new(egui::include_image!("../../assets/icons/fail-icon.svg"))
|
||||||
|
.max_width(18.0).max_height(18.0)
|
||||||
|
.tint(theme::c_error())
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
ui.add(
|
||||||
|
egui::Image::new(egui::include_image!("../../assets/icons/check.svg"))
|
||||||
|
.max_width(18.0).max_height(18.0)
|
||||||
|
.tint(theme::c_success())
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
if let Some(err) = validate_username(&state.username) {
|
||||||
|
ui.colored_label(theme::c_error(), err);
|
||||||
}
|
}
|
||||||
ui.add_space(6.0);
|
ui.add_space(6.0);
|
||||||
|
|
||||||
@@ -221,27 +286,50 @@ impl InstallerStep for UsersStep {
|
|||||||
form_label(ui, &t!("password_confirm_label"));
|
form_label(ui, &t!("password_confirm_label"));
|
||||||
{
|
{
|
||||||
let show_pwd = &mut self.show_password_confirm;
|
let show_pwd = &mut self.show_password_confirm;
|
||||||
form_input_password(ui, &mut state.password_confirm, show_pwd);
|
ui.horizontal(|ui| {
|
||||||
|
form_input_password(ui, &mut state.password_confirm, show_pwd);
|
||||||
|
if !state.password_confirm.is_empty() {
|
||||||
|
if state.password == state.password_confirm {
|
||||||
|
ui.add(
|
||||||
|
egui::Image::new(egui::include_image!("../../assets/icons/check.svg"))
|
||||||
|
.max_width(18.0).max_height(18.0)
|
||||||
|
.tint(theme::c_success())
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
ui.add(
|
||||||
|
egui::Image::new(egui::include_image!("../../assets/icons/fail-icon.svg"))
|
||||||
|
.max_width(18.0).max_height(18.0)
|
||||||
|
.tint(theme::c_error())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
if !state.password_confirm.is_empty() {
|
if !state.password_confirm.is_empty() && state.password != state.password_confirm {
|
||||||
if state.password == state.password_confirm {
|
ui.colored_label(theme::c_warning(), t!("passwords_no_match"));
|
||||||
ui.colored_label(theme::c_success(), "✅");
|
|
||||||
} else {
|
|
||||||
ui.colored_label(theme::c_warning(), t!("passwords_no_match"));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
ui.add_space(6.0);
|
ui.add_space(6.0);
|
||||||
|
|
||||||
// Hostname
|
// Hostname
|
||||||
form_label(ui, &t!("hostname_label"));
|
form_label(ui, &t!("hostname_label"));
|
||||||
form_input_text(ui, &mut state.hostname);
|
ui.horizontal(|ui| {
|
||||||
if !state.hostname.is_empty() {
|
form_input_text(ui, &mut state.hostname);
|
||||||
if is_valid_hostname(&state.hostname) {
|
if !state.hostname.is_empty() {
|
||||||
ui.colored_label(theme::c_success(), "✅");
|
if is_valid_hostname(&state.hostname) {
|
||||||
} else {
|
ui.add(
|
||||||
ui.colored_label(theme::c_warning(), t!("hostname_invalid"));
|
egui::Image::new(egui::include_image!("../../assets/icons/check.svg"))
|
||||||
|
.max_width(18.0).max_height(18.0)
|
||||||
|
.tint(theme::c_success())
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
ui.add(
|
||||||
|
egui::Image::new(egui::include_image!("../../assets/icons/fail-icon.svg"))
|
||||||
|
.max_width(18.0).max_height(18.0)
|
||||||
|
.tint(theme::c_error())
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
ui.add_space(10.0);
|
ui.add_space(10.0);
|
||||||
@@ -250,6 +338,10 @@ impl InstallerStep for UsersStep {
|
|||||||
|
|
||||||
ui.add_space(14.0);
|
ui.add_space(14.0);
|
||||||
|
|
||||||
|
// ─── Otomatik Oturum Açma ───────────────────────────────────────
|
||||||
|
form_checkbox(ui, &mut self.autologin, &t!("display_manager_autologin"));
|
||||||
|
ui.add_space(8.0);
|
||||||
|
|
||||||
// ─── Yönetici (root) Ayarları ─────────────────────────────────────
|
// ─── Yönetici (root) Ayarları ─────────────────────────────────────
|
||||||
form_checkbox(ui, &mut self.use_same_for_admin, &t!("use_same_for_admin"));
|
form_checkbox(ui, &mut self.use_same_for_admin, &t!("use_same_for_admin"));
|
||||||
|
|
||||||
@@ -281,16 +373,33 @@ impl InstallerStep for UsersStep {
|
|||||||
|
|
||||||
// DÜZELTME: 3. parametre olarak show_root_password_confirm eklendi
|
// DÜZELTME: 3. parametre olarak show_root_password_confirm eklendi
|
||||||
let show_root_pwd_conf = &mut self.show_root_password_confirm;
|
let show_root_pwd_conf = &mut self.show_root_password_confirm;
|
||||||
form_input_password(ui, &mut state.root_password_confirm, show_root_pwd_conf);
|
ui.horizontal(|ui| {
|
||||||
|
form_input_password(ui, &mut state.root_password_confirm, show_root_pwd_conf);
|
||||||
|
if !state.root_password_confirm.is_empty() {
|
||||||
|
if state.root_password == state.root_password_confirm {
|
||||||
|
ui.add(
|
||||||
|
egui::Image::new(egui::include_image!("../../assets/icons/check.svg"))
|
||||||
|
.max_width(18.0).max_height(18.0)
|
||||||
|
.tint(theme::c_success())
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
ui.add(
|
||||||
|
egui::Image::new(egui::include_image!("../../assets/icons/fail-icon.svg"))
|
||||||
|
.max_width(18.0).max_height(18.0)
|
||||||
|
.tint(theme::c_error())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
if state.root_password == state.root_password_confirm {
|
if !state.root_password_confirm.is_empty() && state.root_password != state.root_password_confirm {
|
||||||
ui.colored_label(theme::c_success(), "✅");
|
|
||||||
} else {
|
|
||||||
ui.colored_label(theme::c_error(), t!("passwords_no_match"));
|
ui.colored_label(theme::c_error(), t!("passwords_no_match"));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Autologin değerini state'e kaydet
|
||||||
|
state.autologin = self.autologin;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_complete(&self, state: &GlobalState) -> bool {
|
fn is_complete(&self, state: &GlobalState) -> bool {
|
||||||
|
|||||||
+42
-27
@@ -9,7 +9,7 @@ fn lerp_color(a: Color32, b: Color32, t: f32) -> Color32 {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn gradient_button(ui: &mut egui::Ui, text: &str, top_color: Color32, bottom_color: Color32, text_color: Color32, hover_top: Color32, hover_bottom: Color32, border_color: Color32) -> egui::Response {
|
fn gradient_button(ui: &mut egui::Ui, text: &str, top_color: Color32, bottom_color: Color32, text_color: Color32, hover_top: Color32, hover_bottom: Color32, border_color: Color32, enabled: bool) -> egui::Response {
|
||||||
let rounding = Rounding::same(6.0);
|
let rounding = Rounding::same(6.0);
|
||||||
let min_size = Vec2::new(100.0, 32.0);
|
let min_size = Vec2::new(100.0, 32.0);
|
||||||
let padding = Vec2::new(12.0, 6.0);
|
let padding = Vec2::new(12.0, 6.0);
|
||||||
@@ -27,13 +27,26 @@ fn gradient_button(ui: &mut egui::Ui, text: &str, top_color: Color32, bottom_col
|
|||||||
min_size.y.max(galley.size().y + 2.0 * padding.y),
|
min_size.y.max(galley.size().y + 2.0 * padding.y),
|
||||||
);
|
);
|
||||||
|
|
||||||
let (rect, response) = ui.allocate_exact_size(size, egui::Sense::click());
|
let sense = if enabled { egui::Sense::click() } else { egui::Sense::hover() };
|
||||||
|
let (rect, response) = ui.allocate_exact_size(size, sense);
|
||||||
|
|
||||||
|
let (actual_top, actual_bottom, actual_text, actual_border) = if enabled {
|
||||||
|
let hover_gradient = if response.hovered() {
|
||||||
|
(hover_top, hover_bottom)
|
||||||
|
} else {
|
||||||
|
(top_color, bottom_color)
|
||||||
|
};
|
||||||
|
(hover_gradient.0, hover_gradient.1, text_color, border_color)
|
||||||
|
} else {
|
||||||
|
let disabled = lerp_color(top_color, Color32::from_rgb(80, 80, 80), 0.5);
|
||||||
|
(disabled, disabled, Color32::from_gray(160), Color32::from_gray(100))
|
||||||
|
};
|
||||||
|
|
||||||
let n_strips = 16;
|
let n_strips = 16;
|
||||||
for i in 0..n_strips {
|
for i in 0..n_strips {
|
||||||
let t = i as f32 / n_strips as f32;
|
let t = i as f32 / n_strips as f32;
|
||||||
let t2 = (i + 1) as f32 / n_strips as f32;
|
let t2 = (i + 1) as f32 / n_strips as f32;
|
||||||
let c = lerp_color(top_color, bottom_color, (t + t2) * 0.5);
|
let c = lerp_color(actual_top, actual_bottom, (t + t2) * 0.5);
|
||||||
let y0 = rect.top() + rect.height() * t;
|
let y0 = rect.top() + rect.height() * t;
|
||||||
let y1 = rect.top() + rect.height() * t2;
|
let y1 = rect.top() + rect.height() * t2;
|
||||||
let strip_rect = egui::Rect::from_min_max(
|
let strip_rect = egui::Rect::from_min_max(
|
||||||
@@ -43,27 +56,12 @@ fn gradient_button(ui: &mut egui::Ui, text: &str, top_color: Color32, bottom_col
|
|||||||
ui.painter().rect(strip_rect, Rounding::ZERO, c, Stroke::NONE);
|
ui.painter().rect(strip_rect, Rounding::ZERO, c, Stroke::NONE);
|
||||||
}
|
}
|
||||||
|
|
||||||
if response.hovered() {
|
ui.painter().rect(rect, rounding, Color32::TRANSPARENT, Stroke::new(1.0, actual_border));
|
||||||
for i in 0..n_strips {
|
|
||||||
let t = i as f32 / n_strips as f32;
|
|
||||||
let t2 = (i + 1) as f32 / n_strips as f32;
|
|
||||||
let c = lerp_color(hover_top, hover_bottom, (t + t2) * 0.5);
|
|
||||||
let y0 = rect.top() + rect.height() * t;
|
|
||||||
let y1 = rect.top() + rect.height() * t2;
|
|
||||||
let strip_rect = egui::Rect::from_min_max(
|
|
||||||
egui::pos2(rect.left(), y0),
|
|
||||||
egui::pos2(rect.right(), y1),
|
|
||||||
);
|
|
||||||
ui.painter().rect(strip_rect, Rounding::ZERO, c, Stroke::NONE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ui.painter().rect(rect, rounding, Color32::TRANSPARENT, Stroke::new(1.0, border_color));
|
|
||||||
|
|
||||||
ui.painter().galley(
|
ui.painter().galley(
|
||||||
egui::pos2(rect.center().x - galley.size().x / 2.0, rect.center().y - galley.size().y / 2.0),
|
egui::pos2(rect.center().x - galley.size().x / 2.0, rect.center().y - galley.size().y / 2.0),
|
||||||
galley,
|
galley,
|
||||||
text_color,
|
actual_text,
|
||||||
);
|
);
|
||||||
|
|
||||||
if let Some(cursor) = ui.visuals().interact_cursor {
|
if let Some(cursor) = ui.visuals().interact_cursor {
|
||||||
@@ -76,34 +74,48 @@ fn gradient_button(ui: &mut egui::Ui, text: &str, top_color: Color32, bottom_col
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn primary_button(ui: &mut egui::Ui, text: &str) -> egui::Response {
|
pub fn primary_button(ui: &mut egui::Ui, text: &str) -> egui::Response {
|
||||||
|
primary_button_enabled(ui, text, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn primary_button_enabled(ui: &mut egui::Ui, text: &str, enabled: bool) -> egui::Response {
|
||||||
gradient_button(ui, text,
|
gradient_button(ui, text,
|
||||||
Color32::from_rgb(50, 130, 255),
|
Color32::from_rgb(50, 130, 255),
|
||||||
Color32::from_rgb(13, 110, 253),
|
Color32::from_rgb(13, 110, 253),
|
||||||
Color32::WHITE,
|
Color32::WHITE,
|
||||||
Color32::from_rgb(70, 150, 255),
|
Color32::from_rgb(70, 150, 255),
|
||||||
Color32::from_rgb(30, 130, 255),
|
Color32::from_rgb(30, 130, 255),
|
||||||
Color32::from_rgb(13, 110, 253))
|
Color32::from_rgb(13, 110, 253),
|
||||||
|
enabled)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn secondary_button(ui: &mut egui::Ui, text: &str) -> egui::Response {
|
pub fn secondary_button(ui: &mut egui::Ui, text: &str) -> egui::Response {
|
||||||
|
secondary_button_enabled(ui, text, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn secondary_button_enabled(ui: &mut egui::Ui, text: &str, enabled: bool) -> egui::Response {
|
||||||
gradient_button(ui, text,
|
gradient_button(ui, text,
|
||||||
Color32::from_rgb(130, 140, 150), // top
|
Color32::from_rgb(130, 140, 150), // top
|
||||||
Color32::from_rgb(100, 110, 120), // bottom
|
Color32::from_rgb(100, 110, 120), // bottom
|
||||||
Color32::from_rgb(255, 255, 255), // text color32
|
Color32::from_rgb(255, 255, 255), // text color32
|
||||||
Color32::from_rgb(150, 160, 170), // hover top
|
Color32::from_rgb(150, 160, 170), // hover top
|
||||||
Color32::from_rgb(120, 130, 140), // hover bottom
|
Color32::from_rgb(120, 130, 140), // hover bottom
|
||||||
Color32::from_rgb(100, 110, 120) // border
|
Color32::from_rgb(100, 110, 120), // border
|
||||||
)
|
enabled)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn danger_button(ui: &mut egui::Ui, text: &str) -> egui::Response {
|
pub fn danger_button(ui: &mut egui::Ui, text: &str) -> egui::Response {
|
||||||
|
danger_button_enabled(ui, text, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn danger_button_enabled(ui: &mut egui::Ui, text: &str, enabled: bool) -> egui::Response {
|
||||||
gradient_button(ui, text,
|
gradient_button(ui, text,
|
||||||
Color32::from_rgb(240, 83, 99),
|
Color32::from_rgb(240, 83, 99),
|
||||||
Color32::from_rgb(200, 40, 60),
|
Color32::from_rgb(200, 40, 60),
|
||||||
Color32::WHITE,
|
Color32::WHITE,
|
||||||
Color32::from_rgb(255, 100, 120),
|
Color32::from_rgb(255, 100, 120),
|
||||||
Color32::from_rgb(220, 60, 80),
|
Color32::from_rgb(220, 60, 80),
|
||||||
Color32::from_rgb(200, 40, 60))
|
Color32::from_rgb(200, 40, 60),
|
||||||
|
enabled)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
@@ -114,7 +126,8 @@ pub fn white_grd_btn(ui: &mut egui::Ui, text: &str) -> egui::Response {
|
|||||||
Color32::from_rgb(102, 102, 102),
|
Color32::from_rgb(102, 102, 102),
|
||||||
Color32::from_rgb(246, 246, 246),
|
Color32::from_rgb(246, 246, 246),
|
||||||
Color32::from_rgb(255, 255, 255),
|
Color32::from_rgb(255, 255, 255),
|
||||||
Color32::from_rgb(177, 177, 177))
|
Color32::from_rgb(177, 177, 177),
|
||||||
|
true)
|
||||||
}
|
}
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub fn btn_dark_blue(ui: &mut egui::Ui, text: &str) -> egui::Response {
|
pub fn btn_dark_blue(ui: &mut egui::Ui, text: &str) -> egui::Response {
|
||||||
@@ -124,7 +137,8 @@ pub fn btn_dark_blue(ui: &mut egui::Ui, text: &str) -> egui::Response {
|
|||||||
Color32::WHITE,
|
Color32::WHITE,
|
||||||
Color32::from_rgb(70, 140, 207),
|
Color32::from_rgb(70, 140, 207),
|
||||||
Color32::from_rgb(99, 184, 238),
|
Color32::from_rgb(99, 184, 238),
|
||||||
Color32::from_rgb(56, 102, 163))
|
Color32::from_rgb(56, 102, 163),
|
||||||
|
true)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
@@ -135,5 +149,6 @@ pub fn light_gray_btn(ui: &mut egui::Ui, text: &str) -> egui::Response {
|
|||||||
Color32::from_rgb(102, 102, 102), // metin: #666666
|
Color32::from_rgb(102, 102, 102), // metin: #666666
|
||||||
Color32::from_rgb(233, 233, 233), // hover üst (ters)
|
Color32::from_rgb(233, 233, 233), // hover üst (ters)
|
||||||
Color32::from_rgb(249, 249, 249), // hover alt (ters)
|
Color32::from_rgb(249, 249, 249), // hover alt (ters)
|
||||||
Color32::from_rgb(227, 195, 227)) // border: #e3c7e3
|
Color32::from_rgb(227, 195, 227), // border: #e3c7e3
|
||||||
|
true)
|
||||||
}
|
}
|
||||||
|
|||||||
+19
-8
@@ -1,5 +1,5 @@
|
|||||||
use eframe::egui::{self, Rect, Vec2};
|
use eframe::egui::{self, Rect, Vec2};
|
||||||
use super::combobox_stil::{draw_gradient_bg, DROPDOWN_TEXT_COLOR};
|
use super::combobox_stil::draw_gradient_bg;
|
||||||
|
|
||||||
/// Stil uygulanmış ComboBox oluşturur (genişlik ayarı yapılmaz, Grid içinde kullanım için).
|
/// Stil uygulanmış ComboBox oluşturur (genişlik ayarı yapılmaz, Grid içinde kullanım için).
|
||||||
pub fn combo_box(
|
pub fn combo_box(
|
||||||
@@ -48,13 +48,15 @@ pub fn selectable_value<T: PartialEq>(
|
|||||||
|
|
||||||
draw_gradient_bg(ui, rect);
|
draw_gradient_bg(ui, rect);
|
||||||
|
|
||||||
ui.put(rect, |ui: &mut egui::Ui| {
|
let text_color = combobox_stil_text_color(ui);
|
||||||
ui.horizontal(|ui| {
|
let painter = ui.painter();
|
||||||
ui.add_space(6.0);
|
painter.text(
|
||||||
ui.colored_label(DROPDOWN_TEXT_COLOR, label);
|
egui::pos2(rect.left() + 6.0, rect.center().y),
|
||||||
})
|
egui::Align2::LEFT_CENTER,
|
||||||
.response
|
label,
|
||||||
});
|
egui::FontId::proportional(14.0),
|
||||||
|
text_color,
|
||||||
|
);
|
||||||
|
|
||||||
if response.clicked() {
|
if response.clicked() {
|
||||||
*current = value;
|
*current = value;
|
||||||
@@ -62,6 +64,15 @@ pub fn selectable_value<T: PartialEq>(
|
|||||||
response.on_hover_cursor(egui::CursorIcon::PointingHand)
|
response.on_hover_cursor(egui::CursorIcon::PointingHand)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn combobox_stil_text_color(ui: &egui::Ui) -> egui::Color32 {
|
||||||
|
let bg = ui.visuals().panel_fill;
|
||||||
|
if bg.r() < 128 && bg.g() < 128 && bg.b() < 128 {
|
||||||
|
egui::Color32::from_rgb(230, 230, 240)
|
||||||
|
} else {
|
||||||
|
egui::Color32::from_rgb(50, 50, 50)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// ComboBox dropdown öğesi — özel içerik çizmek için (ör. bayrak + metin).
|
/// ComboBox dropdown öğesi — özel içerik çizmek için (ör. bayrak + metin).
|
||||||
/// `add_contents` callback'i verilen `Rect` içine içerik ekler.
|
/// `add_contents` callback'i verilen `Rect` içine içerik ekler.
|
||||||
pub fn selectable_value_custom<R>(
|
pub fn selectable_value_custom<R>(
|
||||||
|
|||||||
+35
-32
@@ -1,64 +1,67 @@
|
|||||||
use eframe::egui::{self, Color32, Rect, Stroke};
|
use eframe::egui::{self, Color32, Rect, Stroke};
|
||||||
use egui::epaint::Mesh;
|
use egui::epaint::Mesh;
|
||||||
|
|
||||||
// ─── ComboBox Popup Renk Sabitleri ──────────────────────────
|
|
||||||
pub const WEAK_BG_INACTIVE: Color32 = Color32::from_rgb(224, 224, 224);
|
|
||||||
pub const WEAK_BG_HOVERED: Color32 = Color32::from_rgb(200, 200, 200);
|
|
||||||
pub const WEAK_BG_ACTIVE: Color32 = Color32::from_rgb(180, 180, 180);
|
|
||||||
|
|
||||||
// ─── Gradient Dropdown Öğesi Renkleri ───────────────────────
|
|
||||||
pub const GRADIENT_TOP: Color32 = Color32::from_rgb(249, 249, 249);
|
|
||||||
pub const GRADIENT_BOT: Color32 = Color32::from_rgb(233, 233, 233);
|
|
||||||
|
|
||||||
// ─── ComboBox Metin Renkleri ────────────────────────────────
|
|
||||||
/// Seçili metin rengi (ComboBox kapalıyken widget üzerinde görünen metin)
|
|
||||||
pub const COMBO_TEXT_COLOR: Color32 = Color32::from_rgb(50, 50, 50);
|
|
||||||
/// Açılır listedeki öğelerin metin rengi
|
|
||||||
pub const DROPDOWN_TEXT_COLOR: Color32 = Color32::from_rgb(50, 50, 50);
|
|
||||||
|
|
||||||
// ─── ComboBox Kenarlık (border) Renkleri ────────────────────
|
|
||||||
/// Üzerine gelindiğinde / tıklanınca kenarlık rengi
|
|
||||||
pub const COMBO_BORDER_ACCENT: Color32 = Color32::from_rgb(155, 155, 155);
|
|
||||||
|
|
||||||
/// Verilen `Visuals` yapısına ComboBox stillerini uygular.
|
/// Verilen `Visuals` yapısına ComboBox stillerini uygular.
|
||||||
pub fn apply_visuals(v: &mut egui::Visuals) {
|
pub fn apply_visuals(v: &mut egui::Visuals) {
|
||||||
v.widgets.inactive.weak_bg_fill = WEAK_BG_INACTIVE;
|
v.widgets.inactive.weak_bg_fill = Color32::from_rgb(224, 224, 224);
|
||||||
v.widgets.hovered.weak_bg_fill = WEAK_BG_HOVERED;
|
v.widgets.hovered.weak_bg_fill = Color32::from_rgb(200, 200, 200);
|
||||||
v.widgets.active.weak_bg_fill = WEAK_BG_ACTIVE;
|
v.widgets.active.weak_bg_fill = Color32::from_rgb(180, 180, 180);
|
||||||
|
|
||||||
v.widgets.inactive.fg_stroke = Stroke::new(1.0, COMBO_TEXT_COLOR);
|
v.widgets.inactive.fg_stroke = Stroke::new(1.0, Color32::from_rgb(50, 50, 50));
|
||||||
v.widgets.hovered.fg_stroke = Stroke::new(1.5, Color32::WHITE);
|
v.widgets.hovered.fg_stroke = Stroke::new(1.5, Color32::WHITE);
|
||||||
v.widgets.active.fg_stroke = Stroke::new(2.0, Color32::WHITE);
|
v.widgets.active.fg_stroke = Stroke::new(2.0, Color32::WHITE);
|
||||||
|
|
||||||
// Kenarlıklar
|
v.widgets.inactive.bg_stroke = Stroke::new(1.0, Color32::from_rgb(155, 155, 155));
|
||||||
v.widgets.inactive.bg_stroke = Stroke::new(1.0, COMBO_BORDER_ACCENT);
|
v.widgets.hovered.bg_stroke = Stroke::new(1.0, Color32::from_rgb(155, 155, 155));
|
||||||
v.widgets.hovered.bg_stroke = Stroke::new(1.0, COMBO_BORDER_ACCENT);
|
v.widgets.active.bg_stroke = Stroke::new(1.0, Color32::from_rgb(155, 155, 155));
|
||||||
v.widgets.active.bg_stroke = Stroke::new(1.0, COMBO_BORDER_ACCENT);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Açık gri gradyan mesh arkaplanı çizer (dropdown öğeleri için).
|
fn is_dark_ui(ui: &egui::Ui) -> bool {
|
||||||
|
let bg = ui.visuals().panel_fill;
|
||||||
|
bg.r() < 128 && bg.g() < 128 && bg.b() < 128
|
||||||
|
}
|
||||||
|
|
||||||
|
fn gradient_top(ui: &egui::Ui) -> Color32 {
|
||||||
|
if is_dark_ui(ui) {
|
||||||
|
Color32::from_rgb(40, 40, 45)
|
||||||
|
} else {
|
||||||
|
Color32::from_rgb(249, 249, 249)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn gradient_bot(ui: &egui::Ui) -> Color32 {
|
||||||
|
if is_dark_ui(ui) {
|
||||||
|
Color32::from_rgb(30, 30, 35)
|
||||||
|
} else {
|
||||||
|
Color32::from_rgb(233, 233, 233)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Açık/koyu temaya uygun gradyan mesh arkaplanı çizer (dropdown öğeleri için).
|
||||||
pub fn draw_gradient_bg(ui: &mut egui::Ui, rect: Rect) {
|
pub fn draw_gradient_bg(ui: &mut egui::Ui, rect: Rect) {
|
||||||
|
let top = gradient_top(ui);
|
||||||
|
let bot = gradient_bot(ui);
|
||||||
let mut mesh = Mesh::default();
|
let mut mesh = Mesh::default();
|
||||||
let idx = mesh.vertices.len() as u32;
|
let idx = mesh.vertices.len() as u32;
|
||||||
mesh.vertices.push(egui::epaint::Vertex {
|
mesh.vertices.push(egui::epaint::Vertex {
|
||||||
pos: egui::pos2(rect.left(), rect.top()),
|
pos: egui::pos2(rect.left(), rect.top()),
|
||||||
uv: egui::pos2(0.0, 0.0),
|
uv: egui::pos2(0.0, 0.0),
|
||||||
color: GRADIENT_TOP,
|
color: top,
|
||||||
});
|
});
|
||||||
mesh.vertices.push(egui::epaint::Vertex {
|
mesh.vertices.push(egui::epaint::Vertex {
|
||||||
pos: egui::pos2(rect.right(), rect.top()),
|
pos: egui::pos2(rect.right(), rect.top()),
|
||||||
uv: egui::pos2(0.0, 0.0),
|
uv: egui::pos2(0.0, 0.0),
|
||||||
color: GRADIENT_TOP,
|
color: top,
|
||||||
});
|
});
|
||||||
mesh.vertices.push(egui::epaint::Vertex {
|
mesh.vertices.push(egui::epaint::Vertex {
|
||||||
pos: egui::pos2(rect.right(), rect.bottom()),
|
pos: egui::pos2(rect.right(), rect.bottom()),
|
||||||
uv: egui::pos2(0.0, 0.0),
|
uv: egui::pos2(0.0, 0.0),
|
||||||
color: GRADIENT_BOT,
|
color: bot,
|
||||||
});
|
});
|
||||||
mesh.vertices.push(egui::epaint::Vertex {
|
mesh.vertices.push(egui::epaint::Vertex {
|
||||||
pos: egui::pos2(rect.left(), rect.bottom()),
|
pos: egui::pos2(rect.left(), rect.bottom()),
|
||||||
uv: egui::pos2(0.0, 0.0),
|
uv: egui::pos2(0.0, 0.0),
|
||||||
color: GRADIENT_BOT,
|
color: bot,
|
||||||
});
|
});
|
||||||
mesh.indices.extend_from_slice(&[idx, idx + 1, idx + 2, idx + 2, idx + 3, idx]);
|
mesh.indices.extend_from_slice(&[idx, idx + 1, idx + 2, idx + 2, idx + 3, idx]);
|
||||||
ui.painter().add(egui::Shape::mesh(mesh));
|
ui.painter().add(egui::Shape::mesh(mesh));
|
||||||
|
|||||||
@@ -0,0 +1,302 @@
|
|||||||
|
use eframe::egui::{self, Rect, Rounding, Stroke, Vec2};
|
||||||
|
use xkbcommon::xkb;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct XkbKey {
|
||||||
|
pub x: f32, pub y: f32, pub w: f32, pub h: f32,
|
||||||
|
pub labels: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct XkbLayout {
|
||||||
|
pub keys: Vec<XkbKey>,
|
||||||
|
pub width: f32, pub height: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Boş bir klavye düzeni (layout derlenemediğinde gösterilir).
|
||||||
|
pub fn empty_layout() -> XkbLayout {
|
||||||
|
XkbLayout { keys: vec![], width: 1.0, height: 1.0 }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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).
|
||||||
|
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 tamamen kaldırıldı) ──
|
||||||
|
|
||||||
|
|
||||||
|
// ── 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)),
|
||||||
|
"AE03" => Some((3.0, r(1), 1.0, 1.0)),
|
||||||
|
"AE04" => Some((4.0, r(1), 1.0, 1.0)),
|
||||||
|
"AE05" => Some((5.0, r(1), 1.0, 1.0)),
|
||||||
|
"AE06" => Some((6.0, r(1), 1.0, 1.0)),
|
||||||
|
"AE07" => Some((7.0, r(1), 1.0, 1.0)),
|
||||||
|
"AE08" => Some((8.0, r(1), 1.0, 1.0)),
|
||||||
|
"AE09" => Some((9.0, r(1), 1.0, 1.0)),
|
||||||
|
"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)),
|
||||||
|
|
||||||
|
// ── 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)),
|
||||||
|
"AD03" => Some((3.5, r(2), 1.0, 1.0)),
|
||||||
|
"AD04" => Some((4.5, r(2), 1.0, 1.0)),
|
||||||
|
"AD05" => Some((5.5, r(2), 1.0, 1.0)),
|
||||||
|
"AD06" => Some((6.5, r(2), 1.0, 1.0)),
|
||||||
|
"AD07" => Some((7.5, r(2), 1.0, 1.0)),
|
||||||
|
"AD08" => Some((8.5, r(2), 1.0, 1.0)),
|
||||||
|
"AD09" => Some((9.5, r(2), 1.0, 1.0)),
|
||||||
|
"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((13.5, r(2), 1.0, 1.0)), // ISO: solundaki tuş (\|)
|
||||||
|
"RTRN" => Some((14.5, r(2), 1.25, 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)),
|
||||||
|
"AC03" => Some((3.75, r(3), 1.0, 1.0)),
|
||||||
|
"AC04" => Some((4.75, r(3), 1.0, 1.0)),
|
||||||
|
"AC05" => Some((5.75, r(3), 1.0, 1.0)),
|
||||||
|
"AC06" => Some((6.75, r(3), 1.0, 1.0)),
|
||||||
|
"AC07" => Some((7.75, r(3), 1.0, 1.0)),
|
||||||
|
"AC08" => Some((8.75, r(3), 1.0, 1.0)),
|
||||||
|
"AC09" => Some((9.75, r(3), 1.0, 1.0)),
|
||||||
|
"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)),
|
||||||
|
|
||||||
|
// ── 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 (< > |)
|
||||||
|
"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)),
|
||||||
|
"AB04" => Some((5.25, r(4), 1.0, 1.0)),
|
||||||
|
"AB05" => Some((6.25, r(4), 1.0, 1.0)),
|
||||||
|
"AB06" => Some((7.25, r(4), 1.0, 1.0)),
|
||||||
|
"AB07" => Some((8.25, r(4), 1.0, 1.0)),
|
||||||
|
"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)),
|
||||||
|
"RTSH" => Some((12.25,r(4), 3.75, 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)),
|
||||||
|
"COMP" => Some((11.75,r(5), 1.25, 1.0)),
|
||||||
|
"RCTL" => Some((13.0, r(5), 1.25, 1.0)),
|
||||||
|
|
||||||
|
// ── (nav cluster tamamen kaldırıldı) ──
|
||||||
|
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn keysym_to_label(sym: xkb::Keysym) -> String {
|
||||||
|
let utf = xkb::keysym_to_utf8(sym);
|
||||||
|
if utf.chars().any(|c| !c.is_control()) {
|
||||||
|
let trimmed = utf.trim();
|
||||||
|
if !trimmed.is_empty() {
|
||||||
|
return trimmed.to_string();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let name = xkb::keysym_get_name(sym);
|
||||||
|
if name == "VoidSymbol" || name == "NoSymbol" {
|
||||||
|
return String::new();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Yaygın XKB sembol adlarını kısa etiketlere çevir
|
||||||
|
match name.as_str() {
|
||||||
|
"BackSpace" => "Bksp".into(),
|
||||||
|
"Tab" => "Tab".into(),
|
||||||
|
"Return" => "Enter".into(),
|
||||||
|
"Caps_Lock" => "Caps".into(),
|
||||||
|
"Shift_L" => "Shift".into(),
|
||||||
|
"Shift_R" => "Shift".into(),
|
||||||
|
"Control_L" => "Ctrl".into(),
|
||||||
|
"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(),
|
||||||
|
"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<XkbLayout> {
|
||||||
|
let mut context = xkb::Context::new(xkb::CONTEXT_NO_FLAGS);
|
||||||
|
context.set_log_level(xkb::LogLevel::Critical);
|
||||||
|
let model = if std::path::Path::new("/usr/share/X11/xkb/symbols/").exists() {
|
||||||
|
"pc105"
|
||||||
|
} else {
|
||||||
|
"pc105"
|
||||||
|
};
|
||||||
|
|
||||||
|
let keymap = xkb::Keymap::new_from_names::<str>(
|
||||||
|
&context,
|
||||||
|
"", // rules
|
||||||
|
model, // model
|
||||||
|
layout, // layout
|
||||||
|
if variant.is_empty() { "" } else { variant }, // variant
|
||||||
|
None::<String>, // options
|
||||||
|
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 moved_comma = false;
|
||||||
|
|
||||||
|
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 };
|
||||||
|
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::<Vec<_>>()
|
||||||
|
.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 mut labels: Vec<String> = {
|
||||||
|
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() }]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// , tuşu BKSL'deyse AC12'ye taşı (Türkçe Q düzeni için)
|
||||||
|
if name == "BKSL" && labels.contains(&",".to_string()) {
|
||||||
|
moved_comma = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if name == "AC12" {
|
||||||
|
labels = vec![",".to_string()];
|
||||||
|
}
|
||||||
|
|
||||||
|
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; }
|
||||||
|
}
|
||||||
|
|
||||||
|
// AC12 keymap'te yoksa manuel ekle
|
||||||
|
if moved_comma && !keys.iter().any(|k| (k.x - 12.75).abs() < 0.1 && (k.y - 3.0).abs() < 0.1) {
|
||||||
|
keys.push(XkbKey { x: 12.75, y: 3.0, w: 1.0, h: 1.0, labels: vec![",".to_string()] });
|
||||||
|
}
|
||||||
|
|
||||||
|
// normalize x,y — tüm tuşlar (0,0) bazlı olsun
|
||||||
|
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 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 painter = ui.painter_at(resp.rect);
|
||||||
|
let origin = resp.rect.min;
|
||||||
|
|
||||||
|
let visuals = ui.visuals();
|
||||||
|
let key_fill = visuals.widgets.inactive.bg_fill;
|
||||||
|
let key_stroke = visuals.widgets.noninteractive.bg_stroke;
|
||||||
|
let text_color = egui::Color32::BLACK;
|
||||||
|
let bg = visuals.window_fill();
|
||||||
|
|
||||||
|
painter.rect_filled(resp.rect, Rounding::same(6.0), bg);
|
||||||
|
|
||||||
|
let font_size = (unit * 0.28).max(6.0);
|
||||||
|
let small_size = (unit * 0.22).max(5.0);
|
||||||
|
|
||||||
|
let sp = unit * spacing;
|
||||||
|
|
||||||
|
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 kw <= 0.0 || kh <= 0.0 { continue; }
|
||||||
|
|
||||||
|
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));
|
||||||
|
|
||||||
|
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.5, font_size),
|
||||||
|
1 => (kx + kw * 0.5, ky + kh * 0.75, 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,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+112
-176
@@ -1,4 +1,4 @@
|
|||||||
use eframe::egui::{self, Rect, Rounding, Stroke, Vec2};
|
use eframe::egui::{self, Vec2};
|
||||||
use xkbcommon::xkb;
|
use xkbcommon::xkb;
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
@@ -10,36 +10,16 @@ pub struct XkbKey {
|
|||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct XkbLayout {
|
pub struct XkbLayout {
|
||||||
pub keys: Vec<XkbKey>,
|
pub keys: Vec<XkbKey>,
|
||||||
pub width: f32, pub height: f32,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// ISO‑105 tuşlu klavye için XKB adı → (x, y, w, h) birim cinsinden.
|
pub fn empty_layout() -> XkbLayout {
|
||||||
/// x,y ondalıklı olabilir (geniş tuşlar için).
|
XkbLayout { keys: vec![] }
|
||||||
|
}
|
||||||
|
|
||||||
fn key_position(name: &str) -> Option<(f32, f32, f32, f32)> {
|
fn key_position(name: &str) -> Option<(f32, f32, f32, f32)> {
|
||||||
// Satır y offset'leri (birim)
|
|
||||||
let r = |row| row as f32;
|
let r = |row| row as f32;
|
||||||
|
|
||||||
// Standart ISO‑105 düzeni. x değerleri kümülatif.
|
|
||||||
match name {
|
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)),
|
"TLDE" => Some((0.0, r(1), 1.0, 1.0)),
|
||||||
"AE01" => Some((1.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)),
|
"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)),
|
"AE10" => Some((10.0, r(1), 1.0, 1.0)),
|
||||||
"AE11" => Some((11.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)),
|
"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)),
|
"TAB" => Some((0.0, r(2), 1.5, 1.0)),
|
||||||
"AD01" => Some((1.5, r(2), 1.0, 1.0)),
|
"AD01" => Some((1.5, r(2), 1.0, 1.0)),
|
||||||
"AD02" => Some((2.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)),
|
"AD10" => Some((10.5, r(2), 1.0, 1.0)),
|
||||||
"AD11" => Some((11.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)),
|
"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)),
|
"CAPS" => Some((0.0, r(3), 1.75, 1.0)),
|
||||||
"AC01" => Some((1.75, r(3), 1.0, 1.0)),
|
"AC01" => Some((1.75, r(3), 1.0, 1.0)),
|
||||||
"AC02" => Some((2.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)),
|
"AC10" => Some((10.75,r(3), 1.0, 1.0)),
|
||||||
"AC11" => Some((11.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)),
|
"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)),
|
"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)),
|
"AB01" => Some((2.25, r(4), 1.0, 1.0)),
|
||||||
"AB02" => Some((3.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)),
|
"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)),
|
"AB08" => Some((9.25, r(4), 1.0, 1.0)),
|
||||||
"AB09" => Some((10.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)),
|
"AB10" => Some((11.25,r(4), 1.0, 1.0)),
|
||||||
"AB11" => Some((12.25,r(4), 1.0, 1.0)),
|
"RTSH" => Some((12.25,r(4), 3.0, 1.0)),
|
||||||
"RTSH" => Some((13.25,r(4), 2.75, 1.0)),
|
|
||||||
|
|
||||||
// ── Space bar row ─────────────
|
|
||||||
"LCTL" => Some((0.0, r(5), 1.25, 1.0)),
|
"LCTL" => Some((0.0, r(5), 1.25, 1.0)),
|
||||||
"LWIN" => Some((1.25, 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)),
|
"LALT" => Some((2.5, r(5), 1.5, 1.0)),
|
||||||
"SPCE" => Some((4.0, r(5), 6.25, 1.0)),
|
"SPCE" => Some((4.0, r(5), 6.25, 1.0)),
|
||||||
"RALT" | "LVL3" => Some((10.25,r(5), 1.5, 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((11.75, r(5), 1.25, 1.0)),
|
||||||
"COMP" => Some((13.0, r(5), 1.25, 1.0)),
|
"RCTL" => Some((13.0, r(5), 2.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)),
|
|
||||||
|
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
@@ -162,94 +92,76 @@ fn key_position(name: &str) -> Option<(f32, f32, f32, f32)> {
|
|||||||
|
|
||||||
fn keysym_to_label(sym: xkb::Keysym) -> String {
|
fn keysym_to_label(sym: xkb::Keysym) -> String {
|
||||||
let utf = xkb::keysym_to_utf8(sym);
|
let utf = xkb::keysym_to_utf8(sym);
|
||||||
if !utf.is_empty() { return utf; }
|
if utf.chars().any(|c| !c.is_control()) {
|
||||||
|
let trimmed = utf.trim();
|
||||||
let name = xkb::keysym_get_name(sym);
|
if !trimmed.is_empty() {
|
||||||
if name == "VoidSymbol" || name == "NoSymbol" {
|
return trimmed.to_uppercase();
|
||||||
return String::new();
|
}
|
||||||
}
|
}
|
||||||
|
let name = xkb::keysym_get_name(sym);
|
||||||
// Yaygın XKB sembol adlarını kısa etiketlere çevir
|
if name == "VoidSymbol" || name == "NoSymbol" { return String::new(); }
|
||||||
match name.as_str() {
|
match name.as_str() {
|
||||||
"BackSpace" => "⌫".into(),
|
"BackSpace" => "Bksp".into(),
|
||||||
"Tab" => "⇥".into(),
|
"Tab" => "Tab".into(),
|
||||||
"Return" => "↵".into(),
|
"Return" => "Enter".into(),
|
||||||
"Caps_Lock" => "⇪".into(),
|
"Caps_Lock" => "Caps".into(),
|
||||||
"Shift_L" => "⇧".into(),
|
"Shift_L" | "Shift_R" => "Shift".into(),
|
||||||
"Shift_R" => "⇧".into(),
|
"Control_L" | "Control_R" => "Ctrl".into(),
|
||||||
"Control_L" => "Ctrl".into(),
|
|
||||||
"Control_R" => "Ctrl".into(),
|
|
||||||
"Alt_L" => "Alt".into(),
|
"Alt_L" => "Alt".into(),
|
||||||
"Alt_R" => "AltGr".into(),
|
"Alt_R" | "ISO_Level3_Shift" => "AltGr".into(),
|
||||||
"ISO_Level3_Shift" => "AltGr".into(),
|
"Super_L" | "Super_R" => "Win".into(),
|
||||||
"Super_L" => "Win".into(),
|
|
||||||
"Super_R" => "Win".into(),
|
|
||||||
"Menu" => "Menü".into(),
|
"Menu" => "Menü".into(),
|
||||||
"Escape" => "Esc".into(),
|
"Escape" => "Esc".into(),
|
||||||
"Delete" => "Del".into(),
|
|
||||||
"Insert" => "Ins".into(),
|
|
||||||
"Prior" => "PgUp".into(),
|
|
||||||
"Next" => "PgDn".into(),
|
|
||||||
"space" => "".into(),
|
"space" => "".into(),
|
||||||
"Multi_key" => "Compose".into(),
|
|
||||||
_ if name.starts_with("XF86") => String::new(),
|
|
||||||
_ => name,
|
_ => name,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// XKB keymap'inden klavye düzenini oluşturur.
|
|
||||||
pub fn compile_layout(layout: &str, variant: &str) -> Option<XkbLayout> {
|
pub fn compile_layout(layout: &str, variant: &str) -> Option<XkbLayout> {
|
||||||
let context = xkb::Context::new(xkb::CONTEXT_NO_FLAGS);
|
let mut context = xkb::Context::new(xkb::CONTEXT_NO_FLAGS);
|
||||||
let model = if std::path::Path::new("/usr/share/X11/xkb/symbols/").exists() {
|
context.set_log_level(xkb::LogLevel::Critical);
|
||||||
"pc105"
|
|
||||||
} else {
|
|
||||||
"pc105"
|
|
||||||
};
|
|
||||||
|
|
||||||
let keymap = xkb::Keymap::new_from_names::<str>(
|
let keymap = xkb::Keymap::new_from_names::<str>(
|
||||||
&context,
|
&context, "", "pc105", layout,
|
||||||
"", // rules
|
if variant.is_empty() { "" } else { variant },
|
||||||
model, // model
|
None::<String>, xkb::COMPILE_NO_FLAGS,
|
||||||
layout, // layout
|
|
||||||
if variant.is_empty() { "" } else { variant }, // variant
|
|
||||||
None::<String>, // options
|
|
||||||
xkb::COMPILE_NO_FLAGS,
|
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let min = keymap.min_keycode();
|
let min = keymap.min_keycode();
|
||||||
let max = keymap.max_keycode();
|
let max = keymap.max_keycode();
|
||||||
let mut keys = Vec::new();
|
let mut keys = Vec::new();
|
||||||
let mut min_x = f32::MAX;
|
|
||||||
let mut min_y = 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) {
|
for code_u32 in (min.raw() as u32)..=(max.raw() as u32) {
|
||||||
let code = xkb::Keycode::new(code_u32);
|
let code = xkb::Keycode::new(code_u32);
|
||||||
let Some(name) = keymap.key_get_name(code) else { continue };
|
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::<Vec<_>>().join("");
|
||||||
|
if !label.is_empty() { enter_label = label; }
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
let Some((x, y, w, h)) = key_position(name) else { 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();
|
let mut raw_labels = Vec::new();
|
||||||
for level in 0..4 {
|
for level in 0..4 {
|
||||||
let syms = keymap.key_get_syms_by_level(code, 0, level);
|
let syms = keymap.key_get_syms_by_level(code, 0, level);
|
||||||
let label = syms.iter()
|
let label = syms.iter().map(|&s| keysym_to_label(s)).collect::<Vec<_>>().join("");
|
||||||
.map(|&s| keysym_to_label(s))
|
|
||||||
.collect::<Vec<_>>()
|
|
||||||
.join("");
|
|
||||||
if !label.is_empty() && !raw_labels.contains(&label) {
|
if !label.is_empty() && !raw_labels.contains(&label) {
|
||||||
raw_labels.push(label);
|
raw_labels.push(label);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Her tuşta sadece 1 karakter göster (büyük harf tercihli)
|
|
||||||
let labels: Vec<String> = {
|
let labels: Vec<String> = {
|
||||||
let single: Vec<_> = raw_labels.iter().filter(|l| l.chars().count() == 1).collect();
|
let single: Vec<_> = raw_labels.iter().filter(|l| l.chars().count() == 1).collect();
|
||||||
if single.is_empty() {
|
if single.is_empty() {
|
||||||
// Hiç tek karakter yoksa ilk label'ı göster (F1, Esc, …)
|
|
||||||
raw_labels.into_iter().take(1).collect()
|
raw_labels.into_iter().take(1).collect()
|
||||||
} else {
|
} 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()));
|
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() }]
|
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<XkbLayout> {
|
|||||||
|
|
||||||
keys.push(XkbKey { x, y, w, h, labels });
|
keys.push(XkbKey { x, y, w, h, labels });
|
||||||
|
|
||||||
if x < min_x { min_x = x; }
|
|
||||||
if y < min_y { min_y = y; }
|
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 {
|
for key in &mut keys {
|
||||||
key.x -= min_x;
|
|
||||||
key.y -= min_y;
|
key.y -= min_y;
|
||||||
}
|
}
|
||||||
|
|
||||||
Some(XkbLayout {
|
Some(XkbLayout {
|
||||||
keys,
|
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) {
|
pub fn draw_layout(ui: &mut egui::Ui, layout: &XkbLayout, max_size: Vec2) {
|
||||||
let margin = 6.0;
|
let margin = 6.0;
|
||||||
let spacing = 0.08;
|
|
||||||
let corner = 3.0;
|
|
||||||
|
|
||||||
let uw = (max_size.x - margin * 2.0) / layout.width;
|
let svg_size = Vec2::new(840.0, 300.0);
|
||||||
let uh = (max_size.y - margin * 2.0) / layout.height;
|
let img_scale = ((max_size.x - margin * 2.0) / svg_size.x)
|
||||||
let unit = uw.min(uh).max(12.0);
|
.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 (_id, resp) = ui.allocate_exact_size(total_size, egui::Sense::hover());
|
||||||
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 painter = ui.painter_at(resp.rect);
|
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 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 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 img_rect = egui::Rect::from_min_size(origin, draw_size);
|
||||||
let small_size = (unit * 0.22).max(5.0);
|
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 {
|
for key in &layout.keys {
|
||||||
let kx = origin.x + margin + key.x * unit + sp;
|
if let Some(lbl) = key.labels.first() {
|
||||||
let ky = origin.y + margin + key.y * unit + sp;
|
if lbl == "_RTRN_BOT" { continue; }
|
||||||
let kw = key.w * unit - sp * 2.0;
|
if key.y == 1.0 && (key.x - 13.5).abs() < 0.1 {
|
||||||
let kh = key.h * unit - sp * 2.0;
|
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));
|
let ex = origin.x + (svg_cx - svg_w / 2.0) * img_scale;
|
||||||
painter.rect(key_rect, Rounding::same(corner), key_fill, Stroke::new(key_stroke.width.max(0.5), key_stroke.color));
|
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() {
|
if let Some(lbl) = key.labels.first() {
|
||||||
let lbl = label.trim();
|
let trimmed = lbl.trim();
|
||||||
if lbl.is_empty() { continue; }
|
if trimmed.is_empty() { continue; }
|
||||||
let (lx, ly, sz) = match i {
|
|
||||||
0 => (kx + kw * 0.5, ky + kh * 0.3, font_size),
|
let text_pos = egui::pos2(ex + ew / 2.0, ey + eh / 2.0);
|
||||||
1 => (kx + kw * 0.5, ky + kh * 0.65, small_size),
|
|
||||||
_ => (kx + kw * 0.5, ky + kh * 0.9, small_size),
|
painter.text(text_pos, egui::Align2::CENTER_CENTER, trimmed, egui::FontId::proportional(font_size), text_color);
|
||||||
};
|
|
||||||
painter.text(
|
|
||||||
egui::pos2(lx, ly), egui::Align2::CENTER_CENTER,
|
|
||||||
lbl, egui::FontId::proportional(sz), 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,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+27
-17
@@ -15,6 +15,7 @@ use std::sync::atomic::{AtomicU32, Ordering};
|
|||||||
static BG_DARK: AtomicU32 = AtomicU32::new(0x121214);
|
static BG_DARK: AtomicU32 = AtomicU32::new(0x121214);
|
||||||
static BG_PANEL: AtomicU32 = AtomicU32::new(0x1A1A1E);
|
static BG_PANEL: AtomicU32 = AtomicU32::new(0x1A1A1E);
|
||||||
static BG_WIDGET: AtomicU32 = AtomicU32::new(0x242428);
|
static BG_WIDGET: AtomicU32 = AtomicU32::new(0x242428);
|
||||||
|
static BG_FOOTER: AtomicU32 = AtomicU32::new(0x121214);
|
||||||
|
|
||||||
|
|
||||||
static BORDER: AtomicU32 = AtomicU32::new(0x323238);
|
static BORDER: AtomicU32 = AtomicU32::new(0x323238);
|
||||||
@@ -26,6 +27,8 @@ static ERROR: AtomicU32 = AtomicU32::new(0xF44336);
|
|||||||
static SIDEBAR: AtomicU32 = AtomicU32::new(0x0B0B0D);
|
static SIDEBAR: AtomicU32 = AtomicU32::new(0x0B0B0D);
|
||||||
static ACCENT_COLOR: AtomicU32 = AtomicU32::new(0xd9005b); // Pisi Premium Vurgu (#d9005b)
|
static ACCENT_COLOR: AtomicU32 = AtomicU32::new(0xd9005b); // Pisi Premium Vurgu (#d9005b)
|
||||||
static SIDEBAR_TEXT: AtomicU32 = AtomicU32::new(0x000000);
|
static SIDEBAR_TEXT: AtomicU32 = AtomicU32::new(0x000000);
|
||||||
|
static KEYBOARD_BG: AtomicU32 = AtomicU32::new(0xFFFFFF);
|
||||||
|
static BG_ROOT: AtomicU32 = AtomicU32::new(0x1A1A1E);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -46,33 +49,40 @@ 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_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() -> 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_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_root_bg() -> Color32 { color_from_hex(BG_ROOT.load(Ordering::Relaxed)) }
|
||||||
|
pub fn c_keyboard_bg() -> Color32 { color_from_hex(KEYBOARD_BG.load(Ordering::Relaxed)) }
|
||||||
|
|
||||||
pub fn set_theme_mode(is_dark: bool) {
|
pub fn set_theme_mode(is_dark: bool) {
|
||||||
if is_dark {
|
if is_dark {
|
||||||
|
BG_FOOTER.store(0x121214, Ordering::Relaxed);
|
||||||
BG_DARK.store(0x121214, Ordering::Relaxed);
|
BG_DARK.store(0x121214, Ordering::Relaxed);
|
||||||
BG_PANEL.store(0x1A1A1E, Ordering::Relaxed);
|
BG_PANEL.store(0x1A1A1E, Ordering::Relaxed);
|
||||||
BG_WIDGET.store(0x242428, Ordering::Relaxed);
|
BG_WIDGET.store(0x242428, Ordering::Relaxed);
|
||||||
BORDER.store(0x323238, Ordering::Relaxed);
|
BORDER.store(0x323238, Ordering::Relaxed);
|
||||||
TEXT.store(0xF3F3F5, Ordering::Relaxed);
|
TEXT.store(0xF3F3F5, Ordering::Relaxed); // #F3F3F5
|
||||||
TEXT_DIM.store(0x9E9EAF, Ordering::Relaxed);
|
TEXT_DIM.store(0x9E9EAF, Ordering::Relaxed); // #9E9EAF
|
||||||
SIDEBAR.store(0x0B0B0D, Ordering::Relaxed);
|
SIDEBAR.store(0x0B0B0D, Ordering::Relaxed);
|
||||||
|
SIDEBAR_TEXT.store(0xffffff, Ordering::Relaxed);
|
||||||
|
KEYBOARD_BG.store(0x000000, Ordering::Relaxed);
|
||||||
|
BG_ROOT.store(0x1A1A1E, Ordering::Relaxed);
|
||||||
} else {
|
} else {
|
||||||
|
BG_FOOTER.store(0xf7f7f7, Ordering::Relaxed);
|
||||||
BG_DARK.store(0xf7f7f7, Ordering::Relaxed);
|
BG_DARK.store(0xf7f7f7, Ordering::Relaxed);
|
||||||
SIDEBAR.store(0x292f34, Ordering::Relaxed);
|
SIDEBAR.store(0x292f34, Ordering::Relaxed);
|
||||||
TEXT.store(0x000000, Ordering::Relaxed);
|
TEXT.store(0x000000, Ordering::Relaxed); // #000000
|
||||||
TEXT_DIM.store(0x000000, Ordering::Relaxed);
|
TEXT_DIM.store(0x000000, Ordering::Relaxed); // #000000
|
||||||
SIDEBAR_TEXT.store(0xffffff, Ordering::Relaxed);
|
SIDEBAR_TEXT.store(0xffffff, Ordering::Relaxed);
|
||||||
|
KEYBOARD_BG.store(0xffffff, Ordering::Relaxed);
|
||||||
|
BG_ROOT.store(0xf7f7f7, Ordering::Relaxed);
|
||||||
|
|
||||||
|
//BG_DARK.store(0xF0F0F5, Ordering::Relaxed);
|
||||||
// BG_DARK.store(0xF0F0F5, Ordering::Relaxed);
|
BG_PANEL.store(0xFFFFFF, Ordering::Relaxed);
|
||||||
// BG_PANEL.store(0xFFFFFF, Ordering::Relaxed);
|
BG_WIDGET.store(0xE8E8EE, Ordering::Relaxed);
|
||||||
// BG_WIDGET.store(0xE8E8EE, Ordering::Relaxed);
|
BORDER.store(0xD0D0DA, Ordering::Relaxed);
|
||||||
// BORDER.store(0xD0D0DA, Ordering::Relaxed);
|
//TEXT.store(0x20202A, Ordering::Relaxed); // #20202A
|
||||||
// TEXT.store(0x20202A, Ordering::Relaxed);
|
//TEXT_DIM.store(0x60606A, Ordering::Relaxed); // #60606A
|
||||||
// TEXT_DIM.store(0x60606A, Ordering::Relaxed);
|
//SIDEBAR.store(0xE4E4EA, Ordering::Relaxed);
|
||||||
// SIDEBAR.store(0xE4E4EA, Ordering::Relaxed);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -183,13 +193,13 @@ fn build_visuals(is_dark: bool) -> Visuals {
|
|||||||
v.widgets.noninteractive.bg_fill = c_bg_widget();
|
v.widgets.noninteractive.bg_fill = c_bg_widget();
|
||||||
|
|
||||||
// Pasif Hali: Widget arka planı (TextEdit, ComboBox giriş alanı)
|
// Pasif Hali: Widget arka planı (TextEdit, ComboBox giriş alanı)
|
||||||
v.widgets.inactive.bg_fill = Color32::from_rgb(255, 255, 255);
|
v.widgets.inactive.bg_fill = if is_dark { Color32::from_rgb(255, 255, 255) } else { Color32::from_rgb(245, 245, 245) };
|
||||||
|
|
||||||
// Üzerine Gelme Hali: Hafif açık
|
// Üzerine Gelme Hali: Hafif açık
|
||||||
v.widgets.hovered.bg_fill = Color32::from_rgb(55, 58, 68);
|
v.widgets.hovered.bg_fill = if is_dark { Color32::from_rgb(55, 58, 68) } else { Color32::from_rgb(235, 235, 235) };
|
||||||
|
|
||||||
// Tıklanma/Açık Kalma Hali: Daha açık
|
// Tıklanma/Açık Kalma Hali: Daha açık
|
||||||
v.widgets.active.bg_fill = Color32::WHITE;
|
v.widgets.active.bg_fill = if is_dark { Color32::WHITE } else { Color32::from_rgb(230, 230, 230) };
|
||||||
|
|
||||||
// ComboBox stilleri (combobox_stil.rs)
|
// ComboBox stilleri (combobox_stil.rs)
|
||||||
crate::ui::combobox_stil::apply_visuals(&mut v);
|
crate::ui::combobox_stil::apply_visuals(&mut v);
|
||||||
|
|||||||
Reference in New Issue
Block a user