Merge branch 'main' into 'main'

refactor: extract ComboBox styling into dedicated module, add location step

See merge request ayhanyalcinsoy/yali-rs!6
This commit is contained in:
2026-06-18 11:32:28 +00:00
23 changed files with 1239 additions and 525 deletions
+5 -1
View File
@@ -1,2 +1,6 @@
/target /target
Cargo.lock Cargo.lock
.idea/
*.swp
*.swo
.vscode/
+98
View File
@@ -0,0 +1,98 @@
# Changelog
## [4.0.0] — 2026-06-18
### Added
- `CHANGELOG.md`
- `src/ui/combobox.rs` — ComboBox yardımcıları (`show_combo_box`, `selectable_value`, `selectable_value_custom`, `draw_gradient_bg`)
- `src/ui/combobox_stil.rs` — ComboBox görsel sabitleri ve `apply_visuals()`
- `src/steps/location.rs` — Bölge/zaman dilimi seçimi adımı
- `src/steps/network.rs` — Ağ yapılandırma adımı
- `src/steps/rescue.rs` — Kurtarma modu
- `xkbcommon` bağımlılığı
- Klavye görseli: XKB keymap → egui çizim (`compile_layout`, `draw_layout`, `keysym_to_label`)
- ISO105 tuş pozisyon eşlemesi (function tuşları, numpad, navigasyon dahil)
- Her tuşta tek karakter gösterimi (büyük harf tercihli)
### Changed
- Klavye görseli: KLE JSON indirme/parse → xkbcommon ile doğrudan keymap derleme
- `Cargo.toml`: `ureq`, `serde_json` kaldırıldı; `xkbcommon` eklendi
- ComboBox stilleri `theme.rs``combobox_stil.rs` taşındı
- `secondary_button` gradient renklerine açıklama satırları eklendi
- `draw_gradient_bg``combobox_stil.rs` taşındı
- Tüm ComboBox kullanımları `theme::show_combo_box` / `theme::selectable_value` ile güncellendi
- Klavye adımı (`keyboard.rs`): `ScrollArea`+`selectable_label` → ComboBox
- `TextEdit::multiline` arka planı per-widget `Frame::group` ile yapıldı
- `locale/` dosyaları güncellendi
- Font boyutları büyütüldü (label 9→12, secondary 7→9)
### Removed
- `ureq`, `serde_json` bağımlılıkları
- `assets/keyboards/KLE_*.json` önbellek dosyaları
- `assets/keyboards/KB_*.svg` dosyaları
- Eski klavye SVG yükleme kodu
- KLE JSON download/cache mantığı
- Demo sayfasındaki eski buton örnekleri
### Fixed
- `set_style`/`set_visuals` sırası: `set_style` önce çağrılıyor
- Bölge/zaman dilimi etiketleri dikey hizalandı
- Klavye adımında `on_enter` ilk render'da çağrılmama sorunu — layout `show()` içinde de initialize edilir
- `xkbcommon::Keycode` iterasyonu (`raw()` ile dönüşüm)
- `None::<&str>``None::<String>` tip uyuşmazlığı
---
## [4.0.0] — 2026-06-06
### Added
- Kurtarma modu (rescue step)
- Ağ yapılandırma adımı (NetworkManager/Wicd)
- Netinstall adımı
- LVM ve LUKS şifreleme desteği
- Demo modu (`--demo`)
- Otomatik kurulum (`--auto-install answer.toml`)
- `rust-i18n` ile çoklu dil desteği (Türkçe, İngilizce)
- Gradient butonlar (`gradient_button`, `secondary_button`)
- Dil seçici (bayrak + etiket)
- SVG yükleme desteği (`egui_extras` + `image`)
- Slayt gösterisi (branding.toml ile yapılandırılabilir)
- Adım listesi sidebar'da (işlenen adımlar yeşil ✔️)
- Anlık kurulum dosyası takibi
- Gelişmiş bölümleme UI'ı (kullanım çubuğu, LUKS göstergesi, mount-point seçici)
### Changed
- `pspec.xml`/`actions.py` → Cargo tabanlı derleme
- `comar`/`mudur` çağrıları`zbus` D-Bus ve doğrudan komut çalıştırma
- Shell tabanlı şifre yönetimi → güvenli stdin piping (LUKS/GRUB)
- Kullanıcı adı otomatik türetme (gerçek isim → sistem kullanıcı adı)
- Hostname doğrulama/sanitizasyon
- Temizlik işlemleri: kullanıcı silme, havuz yönetimi, önyükleme dizini temizliği
- Rsync komutu dinamik hariç tutma ile
- Versiyon 4.0.1 → 4.0.0 (Cargo.toml uyumu)
### Fixed
- GRUB şifre yazma güvenliği
- Dracut/Mkinitcpio LVM modülü ekleme
- EFI bölümü algılama ve mount etme
- Şifre gücü göstergesi renkleri
- Otomatik bölümleme (LVM/LUKS sıralaması)
---
## [0.1.0] — 2026-05-20
### Added
- İlk kararlı sürüm
- Proje iskeleti (egui + wgpu)
- Adım tabanlı kurulum sihirbazı
- Otomatik disk bölümleme (GPT/MBR)
- Kullanıcı oluşturma (şifre gücü kontrolü dahil)
- Bootloader yapılandırması (GRUB)
- Görüntü yöneticisi seçimi
- Özet ekranı
- Temel renk teması (açık/koyu)
- Özelleştirilebilir branding (branding.toml)
- Kurtarma amaçlı hata ekranı
- İşlem kuyruğu (JobQueue / async task yönetimi)
- YALI serileştirme (installer state save/load)
+1
View File
@@ -16,6 +16,7 @@ rust-i18n = "3.1"
lazy_static = "1.4" lazy_static = "1.4"
async-trait = "0.1" async-trait = "0.1"
chrono = "0.4" chrono = "0.4"
xkbcommon = "0.9"
[patch.crates-io] [patch.crates-io]
# Yerel mudur veya comar-api kütüphaneleri buraya eklenebilir # Yerel mudur veya comar-api kütüphaneleri buraya eklenebilir
+113 -2
View File
@@ -1,3 +1,114 @@
# YALI (Yet Another Linux Installer) Rewrite # YALI (Yet Another Linux Installer) — Rust / egui
Rust and egui based YALI which written python and qt originally. It is the installer that sets up the system by installing packages from the installation media (eg. CD, network, Internet) and carrying out the basic configuration. YALI is intended to be smooth in operation and fast in performance. 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.
+21
View File
@@ -0,0 +1,21 @@
=== COMAR Python'dan Arındırma Planı ===
Hedef: Python bağımlılığını tamamen kaldırmak, tek dil (C) kullanmak.
Kapsam:
- script.c → Python yorumlayıcısını kaldır, model verisini C struct'larında tut
- db.c → Python dict yerine C struct kullan (iksemel XML parse zaten C'de)
- pydbus.c → İşlevleri script.c'ye taşı, bu dosyayı kaldır
- core.py → C'de yeniden yaz (~50 satır, introspect/listeleme/register/remove)
- policy.py → C'de yeniden yaz (~8 satır, decorator → C callback)
- CMakeLists.txt → PythonLibs bağımlılığını kaldır
- Betik sistemi (scripts/<model>/<app>.py) → aktifse Lua'ya geçir, değilse kaldır
Yapılacaklar:
1. scripts/ dizinindeki .py dosyalarının kullanım durumunu kontrol et
2. Model veritabanını C struct'larına çevir (db.c)
3. core.py fonksiyonlarını C'ye taşı
4. script.c'deki Python API çağrılarını temizle
5. pydbus.c'yi script.c'ye birleştir
6. CMakeLists.txt'den PythonLibs'i çıkar
7. Python 2→3 geçişi yapma, direkt çıkar
+23 -5
View File
@@ -8,10 +8,16 @@ Bu dosya, PisiLinux için geliştirilen yeni Rust tabanlı yükleyicinin (Yali)
- [x] Global State yönetimi (Dil, kullanıcı, disk bilgileri) - [x] Global State yönetimi (Dil, kullanıcı, disk bilgileri)
- [x] **i18n (Çoklu Dil) desteğinin entegre edilmesi** - [x] **i18n (Çoklu Dil) desteğinin entegre edilmesi**
- [x] Navigasyon sistemi (Geri/İleri/İptal butonları yönetimi) - [x] Navigasyon sistemi (Geri/İleri/İptal butonları yönetimi)
- [x] Branding config hot-reload (dil değişince logo/slayt yeniden yüklenir)
## 2. Kurulum Modülleri (Calamares Benzeri) ## 2. Kurulum Modülleri (Calamares Benzeri)
- [x] **Hoşgeldiniz (Welcome):** Sistem gereksinim kontrolü ve dil seçimi. - [x] **Hoşgeldiniz (Welcome):** Sistem gereksinim kontrolü ve dil seçimi.
- [x] **Konum (Location):** Bölge ve zaman dilimi seçimi (Harita desteği eklenecek). - [x] **Konum (Location):** Bölge ve zaman dilimi seçimi.
- [x] Sistem saat dilimi yükleme (timedatectl / zone.tab)
- [x] Bölge/alan çift ComboBox (Calamares tarzı)
- [x] GeoIP ile otomatik dil/saat dilimi tespiti
- [x] NTP veya manuel tarih/saat ayarı
- [ ] Harita desteği (gelecek)
- [x] **Klavye (Keyboard):** Klavye düzeni seçimi ve test alanı. - [x] **Klavye (Keyboard):** Klavye düzeni seçimi ve test alanı.
- [x] **Bölümleme (Partition):** - [x] **Bölümleme (Partition):**
- [x] Disklerin listelenmesi (sysinfo/block-utils). - [x] Disklerin listelenmesi (sysinfo/block-utils).
@@ -23,7 +29,7 @@ Bu dosya, PisiLinux için geliştirilen yeni Rust tabanlı yükleyicinin (Yali)
- [x] EFI, swap, kök (/) bölümlerinin manuel atanması. - [x] EFI, swap, kök (/) bölümlerinin manuel atanması.
- [x] Değişiklik planı gösterimi (özet tablo). - [x] Değişiklik planı gösterimi (özet tablo).
- [x] GlobalState'e `custom_partitions: Vec<CustomPartition>` eklenmesi. - [x] GlobalState'e `custom_partitions: Vec<CustomPartition>` eklenmesi.
- [x] Yeniden boyutlandırma (mevcut bölümleri koruyarak) — gelecek. - [x] Yeniden boyutlandırma (mevcut bölümleri koruyarak) — iptal.
- [x] **Kullanıcılar (Users):** Kullanıcı adı, parola ve hostname ayarları. - [x] **Kullanıcılar (Users):** Kullanıcı adı, parola ve hostname ayarları.
- [x] **Özet (Summary):** Yapılacak işlemlerin son kontrolü. - [x] **Özet (Summary):** Yapılacak işlemlerin son kontrolü.
- [x] **Kurulum (Execution):** Gerçek işlemlerin (rsync, chroot, grub) yapıldığı ekran. - [x] **Kurulum (Execution):** Gerçek işlemlerin (rsync, chroot, grub) yapıldığı ekran.
@@ -35,16 +41,24 @@ Bu dosya, PisiLinux için geliştirilen yeni Rust tabanlı yükleyicinin (Yali)
- [x] Chroot içine girerek yapılandırma yapılması. - [x] Chroot içine girerek yapılandırma yapılması.
- [x] Bootloader (GRUB) kurulumu. - [x] Bootloader (GRUB) kurulumu.
- [x] COMAR üzerinden servislerin ve tetikleyicilerin çalıştırılması. - [x] COMAR üzerinden servislerin ve tetikleyicilerin çalıştırılması.
- [x] LUKS/GRUB parola yönetimi (stdin pipe, shell injection yok).
## 4. Görsel ve Kullanıcı Deneyimi (UX/UI) ## 4. Görsel ve Kullanıcı Deneyimi (UX/UI)
- [x] PisiLinux temasına uygun egui stillerinin uygulanması. - [x] PisiLinux temasına uygun egui stillerinin uygulanması.
- [x] Özel gradient butonlar (primary, secondary, danger — `buttons.rs`).
- [x] Stil uygulanmış ComboBox yardımcıları (`combobox.rs`).
- [x] ComboBox görsel sabitleri ayrı modülde (`combobox_stil.rs`).
- [x] Gradient arkaplanlı dropdown öğeleri, pointing hand imleci.
- [x] Kurulum sırasında slayt gösterisi (Slideshow). - [x] Kurulum sırasında slayt gösterisi (Slideshow).
- [x] Slayt gösterisi öncelikle ortak slaytları göstermeli, sonra masaüstü ortamına özel slaytları göstermeli. - [x] Slayt gösterisi öncelikle ortak slaytları göstermeli, sonra masaüstü ortamına özel slaytları göstermeli.
- [x] ISO hangi masaüstü ortamını içeriyorsa onun slaytı gösterilmeli. - [x] ISO hangi masaüstü ortamını içeriyorsa onun slaytı gösterilmeli.
- [x] Hata yakalama ve kullanıcıya bildirme ekranları. - [x] Hata yakalama ve kullanıcıya bildirme ekranları.
- [x] `weak_bg_fill` ile ComboBox popup arkaplan rengi (globalselde ayarlanabilir).
- [x] SVG ikon desteği (egui_extras `svg` feature).
- [x] Tema uygulama sırası düzeltmesi: önce `set_style`, sonra `set_visuals`.
## 5. Test ve Dağıtım ## 5. Test ve Dağıtım
- [x] --demo parameteresi ile komutlar aktif çalışmadan sadece görsel arayüzde başlangıçtan bitişe kadar gezinme, slaytlar arasında 5 sn kadar bekleme süresi. Böylece arayüz testi yapılmış olur. - [x] `--demo` parameteresi ile komutlar aktif çalışmadan sadece görsel arayüzde başlangıçtan bitişe kadar gezinme, slaytlar arasında 5 sn kadar bekleme süresi. Böylece arayüz testi yapılmış olur.
- [ ] ISO ortamında çalışma testleri. - [ ] ISO ortamında çalışma testleri.
- [ ] Farklı disk yapılarında (UEFI/Legacy, NVMe/SATA) testler. - [ ] Farklı disk yapılarında (UEFI/Legacy, NVMe/SATA) testler.
- [x] Otomatik kurulum (Auto-install/Answer file) desteği. - [x] Otomatik kurulum (Auto-install/Answer file) desteği.
@@ -84,5 +98,9 @@ Bu dosya, PisiLinux için geliştirilen yeni Rust tabanlı yükleyicinin (Yali)
## Kalan Eksiklikler ## Kalan Eksiklikler
- [x] **Disk Bölümü Yeniden Boyutlandırma (Shrink):** *(Kapsam notu: ntfsresize/resize2fs entegrasyonu disk I/O riskleri taşıdığından, mevcut manuel bölümleme akışında "bölümü sil ve yeniden oluştur" yaklaşımı yeterli; ayrıca calamares da aynı yolu izler.)* - [x] **Disk Bölümü Yeniden Boyutlandırma (Shrink):** *(Kapsam dışı — mevcut manuel bölümleme akışında "bölümü sil ve yeniden oluştur" yaklaşımı yeterli.)*
- [x] **Kurtarma Modunda Pisi Geçmiş Geri Alma:** `rescue.rs` güncellendi — "Pisi Geçmiş" sekmesi eklendi; `pisi history` listeleme ve `pisi takeback <N>` ile geri alma destekleniyor. - [x] **Kurtarma Modunda Pisi Geçmiş Geri Alma:** `rescue.rs` — "Pisi Geçmiş" sekmesi eklendi; `pisi history` listeleme ve `pisi takeback <N>` ile geri alma destekleniyor.
- [ ] **ISO ortamında test:** Canlı ISO üzerinde kurulum testi yapılmadı.
- [ ] **Farklı disk yapılarında test:** UEFI/Legacy, NVMe/SATA farklı kombinasyonları test edilmedi.
- [ ] **Harita desteği:** Konum adımına görsel harita seçici eklenmesi planlanıyor.
- [ ] **Ağ kurulumu:** WiFi profillerinin kaydedilmesi ve otomatik bağlanma.
+1
View File
@@ -401,6 +401,7 @@ job_media_check = "Verifying installation media"
# ── CLI / Auto-install ───────────────────────────────────── # ── CLI / Auto-install ─────────────────────────────────────
hostname = "Hostname" hostname = "Hostname"
timezone = "Timezone" timezone = "Timezone"
region = "Region"
username = "Username" username = "Username"
# ── Slideshow (fallback) ─────────────────────────────────── # ── Slideshow (fallback) ───────────────────────────────────
+1
View File
@@ -422,6 +422,7 @@ lvm_lv_action = "İşlem"
# ── CLI / Otomatik Kurulum ───────────────────────────────── # ── CLI / Otomatik Kurulum ─────────────────────────────────
hostname = "Bilgisayar adı" hostname = "Bilgisayar adı"
timezone = "Zaman dilimi" timezone = "Zaman dilimi"
region = "Bölge"
username = "Kullanıcı adı" username = "Kullanıcı adı"
# ── Slayt Gösterisi (yedek) ──────────────────────────────── # ── Slayt Gösterisi (yedek) ────────────────────────────────
+8 -35
View File
@@ -101,36 +101,9 @@ pub fn language_selector_ui(ui: &mut eframe::egui::Ui, state: &mut crate::instal
("en", "English", eframe::egui::include_image!("../assets/flags/us.svg")), ("en", "English", eframe::egui::include_image!("../assets/flags/us.svg")),
]; ];
// Standart egui makrosu - Tema motoru sayesinde otomatik olarak oval ve renk geçişli hissi verir crate::ui::theme::show_combo_box(ui, "language_select_combo", current_lang_label, available_width, |ui, w| {
eframe::egui::ComboBox::from_id_source("language_select_combo") for (code, label, image_source) in languages.iter() {
.selected_text(current_lang_label) let resp = crate::ui::theme::selectable_value_custom(ui, w, |ui, rect| {
.width(available_width) // Seçim kutusunu yatayda genişletir
.show_ui(ui, |ui| {
// Açılan alt listenin genişliğini de üst butonla eşitliyoruz (HTML SelectBox Mantığı)
ui.set_min_width(available_width);
ui.set_max_width(available_width);
for (code, label, image_source) in languages.iter() {
let item_height = 28.0;
// Eleman kutularını sarmalayarak tam genişlikte tıklanabilir yapıyoruz
let (rect, item_resp) = ui.allocate_exact_size(
egui::vec2(available_width, item_height),
egui::Sense::click(),
);
{
use egui::epaint::Mesh;
let top = egui::Color32::from_rgb(249, 249, 249);
let bot = egui::Color32::from_rgb(233, 233, 233);
let mut mesh = Mesh::default();
let idx = mesh.vertices.len() as u32;
mesh.vertices.push(egui::epaint::Vertex { pos: egui::pos2(rect.left(), rect.top()), uv: egui::pos2(0.0, 0.0), color: top });
mesh.vertices.push(egui::epaint::Vertex { pos: egui::pos2(rect.right(), rect.top()), uv: egui::pos2(0.0, 0.0), color: top });
mesh.vertices.push(egui::epaint::Vertex { pos: egui::pos2(rect.right(), rect.bottom()), uv: egui::pos2(0.0, 0.0), color: bot });
mesh.vertices.push(egui::epaint::Vertex { pos: egui::pos2(rect.left(), rect.bottom()), uv: egui::pos2(0.0, 0.0), color: bot });
mesh.indices.extend_from_slice(&[idx, idx + 1, idx + 2, idx + 2, idx + 3, idx]);
ui.painter().add(egui::Shape::mesh(mesh));
}
ui.put(rect, |ui: &mut egui::Ui| { ui.put(rect, |ui: &mut egui::Ui| {
ui.horizontal(|ui| { ui.horizontal(|ui| {
ui.add_space(6.0); ui.add_space(6.0);
@@ -139,11 +112,11 @@ pub fn language_selector_ui(ui: &mut eframe::egui::Ui, state: &mut crate::instal
ui.label(*label); ui.label(*label);
}).response }).response
}); });
if item_resp.clicked() { });
state.language = code.to_string(); if resp.clicked() {
} state.language = code.to_string();
item_resp.on_hover_cursor(egui::CursorIcon::PointingHand);
} }
}); }
});
}); });
} }
-205
View File
@@ -510,212 +510,7 @@ impl InstallerStep for WelcomeStep {
} }
} }
// ─────────────────────────────────────────────
// LOCATION STEP
// ─────────────────────────────────────────────
pub struct LocationStep {
timezones: Vec<String>,
geoip_done: bool,
datetime_init_done: bool,
}
impl Default for LocationStep {
fn default() -> Self {
let tz_list = vec![
"Africa/Cairo", "America/Argentina/Buenos_Aires", "America/New_York",
"America/Sao_Paulo", "Asia/Baku", "Asia/Dubai", "Asia/Istanbul",
"Asia/Tokyo", "Australia/Sydney", "Europe/Berlin", "Europe/Istanbul",
"Europe/London", "Europe/Moscow", "Europe/Paris", "Europe/Rome",
"Pacific/Auckland",
].iter().map(|s| s.to_string()).collect();
Self {
timezones: tz_list,
geoip_done: false,
datetime_init_done: false,
}
}
}
impl LocationStep {
fn geoip_lookup(state: &mut GlobalState) {
let out = std::process::Command::new("curl")
.args(["-s", "--connect-timeout", "3", "http://ip-api.com/json"])
.output();
if let Ok(o) = out {
if let Ok(text) = String::from_utf8(o.stdout) {
let extract = |key: &str| -> Option<String> {
let pat = format!("\"{}\":\"", key);
text.find(&pat).and_then(|pos| {
let start = pos + pat.len();
text[start..].find('"').map(|end| text[start..start+end].to_string())
})
};
if let Some(country) = extract("countryCode") {
match country.as_str() {
"TR" => state.language = "tr".to_string(),
"DE" => state.language = "de".to_string(),
"FR" => state.language = "fr".to_string(),
"IT" => state.language = "it".to_string(),
"ES" => state.language = "es".to_string(),
"RU" => state.language = "ru".to_string(),
"NL" => state.language = "nl".to_string(),
"PL" => state.language = "pl".to_string(),
_ => {}
}
}
if let Some(tz) = extract("timezone") {
if !tz.is_empty() {
state.timezone = tz;
}
}
}
}
}
fn init_from_system(&mut self, state: &mut GlobalState) {
use chrono::{Datelike, Timelike};
let now_str = chrono::Local::now().format("%Y-%m-%d %H:%M").to_string();
if let Ok(dt) = chrono::NaiveDateTime::parse_from_str(&now_str, "%Y-%m-%d %H:%M") {
state.manual_year = dt.year();
state.manual_month = dt.month();
state.manual_day = dt.day();
state.manual_hour = dt.hour();
state.manual_minute = dt.minute();
}
self.datetime_init_done = true;
}
}
impl InstallerStep for LocationStep {
fn name(&self) -> String {
format!("{} / {}", t!("location"), t!("datetime"))
}
fn on_enter(&mut self, state: &mut GlobalState) {
if !self.geoip_done {
Self::geoip_lookup(state);
self.geoip_done = true;
}
if !self.datetime_init_done {
self.init_from_system(state);
}
}
fn show(&mut self, ui: &mut egui::Ui, state: &mut GlobalState) {
use crate::ui::theme;
// --- BÖLGE VE ZAMAN DİLİMİ (Region & Timezone) ---
theme::section_heading(ui, &t!("location_title"));
ui.label(
egui::RichText::new(t!("location_description"))
.color(theme::c_text_dim())
.size(13.0),
);
ui.add_space(8.0);
ui.horizontal(|ui| {
ui.label(egui::RichText::new(t!("location_title")).strong().color(theme::c_text()));
let current_tz = if state.timezone.is_empty() {
"Europe/Istanbul".to_string()
} else {
state.timezone.clone()
};
if state.timezone.is_empty() {
state.timezone = current_tz.clone();
}
egui::ComboBox::from_id_source("timezone_select")
.selected_text(&current_tz)
.width(280.0)
.show_ui(ui, |ui| {
for tz in &self.timezones {
ui.selectable_value(&mut state.timezone, tz.clone(), tz);
}
});
});
ui.add_space(16.0);
ui.separator();
ui.add_space(16.0);
// --- TARİH VE SAAT AYARLARI (Date & Time) ---
theme::section_heading(ui, &t!("datetime_title"));
ui.label(
egui::RichText::new(t!("datetime_description"))
.color(theme::c_text_dim())
.size(13.0),
);
ui.add_space(8.0);
egui::Frame::group(ui.style())
.fill(theme::c_bg_widget())
.rounding(6.0)
.inner_margin(egui::Margin::same(12.0))
.show(ui, |ui| {
ui.set_width(ui.available_width());
egui::Grid::new("datetime_grid")
.num_columns(2)
.spacing([12.0, 10.0])
.show(ui, |ui| {
// NTP
ui.label(egui::RichText::new(t!("datetime_ntp_label")).strong().color(theme::c_text()));
ui.horizontal(|ui| {
ui.add(egui::Checkbox::new(&mut state.use_ntp, ""));
let status = if state.use_ntp { t!("datetime_ntp_on") } else { t!("datetime_ntp_off") };
ui.colored_label(
if state.use_ntp { theme::c_success() } else { theme::c_error() },
status,
);
});
ui.end_row();
if !state.use_ntp {
// Tarih
ui.label(egui::RichText::new(t!("datetime_date_label")).strong().color(theme::c_text()));
ui.horizontal(|ui| {
ui.label(t!("datetime_year"));
ui.add(egui::DragValue::new(&mut state.manual_year).clamp_range(2000..=2100).speed(1));
ui.label(t!("datetime_month"));
ui.add(egui::DragValue::new(&mut state.manual_month).clamp_range(1..=12).speed(1));
ui.label(t!("datetime_day"));
ui.add(egui::DragValue::new(&mut state.manual_day).clamp_range(1..=31).speed(1));
});
ui.end_row();
// Saat
ui.label(egui::RichText::new(t!("datetime_time_label")).strong().color(theme::c_text()));
ui.horizontal(|ui| {
ui.add(egui::DragValue::new(&mut state.manual_hour).clamp_range(0..=23).speed(1));
ui.label(":");
ui.add(egui::DragValue::new(&mut state.manual_minute).clamp_range(0..=59).speed(1));
});
ui.end_row();
}
});
});
ui.add_space(8.0);
if state.use_ntp {
ui.label(
egui::RichText::new(t!("datetime_ntp_info"))
.size(11.0)
.color(theme::c_text_dim()),
);
} else {
ui.label(
egui::RichText::new(t!("datetime_manual_info"))
.size(11.0)
.color(theme::c_text_dim()),
);
}
}
fn is_complete(&self, state: &GlobalState) -> bool {
!state.timezone.is_empty()
}
}
/// Mevcut bir Linux kurulumu olup olmadığını kontrol eder. /// Mevcut bir Linux kurulumu olup olmadığını kontrol eder.
/// Basitçe /mnt/etc/fstab varlığına veya blkid ile Linux bölümlerine bakar. /// Basitçe /mnt/etc/fstab varlığına veya blkid ile Linux bölümlerine bakar.
+25 -24
View File
@@ -6,8 +6,8 @@ mod autoinstall;
mod branding; mod branding;
mod funct; mod funct;
use installer::{GlobalState, InstallerStep, WelcomeStep, LocationStep}; use installer::{GlobalState, InstallerStep, WelcomeStep};
use steps::{KeyboardStep, UsersStep, SummaryStep, ExecutionStep, FinishStep, PartitionStep, BootloaderStep, NetworkStep, DisplayManagerStep}; use steps::{LocationStep, KeyboardStep, UsersStep, SummaryStep, ExecutionStep, FinishStep, PartitionStep, BootloaderStep, NetworkStep, DisplayManagerStep};
use eframe::egui; use eframe::egui;
use rust_i18n::t; use rust_i18n::t;
@@ -211,6 +211,7 @@ struct YaliApp {
last_step: usize, last_step: usize,
branding: branding::BrandingConfig, branding: branding::BrandingConfig,
loaded_language: String, loaded_language: String,
#[allow(dead_code)]
show_cancel_dialog: bool, show_cancel_dialog: bool,
} }
@@ -451,9 +452,9 @@ impl eframe::App for YaliApp {
} else { } else {
t!("next") t!("next")
}; };
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 = if is_valid {
ui::theme::primary_button(ui, &next_label).clicked() ui::theme::primary_button(ui, &next_label).clicked()
} else { } else {
@@ -476,25 +477,25 @@ impl eframe::App for YaliApp {
ui.add_space(10.0); ui.add_space(10.0);
}); });
// ── İptal onay diyaloğu ─────────────────────────────── // // ── İptal onay diyaloğu ───────────────────────────────
if self.show_cancel_dialog { // if self.show_cancel_dialog {
egui::Window::new(t!("cancel_confirm_title")) // egui::Window::new(t!("cancel_confirm_title"))
.collapsible(false) // .collapsible(false)
.resizable(false) // .resizable(false)
.anchor(egui::Align2::CENTER_CENTER, [0.0, 0.0]) // .anchor(egui::Align2::CENTER_CENTER, [0.0, 0.0])
.show(ctx, |ui| { // .show(ctx, |ui| {
ui.label(t!("cancel_confirm_msg")); // ui.label(t!("cancel_confirm_msg"));
ui.add_space(12.0); // ui.add_space(12.0);
ui.horizontal(|ui| { // ui.horizontal(|ui| {
if ui.button(t!("cancel_confirm_yes")).clicked() { // if ui.button(t!("cancel_confirm_yes")).clicked() {
ctx.send_viewport_cmd(egui::ViewportCommand::Close); // ctx.send_viewport_cmd(egui::ViewportCommand::Close);
} // }
if ui.button(t!("cancel_confirm_no")).clicked() { // if ui.button(t!("cancel_confirm_no")).clicked() {
self.show_cancel_dialog = false; // self.show_cancel_dialog = false;
} // }
}); // });
}); // });
} // }
// ── Merkez panel ───────────────────────────────────── // ── Merkez panel ─────────────────────────────────────
egui::CentralPanel::default() egui::CentralPanel::default()
+6 -11
View File
@@ -1,6 +1,7 @@
use eframe::egui; 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;
#[derive(Default)] #[derive(Default)]
pub struct BootloaderStep { pub struct BootloaderStep {
@@ -47,17 +48,11 @@ impl InstallerStep for BootloaderStep {
} else { } else {
state.bootloader_device.clone() state.bootloader_device.clone()
}; };
egui::ComboBox::from_id_source("boot_device_select") theme::show_combo_box(ui, "boot_device_select", current.as_str(), ui.available_width(), |ui, w| {
.selected_text(&current) for dev in &device_list {
.show_ui(ui, |ui| { theme::selectable_value(ui, &mut state.bootloader_device, dev.clone(), dev.as_str(), w);
for dev in &device_list { }
ui.selectable_value( });
&mut state.bootloader_device,
dev.clone(),
dev,
);
}
});
ui.label( ui.label(
egui::RichText::new(t!("bootloader_device_hint")) egui::RichText::new(t!("bootloader_device_hint"))
.size(11.0) .size(11.0)
+15 -25
View File
@@ -1,6 +1,7 @@
use eframe::egui; 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;
const DM_OPTIONS: &[&str] = &["sddm", "lightdm", "gdm"]; const DM_OPTIONS: &[&str] = &["sddm", "lightdm", "gdm"];
const DE_OPTIONS: &[&str] = &["plasma", "xfce", "gnome", "cinnamon", "budgie", "lxde", "lxqt", "mate", "lumina"]; const DE_OPTIONS: &[&str] = &["plasma", "xfce", "gnome", "cinnamon", "budgie", "lxde", "lxqt", "mate", "lumina"];
@@ -31,37 +32,26 @@ impl InstallerStep for DisplayManagerStep {
.show(ui, |ui| { .show(ui, |ui| {
// Ekran yöneticisi // Ekran yöneticisi
ui.label(t!("display_manager_select")); ui.label(t!("display_manager_select"));
egui::ComboBox::from_id_source("dm_combo") let dm_current = state.display_manager.clone();
.selected_text(state.display_manager.as_str()) theme::show_combo_box(ui, "dm_combo", dm_current.as_str(), ui.available_width(), |ui, w| {
.show_ui(ui, |ui| { for dm in DM_OPTIONS {
for dm in DM_OPTIONS { theme::selectable_value(ui, &mut state.display_manager, dm.to_string(), *dm, w);
ui.selectable_value( }
&mut state.display_manager, });
dm.to_string(),
*dm,
);
}
});
ui.end_row(); ui.end_row();
// Masaüstü ortamı (seçilebilir) // Masaüstü ortamı (seçilebilir)
ui.label(t!("display_manager_de")); ui.label(t!("display_manager_de"));
let current_de = if state.desktop_environment.is_empty() || state.desktop_environment == "default" { let de_current = if state.desktop_environment.is_empty() || state.desktop_environment == "default" {
"plasma" "plasma".to_string()
} else { } else {
state.desktop_environment.as_str() state.desktop_environment.clone()
}; };
egui::ComboBox::from_id_source("de_combo") theme::show_combo_box(ui, "de_combo", de_current.as_str(), ui.available_width(), |ui, w| {
.selected_text(current_de) for de in DE_OPTIONS {
.show_ui(ui, |ui| { theme::selectable_value(ui, &mut state.desktop_environment, de.to_string(), *de, w);
for de in DE_OPTIONS { }
ui.selectable_value( });
&mut state.desktop_environment,
de.to_string(),
*de,
);
}
});
ui.end_row(); ui.end_row();
// Otomatik oturum açma // Otomatik oturum açma
+67 -100
View File
@@ -1,4 +1,4 @@
use eframe::egui; use eframe::egui::{self, Color32};
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;
@@ -8,8 +8,6 @@ use crate::ui::theme;
/// Dosya okunamazsa sabit bir temel liste kullanılır. /// Dosya okunamazsa sabit bir temel liste kullanılır.
pub struct KeyboardStep { pub struct KeyboardStep {
test_input: String, test_input: String,
layout_filter: String,
variant_filter: String,
layouts: Vec<(String, String)>, // (kod, gösterim adı) layouts: Vec<(String, String)>, // (kod, gösterim adı)
variants: Vec<(String, String, String)>, // (layout, variant, gösterim adı) variants: Vec<(String, String, String)>, // (layout, variant, gösterim adı)
} }
@@ -17,6 +15,7 @@ pub struct KeyboardStep {
fn default_keyboard_layouts() -> Vec<(String, String)> { fn default_keyboard_layouts() -> Vec<(String, String)> {
vec![ vec![
("tr".to_string(), "Türkçe (Q)".to_string()), ("tr".to_string(), "Türkçe (Q)".to_string()),
("az".to_string(), "Azərbaycanca".to_string()),
("us".to_string(), "İngilizce (US)".to_string()), ("us".to_string(), "İngilizce (US)".to_string()),
("de".to_string(), "Almanca".to_string()), ("de".to_string(), "Almanca".to_string()),
("fr".to_string(), "Fransızca".to_string()), ("fr".to_string(), "Fransızca".to_string()),
@@ -29,6 +28,7 @@ fn default_keyboard_layouts() -> Vec<(String, String)> {
fn default_keyboard_variants() -> Vec<(String, String, String)> { fn default_keyboard_variants() -> Vec<(String, String, String)> {
vec![ vec![
("tr".to_string(), "f".to_string(), "Türkçe (F)".to_string()), ("tr".to_string(), "f".to_string(), "Türkçe (F)".to_string()),
("az".to_string(), "cyrillic".to_string(), "Azərbaycan (Kiril)".to_string()),
] ]
} }
@@ -129,8 +129,6 @@ impl Default for KeyboardStep {
let (layouts, variants) = load_keyboard_layouts(); let (layouts, variants) = load_keyboard_layouts();
Self { Self {
test_input: String::new(), test_input: String::new(),
layout_filter: String::new(),
variant_filter: String::new(),
layouts, layouts,
variants, variants,
} }
@@ -155,122 +153,91 @@ impl InstallerStep for KeyboardStep {
} }
fn show(&mut self, ui: &mut egui::Ui, state: &mut GlobalState) { fn show(&mut self, ui: &mut egui::Ui, state: &mut GlobalState) {
if state.keyboard_layout.is_empty() {
state.keyboard_layout = if state.language == "tr" { "tr" } else { "us" }.to_string();
}
ui.heading(t!("keyboard_title")); ui.heading(t!("keyboard_title"));
ui.label(t!("keyboard_description")); ui.label(t!("keyboard_description"));
ui.add_space(12.0); ui.add_space(12.0);
// ── Klavye görseli (xkbcommon ile çizilir, temaya uyumlu) ─
if let Some(kle) = crate::ui::kle_svg::compile_layout(&state.keyboard_layout, &state.keyboard_variant) {
let max = egui::Vec2::new(ui.available_width(), ui.available_height().max(100.0));
crate::ui::kle_svg::draw_layout(ui, &kle, max);
}
ui.add_space(12.0);
// ── Layout ve Varyant ComboBox'ları yan yana ──────────────
let available_variants: Vec<_> = self let available_variants: Vec<_> = self
.variants .variants
.iter() .iter()
.filter(|(layout, _, _)| layout == &state.keyboard_layout) .filter(|(layout, _, _)| layout == &state.keyboard_layout)
.collect(); .collect();
ui.columns(2, |cols| { let current_variant_name = if state.keyboard_variant.is_empty() {
cols[0].vertical(|ui| { t!("keyboard_variant_default").to_string()
egui::Frame::group(ui.style()) } else {
.fill(theme::c_bg_widget()) let vn = variant_display_name(&state.keyboard_layout, &state.keyboard_variant, &self.variants);
.stroke(egui::Stroke::new(1.0, egui::Color32::from_gray(200))) if vn.is_empty() { state.keyboard_variant.clone() } else { vn }
.rounding(egui::Rounding::same(8.0)) };
.inner_margin(egui::Margin::same(8.0)) let current_layout_name = layout_display_name(&state.keyboard_layout, &self.layouts);
.show(ui, |ui| {
ui.label(t!("keyboard_layout_label"));
ui.add(
egui::TextEdit::singleline(&mut self.layout_filter)
.hint_text(t!("keyboard_layout_search_hint")),
);
ui.add_space(4.0);
egui::ScrollArea::vertical() ui.horizontal(|ui| {
.id_source("kbd_layout_scroll") ui.vertical(|ui| {
.auto_shrink([false, false]) ui.add_space(6.0);
.max_height(240.0) ui.label(egui::RichText::new(t!("keyboard_layout_label")).strong().color(theme::c_text()));
.show(ui, |ui| { });
let filter = self.layout_filter.to_lowercase(); ui.add_space(8.0);
let filtered_layouts: Vec<_> = self.layouts.iter() let lw = ui.available_width().min(200.0);
.filter(|(code, display)| { theme::show_combo_box(ui, "kbd_layout", current_layout_name.as_str(), lw, |ui, w| {
filter.is_empty() for (code, display) in &self.layouts {
|| code.to_lowercase().contains(&filter) if theme::selectable_value(ui, &mut state.keyboard_layout, code.clone(), display.as_str(), w).clicked() {
|| display.to_lowercase().contains(&filter) if !self.variants.iter().any(|(layout, _, _)| layout == code) {
}) state.keyboard_variant.clear();
.collect(); }
apply_system_keyboard_layout(&state.keyboard_layout, &state.keyboard_variant);
if filtered_layouts.is_empty() { }
ui.label(t!("keyboard_layout_search_no_match")); }
}
for (code, display) in filtered_layouts {
let is_selected = state.keyboard_layout == *code;
if ui.selectable_label(is_selected, display).clicked() {
state.keyboard_layout = code.to_string();
self.variant_filter.clear();
if !self.variants.iter().any(|(layout, _, _)| layout == code) {
state.keyboard_variant.clear();
}
apply_system_keyboard_layout(&state.keyboard_layout, &state.keyboard_variant);
}
}
});
});
}); });
cols[1].vertical(|ui| { ui.add_space(16.0);
egui::Frame::group(ui.style())
.fill(theme::c_bg_widget())
.stroke(egui::Stroke::new(1.0, egui::Color32::from_gray(200)))
.rounding(egui::Rounding::same(8.0))
.inner_margin(egui::Margin::same(8.0))
.show(ui, |ui| {
ui.label(t!("keyboard_variant_label"));
ui.add(
egui::TextEdit::singleline(&mut self.variant_filter)
.hint_text(t!("keyboard_variant_search_hint")),
);
ui.add_space(4.0);
egui::ScrollArea::vertical() ui.vertical(|ui| {
.id_source("kbd_variant_scroll") ui.add_space(6.0);
.auto_shrink([false, false]) ui.label(egui::RichText::new(t!("keyboard_variant_label")).strong().color(theme::c_text()));
.max_height(240.0) });
.show(ui, |ui| { ui.add_space(8.0);
let variant_filter = self.variant_filter.to_lowercase(); let vw = ui.available_width().min(200.0);
let filtered_variants: Vec<_> = available_variants theme::show_combo_box(ui, "kbd_variant", current_variant_name.as_str(), vw, |ui, w| {
.into_iter() let default_variant = String::new();
.filter(|(_, variant, display)| { if theme::selectable_value(ui, &mut state.keyboard_variant, default_variant, &t!("keyboard_variant_default"), w).clicked() {
variant_filter.is_empty() apply_system_keyboard_layout(&state.keyboard_layout, "");
|| variant.to_lowercase().contains(&variant_filter) }
|| display.to_lowercase().contains(&variant_filter) for (_, variant, display) in &available_variants {
}) if theme::selectable_value(ui, &mut state.keyboard_variant, variant.clone(), display.as_str(), w).clicked() {
.collect(); apply_system_keyboard_layout(&state.keyboard_layout, &state.keyboard_variant);
}
let mut selected_default = state.keyboard_variant.is_empty(); }
if ui.selectable_value(&mut selected_default, true, t!("keyboard_variant_default")).clicked() {
state.keyboard_variant.clear();
apply_system_keyboard_layout(&state.keyboard_layout, "");
}
if filtered_variants.is_empty() {
ui.label(t!("keyboard_variant_search_no_match"));
}
for (_, variant, display) in filtered_variants {
let is_selected = state.keyboard_variant == *variant;
if ui.selectable_label(is_selected, display).clicked() {
state.keyboard_variant = variant.to_string();
apply_system_keyboard_layout(&state.keyboard_layout, &state.keyboard_variant);
}
}
});
});
}); });
}); });
ui.add_space(12.0); ui.add_space(12.0);
ui.label(t!("keyboard_test_label")); ui.label(t!("keyboard_test_label"));
ui.add_space(4.0); ui.add_space(4.0);
ui.add( egui::Frame::group(ui.style())
egui::TextEdit::multiline(&mut self.test_input) .fill(Color32::from_rgb(255, 255, 255))
.desired_rows(5) .rounding(6.0)
.desired_width(ui.available_width()) .show(ui, |ui| {
.hint_text(t!("keyboard_test_hint")), ui.add(
); egui::TextEdit::singleline(&mut self.test_input)
.desired_width(ui.available_width())
.hint_text(t!("keyboard_test_hint"))
.frame(false),
);
});
ui.add_space(8.0); ui.add_space(8.0);
ui.label(format!("{}: {}", t!("keyboard_selected"), { ui.label(format!("{}: {}", t!("keyboard_selected"), {
+317
View File
@@ -0,0 +1,317 @@
use eframe::egui;
use rust_i18n::t;
use crate::installer::{GlobalState, InstallerStep};
pub struct LocationStep {
pub regions: Vec<String>,
pub zones_by_region: std::collections::HashMap<String, Vec<String>>,
pub geoip_done: bool,
pub datetime_init_done: bool,
}
fn load_timezones() -> Vec<String> {
if let Ok(out) = std::process::Command::new("timedatectl")
.args(["list-timezones"])
.output()
{
if out.status.success() {
let text = String::from_utf8_lossy(&out.stdout);
let zones: Vec<String> = text
.lines()
.map(|l| l.trim().to_string())
.filter(|l| !l.is_empty())
.collect();
if !zones.is_empty() {
return zones;
}
}
}
if let Ok(text) = std::fs::read_to_string("/usr/share/zoneinfo/zone.tab") {
let zones: Vec<String> = text
.lines()
.filter(|l| !l.starts_with('#') && !l.trim().is_empty())
.filter_map(|l| l.split('\t').nth(2))
.map(|s| s.to_string())
.collect();
if !zones.is_empty() {
return zones;
}
}
vec![
"Africa/Cairo", "Africa/Casablanca", "Africa/Johannesburg", "Africa/Lagos",
"Africa/Nairobi", "America/Argentina/Buenos_Aires", "America/Bogota",
"America/Caracas", "America/Chicago", "America/Denver", "America/Lima",
"America/Los_Angeles", "America/Mexico_City", "America/New_York",
"America/Panama", "America/Sao_Paulo", "America/Santiago", "America/Toronto",
"America/Vancouver", "Asia/Almaty", "Asia/Baghdad", "Asia/Baku",
"Asia/Bangkok", "Asia/Dubai", "Asia/Ho_Chi_Minh", "Asia/Hong_Kong",
"Asia/Istanbul", "Asia/Jakarta", "Asia/Jerusalem", "Asia/Kabul",
"Asia/Karachi", "Asia/Kathmandu", "Asia/Kolkata", "Asia/Kuala_Lumpur",
"Asia/Manila", "Asia/Riyadh", "Asia/Seoul", "Asia/Shanghai",
"Asia/Singapore", "Asia/Taipei", "Asia/Tashkent", "Asia/Tehran",
"Asia/Tokyo", "Asia/Yangon", "Australia/Adelaide", "Australia/Brisbane",
"Australia/Melbourne", "Australia/Perth", "Australia/Sydney",
"Europe/Amsterdam", "Europe/Athens", "Europe/Berlin", "Europe/Brussels",
"Europe/Bucharest", "Europe/Copenhagen", "Europe/Dublin",
"Europe/Helsinki", "Europe/Istanbul", "Europe/Kyiv", "Europe/Lisbon",
"Europe/London", "Europe/Madrid", "Europe/Moscow", "Europe/Oslo",
"Europe/Paris", "Europe/Prague", "Europe/Rome", "Europe/Stockholm",
"Europe/Vienna", "Europe/Warsaw", "Europe/Zurich",
"Pacific/Auckland", "Pacific/Fiji", "Pacific/Honolulu",
"Pacific/Port_Moresby",
].iter().map(|s| s.to_string()).collect()
}
fn build_tz_data(zones: &[String]) -> (Vec<String>, std::collections::HashMap<String, Vec<String>>) {
let mut regions_set = std::collections::BTreeSet::new();
let mut zones_by_region: std::collections::HashMap<String, Vec<String>> = std::collections::HashMap::new();
for tz in zones {
if let Some(slash) = tz.find('/') {
let region = tz[..slash].to_string();
let zone = tz[slash + 1..].to_string();
regions_set.insert(region.clone());
zones_by_region.entry(region).or_default().push(zone);
}
}
for zones in zones_by_region.values_mut() {
zones.sort();
}
let regions: Vec<String> = regions_set.into_iter().collect();
(regions, zones_by_region)
}
impl Default for LocationStep {
fn default() -> Self {
let all_zones = load_timezones();
let (regions, zones_by_region) = build_tz_data(&all_zones);
Self {
regions,
zones_by_region,
geoip_done: false,
datetime_init_done: false,
}
}
}
impl LocationStep {
fn geoip_lookup(state: &mut GlobalState) {
let out = std::process::Command::new("curl")
.args(["-s", "--connect-timeout", "3", "http://ip-api.com/json"])
.output();
if let Ok(o) = out {
if let Ok(text) = String::from_utf8(o.stdout) {
let extract = |key: &str| -> Option<String> {
let pat = format!("\"{}\":\"", key);
text.find(&pat).and_then(|pos| {
let start = pos + pat.len();
text[start..].find('"').map(|end| text[start..start+end].to_string())
})
};
if let Some(country) = extract("countryCode") {
match country.as_str() {
"TR" => state.language = "tr".to_string(),
"DE" => state.language = "de".to_string(),
"FR" => state.language = "fr".to_string(),
"IT" => state.language = "it".to_string(),
"ES" => state.language = "es".to_string(),
"RU" => state.language = "ru".to_string(),
"NL" => state.language = "nl".to_string(),
"PL" => state.language = "pl".to_string(),
_ => {}
}
}
if let Some(tz) = extract("timezone") {
if !tz.is_empty() {
state.timezone = tz;
}
}
}
}
}
fn init_from_system(&mut self, state: &mut GlobalState) {
use chrono::{Datelike, Timelike};
let now_str = chrono::Local::now().format("%Y-%m-%d %H:%M").to_string();
if let Ok(dt) = chrono::NaiveDateTime::parse_from_str(&now_str, "%Y-%m-%d %H:%M") {
state.manual_year = dt.year();
state.manual_month = dt.month();
state.manual_day = dt.day();
state.manual_hour = dt.hour();
state.manual_minute = dt.minute();
}
self.datetime_init_done = true;
}
fn split_tz(tz: &str) -> (String, String) {
if let Some(slash) = tz.find('/') {
(tz[..slash].to_string(), tz[slash + 1..].to_string())
} else {
("Other".to_string(), tz.to_string())
}
}
}
impl InstallerStep for LocationStep {
fn name(&self) -> String {
format!("{} / {}", t!("location"), t!("datetime"))
}
fn on_enter(&mut self, state: &mut GlobalState) {
if !self.geoip_done {
Self::geoip_lookup(state);
self.geoip_done = true;
}
if !self.datetime_init_done {
self.init_from_system(state);
}
if state.timezone.is_empty() {
state.timezone = "Europe/Istanbul".to_string();
}
}
fn show(&mut self, ui: &mut egui::Ui, state: &mut GlobalState) {
use crate::ui::theme;
theme::section_heading(ui, &t!("location_title"));
ui.label(
egui::RichText::new(t!("location_description"))
.color(theme::c_text_dim())
.size(13.0),
);
ui.add_space(8.0);
let (mut cur_region, mut cur_zone) = Self::split_tz(&state.timezone);
if !self.regions.contains(&cur_region) {
if let Some(first) = self.regions.first() {
cur_region = first.clone();
if let Some(zones) = self.zones_by_region.get(&cur_region) {
cur_zone = zones.first().cloned().unwrap_or_default();
}
state.timezone = format!("{}/{}", cur_region, cur_zone);
}
}
ui.horizontal(|ui| {
ui.vertical(|ui| {
ui.add_space(6.0);
ui.label(egui::RichText::new(t!("region")).strong().color(theme::c_text()));
});
ui.add_space(4.0);
let rw = 140.0_f32.min(ui.available_width() / 3.0);
let region_text = cur_region.clone();
theme::show_combo_box(ui, "tz_region_select", region_text.as_str(), rw, |ui, w| {
for region in &self.regions {
if theme::selectable_value(ui, &mut cur_region, region.clone(), region.as_str(), w).clicked() {
if let Some(zones) = self.zones_by_region.get(&cur_region) {
cur_zone = zones.first().cloned().unwrap_or_default();
}
state.timezone = format!("{}/{}", cur_region, cur_zone);
}
}
});
ui.add_space(16.0);
ui.vertical(|ui| {
ui.add_space(6.0);
ui.label(egui::RichText::new(t!("timezone")).strong().color(theme::c_text()));
});
ui.add_space(4.0);
let zw = ui.available_width().max(160.0);
if let Some(zones) = self.zones_by_region.get(&cur_region) {
if !zones.contains(&cur_zone) {
cur_zone = zones.first().cloned().unwrap_or_default();
state.timezone = format!("{}/{}", cur_region, cur_zone);
}
let zone_text = cur_zone.clone();
theme::show_combo_box(ui, "tz_zone_select", zone_text.as_str(), zw, |ui, w| {
for zone in zones {
if theme::selectable_value(ui, &mut cur_zone, zone.clone(), zone.as_str(), w).clicked() {
state.timezone = format!("{}/{}", cur_region, cur_zone);
}
}
});
}
});
ui.add_space(16.0);
ui.separator();
ui.add_space(16.0);
theme::section_heading(ui, &t!("datetime_title"));
ui.label(
egui::RichText::new(t!("datetime_description"))
.color(theme::c_text_dim())
.size(13.0),
);
ui.add_space(8.0);
egui::Frame::group(ui.style())
.rounding(6.0)
.inner_margin(egui::Margin::same(12.0))
.show(ui, |ui| {
ui.set_width(ui.available_width());
egui::Grid::new("datetime_grid")
.num_columns(2)
.spacing([12.0, 10.0])
.show(ui, |ui| {
ui.label(egui::RichText::new(t!("datetime_ntp_label")).strong().color(theme::c_text()));
ui.horizontal(|ui| {
ui.add(egui::Checkbox::new(&mut state.use_ntp, ""));
let status = if state.use_ntp { t!("datetime_ntp_on") } else { t!("datetime_ntp_off") };
ui.colored_label(
if state.use_ntp { theme::c_success() } else { theme::c_error() },
status,
);
});
ui.end_row();
if !state.use_ntp {
ui.label(egui::RichText::new(t!("datetime_date_label")).strong().color(theme::c_text()));
ui.horizontal(|ui| {
ui.label(t!("datetime_year"));
ui.add(egui::DragValue::new(&mut state.manual_year).clamp_range(2000..=2100).speed(1));
ui.label(t!("datetime_month"));
ui.add(egui::DragValue::new(&mut state.manual_month).clamp_range(1..=12).speed(1));
ui.label(t!("datetime_day"));
ui.add(egui::DragValue::new(&mut state.manual_day).clamp_range(1..=31).speed(1));
});
ui.end_row();
ui.label(egui::RichText::new(t!("datetime_time_label")).strong().color(theme::c_text()));
ui.horizontal(|ui| {
ui.add(egui::DragValue::new(&mut state.manual_hour).clamp_range(0..=23).speed(1));
ui.label(":");
ui.add(egui::DragValue::new(&mut state.manual_minute).clamp_range(0..=59).speed(1));
});
ui.end_row();
}
});
});
ui.add_space(8.0);
if state.use_ntp {
ui.label(
egui::RichText::new(t!("datetime_ntp_info"))
.size(11.0)
.color(theme::c_text_dim()),
);
} else {
ui.label(
egui::RichText::new(t!("datetime_manual_info"))
.size(11.0)
.color(theme::c_text_dim()),
);
}
}
fn is_complete(&self, state: &GlobalState) -> bool {
!state.timezone.is_empty()
}
}
+2
View File
@@ -9,6 +9,7 @@ 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 use keyboard::KeyboardStep; pub use keyboard::KeyboardStep;
pub use users::UsersStep; pub use users::UsersStep;
@@ -20,3 +21,4 @@ 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 display_manager::DisplayManagerStep;
pub use location::LocationStep;
+28 -47
View File
@@ -2,6 +2,7 @@
use eframe::egui; use eframe::egui;
use rust_i18n::t; use rust_i18n::t;
use crate::ui::theme;
// sysinfo::Disks mount point başına bir kayıt döndürür (partition'lar), // sysinfo::Disks mount point başına bir kayıt döndürür (partition'lar),
// ham disk listesi için lsblk kullanıyoruz. // ham disk listesi için lsblk kullanıyoruz.
@@ -892,15 +893,12 @@ impl PartitionStep {
// Dosya sistemi (boyut önerisi ile) // Dosya sistemi (boyut önerisi ile)
ui.label(t!("mp_fstype")); ui.label(t!("mp_fstype"));
let prev_fs = self.manual.add_fstype; let prev_fs = self.manual.add_fstype;
egui::ComboBox::from_id_source("fs_combo") let fs_label = FS_OPTIONS.get(self.manual.add_fstype).map(|x| x.0).unwrap_or("?");
.selected_text(FS_OPTIONS.get(self.manual.add_fstype).map(|x| x.0).unwrap_or("?")) theme::show_combo_box(ui, "fs_combo", fs_label, ui.available_width(), |ui, w| {
.show_ui(ui, |ui| { for (i, (label, _)) in FS_OPTIONS.iter().enumerate() {
for (i, (label, _)) in FS_OPTIONS.iter().enumerate() { theme::selectable_value(ui, &mut self.manual.add_fstype, i, *label, w);
ui.selectable_value( }
&mut self.manual.add_fstype, i, *label });
);
}
});
// FS tipi değişince akıllı boyut öner // FS tipi değişince akıllı boyut öner
if prev_fs != self.manual.add_fstype && self.manual.add_size_str.is_empty() { if prev_fs != self.manual.add_fstype && self.manual.add_size_str.is_empty() {
let fs = FS_OPTIONS.get(self.manual.add_fstype).map(|x| &x.1); let fs = FS_OPTIONS.get(self.manual.add_fstype).map(|x| &x.1);
@@ -925,16 +923,11 @@ impl PartitionStep {
} else { } else {
self.manual.add_mountpoint.clone() self.manual.add_mountpoint.clone()
}; };
egui::ComboBox::from_id_source("mp_combo") theme::show_combo_box(ui, "mp_combo", &current_mp, 120.0, |ui, w| {
.selected_text(&current_mp) for mp in COMMON_MOUNTPOINTS {
.width(120.0) theme::selectable_value(ui, &mut self.manual.add_mountpoint, mp.to_string(), *mp, w);
.show_ui(ui, |ui| { }
for mp in COMMON_MOUNTPOINTS { });
if ui.selectable_label(false, *mp).clicked() {
self.manual.add_mountpoint = mp.to_string();
}
}
});
let mp_field = egui::TextEdit::singleline(&mut self.manual.add_mountpoint) let mp_field = egui::TextEdit::singleline(&mut self.manual.add_mountpoint)
.hint_text(t!("mp_add_dialog_hint")); .hint_text(t!("mp_add_dialog_hint"));
ui.add(mp_field); ui.add(mp_field);
@@ -1143,15 +1136,11 @@ impl PartitionStep {
let current_vg = state.volume_groups.get(self.manual.add_lv_vg_idx) let current_vg = state.volume_groups.get(self.manual.add_lv_vg_idx)
.map(|vg| vg.name.as_str()) .map(|vg| vg.name.as_str())
.unwrap_or("?"); .unwrap_or("?");
egui::ComboBox::from_id_source("lv_vg_combo") theme::show_combo_box(ui, "lv_vg_combo", current_vg, ui.available_width(), |ui, w| {
.selected_text(current_vg) for (vi, vg) in state.volume_groups.iter().enumerate() {
.show_ui(ui, |ui| { theme::selectable_value(ui, &mut self.manual.add_lv_vg_idx, vi, vg.name.as_str(), w);
for (vi, vg) in state.volume_groups.iter().enumerate() { }
ui.selectable_value( });
&mut self.manual.add_lv_vg_idx, vi, &vg.name
);
}
});
ui.end_row(); ui.end_row();
// LV Adı // LV Adı
@@ -1161,15 +1150,12 @@ impl PartitionStep {
// Dosya sistemi // Dosya sistemi
ui.label(t!("mp_fstype")); ui.label(t!("mp_fstype"));
egui::ComboBox::from_id_source("lv_fs_combo") let lv_fs_label = FS_OPTIONS.get(self.manual.add_lv_fstype).map(|x| x.0).unwrap_or("?");
.selected_text(FS_OPTIONS.get(self.manual.add_lv_fstype).map(|x| x.0).unwrap_or("?")) theme::show_combo_box(ui, "lv_fs_combo", lv_fs_label, ui.available_width(), |ui, w| {
.show_ui(ui, |ui| { for (i, (label, _)) in FS_OPTIONS.iter().enumerate() {
for (i, (label, _)) in FS_OPTIONS.iter().enumerate() { theme::selectable_value(ui, &mut self.manual.add_lv_fstype, i, *label, w);
ui.selectable_value( }
&mut self.manual.add_lv_fstype, i, *label });
);
}
});
ui.end_row(); ui.end_row();
// Boyut // Boyut
@@ -1185,16 +1171,11 @@ impl PartitionStep {
} else { } else {
self.manual.add_lv_mountpoint.clone() self.manual.add_lv_mountpoint.clone()
}; };
egui::ComboBox::from_id_source("lv_mp_combo") theme::show_combo_box(ui, "lv_mp_combo", &current_mp, 120.0, |ui, w| {
.selected_text(&current_mp) for mp in COMMON_MOUNTPOINTS {
.width(120.0) theme::selectable_value(ui, &mut self.manual.add_lv_mountpoint, mp.to_string(), *mp, w);
.show_ui(ui, |ui| { }
for mp in COMMON_MOUNTPOINTS { });
if ui.selectable_label(false, *mp).clicked() {
self.manual.add_lv_mountpoint = mp.to_string();
}
}
});
let mp_field = egui::TextEdit::singleline(&mut self.manual.add_lv_mountpoint) let mp_field = egui::TextEdit::singleline(&mut self.manual.add_lv_mountpoint)
.hint_text(t!("mp_add_dialog_hint")); .hint_text(t!("mp_add_dialog_hint"));
ui.add(mp_field); ui.add(mp_field);
+7 -6
View File
@@ -87,12 +87,13 @@ pub fn primary_button(ui: &mut egui::Ui, text: &str) -> egui::Response {
pub fn secondary_button(ui: &mut egui::Ui, text: &str) -> egui::Response { pub fn secondary_button(ui: &mut egui::Ui, text: &str) -> egui::Response {
gradient_button(ui, text, gradient_button(ui, text,
Color32::from_rgb(130, 140, 150), Color32::from_rgb(130, 140, 150), // top
Color32::from_rgb(100, 110, 120), Color32::from_rgb(100, 110, 120), // bottom
Color32::from_rgb(158, 158, 175), Color32::from_rgb(255, 255, 255), // text color32
Color32::from_rgb(150, 160, 170), Color32::from_rgb(150, 160, 170), // hover top
Color32::from_rgb(120, 130, 140), Color32::from_rgb(120, 130, 140), // hover bottom
Color32::from_rgb(100, 110, 120)) Color32::from_rgb(100, 110, 120) // border
)
} }
pub fn danger_button(ui: &mut egui::Ui, text: &str) -> egui::Response { pub fn danger_button(ui: &mut egui::Ui, text: &str) -> egui::Response {
+85
View File
@@ -0,0 +1,85 @@
use eframe::egui::{self, Rect, Vec2};
use super::combobox_stil::{draw_gradient_bg, DROPDOWN_TEXT_COLOR};
/// Stil uygulanmış ComboBox oluşturur (genişlik ayarı yapılmaz, Grid içinde kullanım için).
pub fn combo_box(
id_source: impl std::hash::Hash,
selected_text: impl Into<egui::WidgetText>,
) -> egui::ComboBox {
egui::ComboBox::from_id_source(id_source).selected_text(selected_text)
}
/// Stil uygulanmış ComboBox oluşturur ve genişlik verir.
pub fn combo_box_with_width(
id_source: impl std::hash::Hash,
selected_text: impl Into<egui::WidgetText>,
width: f32,
) -> egui::ComboBox {
combo_box(id_source, selected_text).width(width)
}
/// ComboBox dropdown'ını açar, genişlik kısıtlaması uygular ve içeriği çizer.
pub fn show_combo_box<R>(
ui: &mut egui::Ui,
id_source: impl std::hash::Hash,
selected_text: impl Into<egui::WidgetText>,
width: f32,
add_contents: impl FnOnce(&mut egui::Ui, f32) -> R,
) -> Option<R> {
combo_box_with_width(id_source, selected_text, width)
.show_ui(ui, |ui| {
ui.set_min_width(width);
ui.set_max_width(width);
add_contents(ui, width)
})
.inner
}
/// ComboBox dropdown öğesi — gradient arkaplan, tam genişlik tıklanabilir, pointing hand imleci.
pub fn selectable_value<T: PartialEq>(
ui: &mut egui::Ui,
current: &mut T,
value: T,
label: &str,
width: f32,
) -> egui::Response {
let item_height = 28.0;
let (rect, response) = ui.allocate_exact_size(Vec2::new(width, item_height), egui::Sense::click());
draw_gradient_bg(ui, rect);
ui.put(rect, |ui: &mut egui::Ui| {
ui.horizontal(|ui| {
ui.add_space(6.0);
ui.colored_label(DROPDOWN_TEXT_COLOR, label);
})
.response
});
if response.clicked() {
*current = value;
}
response.on_hover_cursor(egui::CursorIcon::PointingHand)
}
/// ComboBox dropdown öğesi — özel içerik çizmek için (ör. bayrak + metin).
/// `add_contents` callback'i verilen `Rect` içine içerik ekler.
pub fn selectable_value_custom<R>(
ui: &mut egui::Ui,
width: f32,
add_contents: impl FnOnce(&mut egui::Ui, Rect) -> R,
) -> egui::Response {
let item_height = 28.0;
let (rect, mut response) = ui.allocate_exact_size(Vec2::new(width, item_height), egui::Sense::click());
draw_gradient_bg(ui, rect);
add_contents(ui, rect);
if response.clicked() {
response.mark_changed();
}
response.on_hover_cursor(egui::CursorIcon::PointingHand)
}
+65
View File
@@ -0,0 +1,65 @@
use eframe::egui::{self, Color32, Rect, Stroke};
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.
pub fn apply_visuals(v: &mut egui::Visuals) {
v.widgets.inactive.weak_bg_fill = WEAK_BG_INACTIVE;
v.widgets.hovered.weak_bg_fill = WEAK_BG_HOVERED;
v.widgets.active.weak_bg_fill = WEAK_BG_ACTIVE;
v.widgets.inactive.fg_stroke = Stroke::new(1.0, COMBO_TEXT_COLOR);
v.widgets.hovered.fg_stroke = Stroke::new(1.5, Color32::WHITE);
v.widgets.active.fg_stroke = Stroke::new(2.0, Color32::WHITE);
// Kenarlıklar
v.widgets.inactive.bg_stroke = Stroke::new(1.0, COMBO_BORDER_ACCENT);
v.widgets.hovered.bg_stroke = Stroke::new(1.0, COMBO_BORDER_ACCENT);
v.widgets.active.bg_stroke = Stroke::new(1.0, COMBO_BORDER_ACCENT);
}
/// Açık gri gradyan mesh arkaplanı çizer (dropdown öğeleri için).
pub fn draw_gradient_bg(ui: &mut egui::Ui, rect: Rect) {
let mut mesh = Mesh::default();
let idx = mesh.vertices.len() as u32;
mesh.vertices.push(egui::epaint::Vertex {
pos: egui::pos2(rect.left(), rect.top()),
uv: egui::pos2(0.0, 0.0),
color: GRADIENT_TOP,
});
mesh.vertices.push(egui::epaint::Vertex {
pos: egui::pos2(rect.right(), rect.top()),
uv: egui::pos2(0.0, 0.0),
color: GRADIENT_TOP,
});
mesh.vertices.push(egui::epaint::Vertex {
pos: egui::pos2(rect.right(), rect.bottom()),
uv: egui::pos2(0.0, 0.0),
color: GRADIENT_BOT,
});
mesh.vertices.push(egui::epaint::Vertex {
pos: egui::pos2(rect.left(), rect.bottom()),
uv: egui::pos2(0.0, 0.0),
color: GRADIENT_BOT,
});
mesh.indices.extend_from_slice(&[idx, idx + 1, idx + 2, idx + 2, idx + 3, idx]);
ui.painter().add(egui::Shape::mesh(mesh));
}
+334
View File
@@ -0,0 +1,334 @@
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,
}
/// ISO105 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 ISO105 düzeni. x değerleri kümülatif.
match name {
// ── Function row ────────────────
"ESC" => Some((0.0, r(0), 1.0, 1.0)),
"FK01" => Some((2.0, r(0), 1.0, 1.0)),
"FK02" => Some((3.0, r(0), 1.0, 1.0)),
"FK03" => Some((4.0, r(0), 1.0, 1.0)),
"FK04" => Some((5.0, r(0), 1.0, 1.0)),
"FK05" => Some((6.0, r(0), 1.0, 1.0)),
"FK06" => Some((7.0, r(0), 1.0, 1.0)),
"FK07" => Some((8.0, r(0), 1.0, 1.0)),
"FK08" => Some((9.0, r(0), 1.0, 1.0)),
"FK09" => Some((10.0, r(0), 1.0, 1.0)),
"FK10" => Some((11.0, r(0), 1.0, 1.0)),
"FK11" => Some((12.0, r(0), 1.0, 1.0)),
"FK12" => Some((13.0, r(0), 1.0, 1.0)),
"PRSC" => Some((15.0, r(0), 1.0, 1.0)),
"SCLK" => Some((16.0, r(0), 1.0, 1.0)),
"PAUS" => Some((17.0, r(0), 1.0, 1.0)),
// ── Number row ─────────────────
"TLDE" => Some((0.0, r(1), 1.0, 1.0)),
"AE01" => Some((1.0, r(1), 1.0, 1.0)),
"AE02" => Some((2.0, r(1), 1.0, 1.0)),
"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 (QP) ────────
"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((14.0, r(2), 1.5, 1.0)), // ISO: solundaki tuş (\|)
"RTRN" => Some((14.0, r(2), 1.5, 2.0)), // ISO Enter (2 satır)
// ── Home row (A–L) ────────────
"CAPS" => Some((0.0, r(3), 1.75, 1.0)),
"AC01" => Some((1.75, r(3), 1.0, 1.0)),
"AC02" => Some((2.75, r(3), 1.0, 1.0)),
"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 (ZM) ──────────
"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)),
"AB11" => Some((12.25,r(4), 1.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)),
"LWIN" => Some((1.25, r(5), 1.25, 1.0)),
"LALT" => Some((2.5, r(5), 1.5, 1.0)),
"SPCE" => Some((4.0, r(5), 6.25, 1.0)),
"RALT" | "LVL3" => Some((10.25,r(5), 1.5, 1.0)),
"RWIN" => Some((11.75,r(5), 1.25, 1.0)),
"COMP" => Some((13.0, r(5), 1.25, 1.0)),
"RCTL" => Some((14.25,r(5), 1.25, 1.0)),
// ── Nav cluster ───────────────
"INS" => Some((15.5, r(2), 1.0, 1.0)),
"HOME" => Some((16.5, r(2), 1.0, 1.0)),
"PGUP" => Some((17.5, r(2), 1.0, 1.0)),
"DELE" => Some((15.5, r(3), 1.0, 1.0)),
"END" => Some((16.5, r(3), 1.0, 1.0)),
"PGDN" => Some((17.5, r(3), 1.0, 1.0)),
"UP" => Some((16.5, r(4), 1.0, 1.0)),
"LEFT" => Some((15.5, r(5), 1.0, 1.0)),
"DOWN" => Some((16.5, r(5), 1.0, 1.0)),
"RGHT" => Some((17.5, r(5), 1.0, 1.0)),
// ── Numpad ────────────────────
"NMLK" => Some((19.0, r(2), 1.0, 1.0)),
"KSLU" => Some((20.0, r(2), 1.0, 1.0)), // /
"KAST" => Some((21.0, r(2), 1.0, 1.0)), // *
"KMIN" => Some((22.0, r(2), 1.0, 1.0)), // -
"KP7" => Some((19.0, r(3), 1.0, 1.0)),
"KP8" => Some((20.0, r(3), 1.0, 1.0)),
"KP9" => Some((21.0, r(3), 1.0, 1.0)),
"KPLU" => Some((22.0, r(3), 1.0, 2.0)), // +
"KP4" => Some((19.0, r(4), 1.0, 1.0)),
"KP5" => Some((20.0, r(4), 1.0, 1.0)),
"KP6" => Some((21.0, r(4), 1.0, 1.0)),
"KP1" => Some((19.0, r(5), 1.0, 1.0)),
"KP2" => Some((20.0, r(5), 1.0, 1.0)),
"KP3" => Some((21.0, r(5), 1.0, 1.0)),
"KP0" => Some((19.0, r(6), 2.0, 1.0)),
"KPDL" => Some((21.0, r(6), 1.0, 1.0)), // .
"KPEN" => Some((22.0, r(5), 1.0, 2.0)), // Enter
"K7" => Some((19.0, r(3), 1.0, 1.0)), // old alias
"K8" => Some((20.0, r(3), 1.0, 1.0)),
"K9" => Some((21.0, r(3), 1.0, 1.0)),
"K4" => Some((19.0, r(4), 1.0, 1.0)),
"K5" => Some((20.0, r(4), 1.0, 1.0)),
"K6" => Some((21.0, r(4), 1.0, 1.0)),
"K1" => Some((19.0, r(5), 1.0, 1.0)),
"K2" => Some((20.0, r(5), 1.0, 1.0)),
"K3" => Some((21.0, r(5), 1.0, 1.0)),
"K0" => Some((19.0, r(6), 2.0, 1.0)),
"KDOT" => Some((21.0, r(6), 1.0, 1.0)),
"KENT" => Some((22.0, r(5), 1.0, 2.0)),
_ => None,
}
}
fn keysym_to_label(sym: xkb::Keysym) -> String {
let utf = xkb::keysym_to_utf8(sym);
if !utf.is_empty() { return utf; }
let name = xkb::keysym_get_name(sym);
if name == "VoidSymbol" || name == "NoSymbol" {
return String::new();
}
// Yaygın XKB sembol adlarını kısa etiketlere çevir
match name.as_str() {
"BackSpace" => "".into(),
"Tab" => "".into(),
"Return" => "".into(),
"Caps_Lock" => "".into(),
"Shift_L" => "".into(),
"Shift_R" => "".into(),
"Control_L" => "Ctrl".into(),
"Control_R" => "Ctrl".into(),
"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 context = xkb::Context::new(xkb::CONTEXT_NO_FLAGS);
let model = if std::path::Path::new("/usr/share/X11/xkb/symbols/").exists() {
"pc105"
} else {
"pc105"
};
let 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;
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 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() }]
}
};
keys.push(XkbKey { x, y, w, h, labels });
if x < min_x { min_x = x; }
if y < min_y { min_y = y; }
if x + w > max_x { max_x = x + w; }
if y + h > max_y { max_y = y + h; }
}
// normalize x,y — tüm tuşlar (0,0) bazlı olsun
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 = visuals.text_color();
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.3, font_size),
1 => (kx + kw * 0.5, ky + kh * 0.65, small_size),
_ => (kx + kw * 0.5, ky + kh * 0.9, small_size),
};
painter.text(
egui::pos2(lx, ly), egui::Align2::CENTER_CENTER,
lbl, egui::FontId::proportional(sz), text_color,
);
}
}
}
+3
View File
@@ -1,5 +1,8 @@
pub mod theme; pub mod theme;
pub mod buttons; pub mod buttons;
pub mod combobox;
pub mod combobox_stil;
pub mod kle_svg;
pub mod slideshow; pub mod slideshow;
pub mod error_screen; pub mod error_screen;
+14 -64
View File
@@ -63,6 +63,7 @@ pub fn set_theme_mode(is_dark: bool) {
TEXT.store(0x000000, Ordering::Relaxed); TEXT.store(0x000000, Ordering::Relaxed);
TEXT_DIM.store(0x000000, Ordering::Relaxed); TEXT_DIM.store(0x000000, Ordering::Relaxed);
SIDEBAR_TEXT.store(0xffffff, Ordering::Relaxed); SIDEBAR_TEXT.store(0xffffff, 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);
@@ -92,8 +93,8 @@ pub fn c_accent_dim() -> Color32 {
/// `cc.egui_ctx` üzerinden `setup_custom_fonts` ve `set_visuals` çağrısını birleştirir. /// `cc.egui_ctx` üzerinden `setup_custom_fonts` ve `set_visuals` çağrısını birleştirir.
pub fn apply(ctx: &egui::Context, is_dark: bool) { pub fn apply(ctx: &egui::Context, is_dark: bool) {
setup_fonts(ctx); setup_fonts(ctx);
ctx.set_visuals(build_visuals(is_dark));
ctx.set_style(build_style()); ctx.set_style(build_style());
ctx.set_visuals(build_visuals(is_dark));
} }
fn setup_fonts(ctx: &egui::Context) { fn setup_fonts(ctx: &egui::Context) {
@@ -159,56 +160,7 @@ fn setup_fonts(ctx: &egui::Context) {
ctx.set_fonts(fonts); ctx.set_fonts(fonts);
} }
// fn build_visuals(is_dark: bool) -> Visuals {
// let mut v = if is_dark { Visuals::dark() } else { Visuals::light() };
// // Genel arka planlar
// v.window_fill = c_bg_dark();
// v.panel_fill = c_bg_panel();
// v.faint_bg_color = c_bg_widget();
// v.extreme_bg_color = c_sidebar();
// v.code_bg_color = if is_dark { Color32::from_rgb(0x18, 0x18, 0x28) } else { Color32::from_rgb(0xEE, 0xEE, 0xF5) };
// // Metin
// v.override_text_color = None;
// // Kenarlıklar
// v.widgets.noninteractive.bg_stroke = Stroke::new(1.0, c_border());
// v.widgets.inactive.bg_stroke = Stroke::new(1.0, c_border());
// v.widgets.hovered.bg_stroke = Stroke::new(1.5, c_accent());
// v.widgets.active.bg_stroke = Stroke::new(2.0, c_accent());
// // Widget dolguları
// v.widgets.noninteractive.bg_fill = c_bg_widget();
// v.widgets.inactive.bg_fill = c_bg_widget();
// v.widgets.hovered.bg_fill = if is_dark { Color32::from_rgb(0x35, 0x35, 0x55) } else { Color32::from_rgb(0xD5, 0xD5, 0xE5) };
// v.widgets.active.bg_fill = c_accent_dim();
// // Metin renkleri
// v.widgets.noninteractive.fg_stroke = Stroke::new(1.0, c_text());
// v.widgets.inactive.fg_stroke = Stroke::new(1.0, c_text());
// v.widgets.hovered.fg_stroke = Stroke::new(1.5, c_text());
// v.widgets.active.fg_stroke = Stroke::new(2.0, Color32::WHITE);
// // Yuvarlak köşeler
// v.widgets.noninteractive.rounding = Rounding::same(6.0);
// v.widgets.inactive.rounding = Rounding::same(6.0);
// v.widgets.hovered.rounding = Rounding::same(6.0);
// v.widgets.active.rounding = Rounding::same(6.0);
// // Seçim rengi
// v.selection.bg_fill = c_accent_dim();
// v.selection.stroke = Stroke::new(1.0, c_accent());
// // Hyperlink
// v.hyperlink_color = c_accent();
// // Pencere kenarlığı
// v.window_stroke = Stroke::new(1.0, c_border());
// v.window_rounding = Rounding::same(8.0);
// v
// }
fn build_visuals(is_dark: bool) -> Visuals { fn build_visuals(is_dark: bool) -> Visuals {
let mut v = if is_dark { Visuals::dark() } else { Visuals::light() }; let mut v = if is_dark { Visuals::dark() } else { Visuals::light() };
@@ -225,27 +177,24 @@ fn build_visuals(is_dark: bool) -> Visuals {
// ─── KENARLIKLAR (Calamares Uyumu için İnce İnce Yumuşatıldı) ─────────────────── // ─── KENARLIKLAR (Calamares Uyumu için İnce İnce Yumuşatıldı) ───────────────────
v.widgets.noninteractive.bg_stroke = Stroke::new(1.0, c_border()); v.widgets.noninteractive.bg_stroke = Stroke::new(1.0, c_border());
v.widgets.inactive.bg_stroke = Stroke::new(1.0, Color32::from_rgba_unmultiplied(255, 255, 255, 30)); // Buton etrafında hafif saydam parlaklık
v.widgets.hovered.bg_stroke = Stroke::new(1.5, c_accent());
v.widgets.active.bg_stroke = Stroke::new(2.0, c_accent());
// ─── WIDGET DOLGULARI (Burası ComboBox ve Butonların Kaderini Belirliyor) ─────── // ─── WIDGET DOLGULARI (Burası ComboBox ve Butonların Kaderini Belirliyor) ───────
v.widgets.noninteractive.bg_fill = c_bg_widget(); v.widgets.noninteractive.bg_fill = c_bg_widget();
// Pasif Hali: Calamares Mavisi (İstiyorsan c_accent() ile Pisi Vişne Rengine de Çekebilirsin) // Pasif Hali: Widget arka planı (TextEdit, ComboBox giriş alanı)
v.widgets.inactive.bg_fill = Color32::from_rgb(46, 124, 219); v.widgets.inactive.bg_fill = Color32::from_rgb(255, 255, 255);
// Üzerine Gelme Hali: Parlayan Açık Mavi // Üzerine Gelme Hali: Hafif açık
v.widgets.hovered.bg_fill = Color32::from_rgb(59, 144, 240); v.widgets.hovered.bg_fill = Color32::from_rgb(55, 58, 68);
// Tıklanma/Açık Kalma Hali: Koyu Lacivert/Mavi // Tıklanma/Açık Kalma Hali: Daha açık
v.widgets.active.bg_fill = Color32::from_rgb(20, 70, 140); v.widgets.active.bg_fill = Color32::WHITE;
// ComboBox stilleri (combobox_stil.rs)
crate::ui::combobox_stil::apply_visuals(&mut v);
// ─── METİN RENKLERİ ──────────────────────────────────────────────────────────── // ─── METİN RENKLERİ ────────────────────────────────────────────────────────────
v.widgets.noninteractive.fg_stroke = Stroke::new(1.0, c_text()); v.widgets.noninteractive.fg_stroke = Stroke::new(1.0, c_text());
v.widgets.inactive.fg_stroke = Stroke::new(1.0, Color32::WHITE); // Mavi üstüne beyaz metin okunaklı kılar
v.widgets.hovered.fg_stroke = Stroke::new(1.5, Color32::WHITE);
v.widgets.active.fg_stroke = Stroke::new(2.0, Color32::WHITE);
// ─── YUVARLAK KÖŞELER (Zaten 6.0'dı, Tam İstediğin Calamares Kıvamında) ───────── // ─── YUVARLAK KÖŞELER (Zaten 6.0'dı, Tam İstediğin Calamares Kıvamında) ─────────
v.widgets.noninteractive.rounding = Rounding::same(6.0); v.widgets.noninteractive.rounding = Rounding::same(6.0);
@@ -264,6 +213,8 @@ fn build_visuals(is_dark: bool) -> Visuals {
v.window_stroke = Stroke::new(1.0, c_border()); v.window_stroke = Stroke::new(1.0, c_border());
v.window_rounding = Rounding::same(8.0); v.window_rounding = Rounding::same(8.0);
v.interact_cursor = Some(egui::CursorIcon::PointingHand);
v v
} }
@@ -286,14 +237,13 @@ fn build_style() -> egui::Style {
] ]
.into(); .into();
style.visuals.interact_cursor = Some(egui::CursorIcon::PointingHand);
style style
} }
// ─── Yardımcı widget'lar ───────────────────────────────────────── // ─── Yardımcı widget'lar ─────────────────────────────────────────
pub use super::buttons::*; pub use super::buttons::*;
pub use super::combobox::*;
/// Bölüm başlığı — büyük, açık renkli, alt çizgisiz. /// Bölüm başlığı — büyük, açık renkli, alt çizgisiz.
pub fn section_heading(ui: &mut egui::Ui, text: &str) { pub fn section_heading(ui: &mut egui::Ui, text: &str) {