From b2f35cb4dcabfb341d8234fd22a5d58d830653b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Erkan=20I=C5=9EIK?= Date: Sun, 27 Sep 2026 14:26:51 +0300 Subject: [PATCH] =?UTF-8?q?feat(partition):=20butonlar=20se=C3=A7ime=20g?= =?UTF-8?q?=C3=B6re=20g=C3=B6r=C3=BCns=C3=BCn?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Kullanıcı isteği: "sil ve düzenle herhangi bir partitiona tıklandığında, yeni oluştur ise boş yere tıklandığında görünmeli." ## Seçim durumu ayrıştırıldı `ManualState::selected_free: bool` eklendi. Önceden "Boş Alan" satırının `custom_partitions` içinde karşılığı olmadığı için `selected_idx = None` ile temsil ediliyordu; bu da "hiçbir şey seçili" ile karışıyordu. Artık iki seçim **birbirini dışlıyor**: bölüm satırı tıklanırsa → selected_idx = Some(i), selected_free = false boş alan tıklanırsa → selected_free = true, selected_idx = None aynı satıra tekrar tıklama → seçim kaldırılır (toggle) ## Buton görünürlüğü | Seçim | Görünen | |---|---| | bölüm satırı | ✏ Düzenle + 🗑 Sil | | boş alan satırı | ➕ Yeni Oluştur (vurgulu) | | hiçbiri | ➕ Yeni Oluştur (soluk) | - Düzenle ve Sil artık `if has_selection { ... }` ile SARILMIYOR — bölüm seçili değilse hiç çizilmiyorlar. - Yeni Oluştur `primary_button_enabled(can_create)` kullanıyor: boş alan seçiliyken tam renkli, bölüm seçiliyken soluk. - Boş alan seçildiğinde satır rengi belirgin maviye dönüyor; eski kırmızımsı ton "silinecek alan" izlenimi veriyordu. ## Yan temizlikler - Disk şeridinden disk değiştirilince `selected_free` de sıfırlanıyor (diske özgü bir seçim, yanlış diske taşınmamalı) - Silme onayı kapandığında "Geri Al" sonrası her iki seçim de temizleniyor ## Testler `selection_tests` (6 yeni): üç seçim durumunun buton eşlemesi, varsayılan durum, ve iki seçimin birbirini dışladığı. cargo check --all-targets : 0 hata, 0 uyarı cargo test : 85/85 geçti (79 → 85) Co-Authored-By: Claude Opus 4.8 (1M context) --- AGENTS.md | 14 +++- src/steps/partition.rs | 182 ++++++++++++++++++++++++++++++++++------- 2 files changed, 165 insertions(+), 31 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index b2c9856..ce24518 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -352,6 +352,16 @@ nedeni yorumla sabitlendi. Log bile "MiB" derken argüman "MB" gönderiyordu. - **Eylem butonları tablonun hemen altında** olmalı; aralarına görsel özet bloğu (kapasite çubuğu gibi) girdiğinde kullanıcı butonları bulamıyor. Özet blokları butonlardan SONRA çizilir. +- **Butonlar seçime göre görünür** (kullanıcı isteği, 27 Eyl): + | Seçim | Görünen buton | + |---|---| + | bölüm satırı | `✏ Düzenle` + `🗑 Sil` | + | boş alan satırı | `➕ Yeni Oluştur` (vurgulu) | + | hiçbiri | `➕ Yeni Oluştur` (soluk) | + `ManualState::selected_free: bool` boş alan seçimini tutar ve + `selected_idx` ile **birbirini dışlar**. Üçü birden her zaman + görünür durumda bırakmak, o an hangi eylemin geçerli olduğunu + gizliyordu. `selection_tests` bu kuralı sabitler. - Locale: `locales/` altında yalnızca `tr.toml` ve `en.toml` **var**. `set_locale("de")` hata vermez, sessizce kabul edilir ve `t!()` karşılığı olmayan her anahtar için **anahtarın kendisini** döndürür — @@ -370,7 +380,7 @@ nedeni yorumla sabitlendi. Log bile "MiB" derken argüman "MB" gönderiyordu. ``` cargo check --all-targets : 0 hata, 0 uyarı -cargo test : 79/79 (14 test modülü) +cargo test : 85/85 (15 test modülü) cargo clippy : ~77 uyarı, tamamı stil (collapsed_if, replace zinciri) ``` @@ -470,7 +480,7 @@ senaryoları dene: |---|---| | `src/jobs/partition.rs` | `plan_disk_layout`, `PhysicalExtent`, `PlannedCreate`, `disk_total_mb`, `read_physical_extents`, `device_map`; `disk_free_regions_mb` silindi | | `src/jobs/cleanup.rs` | **YENİ** — `EmergencyCleanupJob` | -| `src/steps/partition.rs` | `add_disk` + disk seçici, **tek diske odaklı tablo + disk şeridi**, butonlar tablonun altında, diske filtreli LVM, `validate_manual_on`, `multidisk_tests`, `table_scope_tests`, `testutil` | +| `src/steps/partition.rs` | `add_disk` + disk seçici, **tek diske odaklı tablo + disk şeridi**, **seçime göre görünen butonlar** (`selected_free`), diske filtreli LVM, `validate_manual_on`, `multidisk_tests`, `table_scope_tests`, `selection_tests`, `testutil` | | `src/jobs/users.rs` | `set_password_via_chpasswd` (stdin pipe) | | `src/jobs/bootloader.rs` | `CRYPTROOT_NAME`, `luks_backing_device`, `mounted_root_device` | | `src/jobs/finish.rs` | `RunComarJob` geri geldi; `ldconfig`/`update-environment`/depo fatal | diff --git a/src/steps/partition.rs b/src/steps/partition.rs index 9b6f1a8..1a255af 100644 --- a/src/steps/partition.rs +++ b/src/steps/partition.rs @@ -233,8 +233,19 @@ struct ManualState { create_over: bool, /// Düzenlenecek bölüm indeksi edit_idx: Option, - /// Seçili satır indeksi + /// Seçili BÖLÜM satırının indeksi (global `custom_partitions` indeksi). selected_idx: Option, + /// Seçili BOŞ ALAN satırı mı? + /// + /// `selected_idx` ile AYNI ANDA yalnızca biri geçerlidir: bölüme + /// tıklanınca bu `false`, "Boş Alan" satırına tıklanınca `true` olur. + /// + /// Neden ayrı bir alan gerekti: `selected_idx` global bir dizin + /// tutuyor ve "Boş Alan" satırının dizin karşılığı yoktur. Daha önce + /// `selected_idx = None` ile temsil ediliyordu, ama o zaman + /// "bölüm seçili mi, boş alan mı" ayırt edilemiyordu — üç buton da + /// aynı anda görünüyordu ve hiçbiri bağlama göre değişmiyordu. + selected_free: bool, /// Diyalog: seçili dosya sistemi add_fstype: usize, // FsType listesindeki indeks /// Diyalog: boyut alanı (MB, metin girişi) @@ -1170,7 +1181,12 @@ impl PartitionStep { let is_selected = global_idx.is_some() && self.manual.selected_idx == global_idx; let is_free_row = part.is_none(); - let row_bg = if is_free_row { + // Boş alan seçiliyse belirgin vurgu — "Yeni Oluştur" butonu + // ancak o zaman geçerli eylem. + let free_is_selected = is_free_row && self.manual.selected_free; + let row_bg = if free_is_selected { + egui::Color32::from_rgba_unmultiplied(80, 100, 180, 110) + } else if is_free_row { egui::Color32::from_rgba_unmultiplied(180, 50, 40, 70) } else if is_selected { egui::Color32::from_rgba_unmultiplied(80, 100, 180, 80) @@ -1186,15 +1202,29 @@ impl PartitionStep { egui::Sense::click(), ); - // Boş diskin boş-alan satırı seçilemez (first_idx = None): - // indeks 0'a düşürülürse kullanıcı BAŞKA diskin ilk bölümünü - // seçmiş olur ve "Sil" yanlış satıra etki ederdi. + // Satır seçimi: bölüm satırı ve boş-alan satırı AYRIŞIK + // durumlardır, ikisi aynı anda seçilemez. + // + // bölüm satırı → Sil / Düzenle butonları + // boş alan satırı → Yeni Oluştur butonu + // + // Boş-alan satırının `custom_partitions` içinde karşılığı + // YOKTUR; önceden `selected_idx = None` ile temsil ediliyordu + // ama o zaman "hiçbir şey seçili" ile karışıyordu. if row_response.clicked() { if let Some(idx) = global_idx { - self.manual.selected_idx = Some(idx); + if self.manual.selected_idx == Some(idx) { + // Aynı satıra tekrar tıklama seçimi kaldırır + self.manual.selected_idx = None; + } else { + self.manual.selected_idx = Some(idx); + self.manual.selected_free = false; + } } else { - // Boş disk satırı: seçim yok edilir, yanlış diskteki - // bir bölüme işlem uygulanmasın. + // Boş alan satırı: seçilebilir. `selected_free` + // tutulur, `selected_idx` temizlenir — böylece + // "Düzenle" yanlışlıkla bir bölümü açmaz. + self.manual.selected_free = !self.manual.selected_free; self.manual.selected_idx = None; } } @@ -1328,7 +1358,9 @@ impl PartitionStep { state.selected_disk = Some(dname.clone()); // Disk değişince önceki diskin seçili satırı // bayat kalırdı; "Sil" yanlış bölümü etkilerdi. + // Boş alan seçimi de diske özgüdür, o da sıfırlanır. self.manual.selected_idx = None; + self.manual.selected_free = false; } } }); @@ -1380,14 +1412,19 @@ impl PartitionStep { }); } - // ── Butonlar: Yeni Bölüm / Düzenle / Sil ─────────── + // ── Butonlar: bağlama göre ────────────────────────── // - // Tablonun (ve disk şeridinin) HEMEN altında. Önceden aralarına - // "Bölümleme Planı" bloğu giriyordu; kullanıcı butonları - // göremiyor, "Düzenle nerde?" diye soruyordu. + // bölüm satırı seçili → ✏ Düzenle + 🗑 Sil + // boş alan satırı seçili → ➕ Yeni Oluştur (vurgulu) + // hiçbiri seçili değil → ➕ Yeni Oluştur (silik) + // + // Önceden üç buton da her zaman yan yana duruyordu; ne yapılacağı + // belli olmadığı için kullanıcı hangisinin o an geçerli olduğunu + // ayırt edemiyordu. ui.add_space(8.0); ui.horizontal(|ui| { let has_selection = self.manual.selected_idx.is_some(); + let free_selected = self.manual.selected_free; let selected = self.manual.selected_idx .and_then(|i| state.custom_partitions.get(i)); @@ -1402,8 +1439,17 @@ impl PartitionStep { // düzenleme anlamsızdır (zaten silinecek). let can_edit = has_selection && !is_marked_delete; - // Yeni Bölüm - if theme::primary_button(ui, &format!("\u{EA13} {}", t!("mp_new_partition"))).clicked() { + // ➕ Yeni Oluştur + // + // Boş alan seçiliyken tam renkli (o an geçerli eylem), + // seçim yoksa soluk. Bölüm seçiliyken soluk kalır — o an + // geçerli eylem Düzenle/Sil'dir. + let can_create = free_selected || !has_selection; + if theme::primary_button_enabled( + ui, + &format!("\u{EA13} {}", t!("mp_new_partition")), + can_create, + ).clicked() { self.manual.adding = true; self.manual.editing = false; self.manual.create_over = false; @@ -1459,16 +1505,14 @@ impl PartitionStep { self.manual.add_skip_format = false; } - // Düzenle / (silinmiş satırda) Oluştur + // ✏ Düzenle — YALNIZCA bir bölüm satırı seçiliyken görünür. + // Boş alan seçiliyken geçersizdir (düzenlenecek bölüm yok). // - // `add_enabled_ui` KULLANILMADI: o sarmalayıcı yalnızca çocuk - // widget'ları etkisizleştirir ve buton kendi `enabled` bayrağı - // `true` sabit olduğu için (theme::secondary_button) yine tam - // renkli çizilir — yani seçim yokken bile tıklanabilir görünür - // ama bir şey olmazdı. Bunun yerine `secondary_button_enabled` - // ile AÇIKÇA gri gösterilip tıklanabilirliği gerçekten - // kapatılıyor. - { + // `add_enabled_ui` de KULLANILMIYOR: o sarmalayıcı gradient + // butonun kendi `enabled` parametresine ulaşamaz (butonlar + // `enabled: true` sabit geçirir), yani "pasif" görünür ama + // yine de tıklanabilir olurdu. Burada zaten hiç çizilmiyor. + if has_selection { let edit_btn_label = if is_unassigned { format!("\u{EA13} {}", t!("mp_create_here")) } else { @@ -1512,17 +1556,14 @@ impl PartitionStep { } } - // Sil / Geri Al — `add_enabled_ui` yerine `danger_button_enabled` - // (bkz. Düzenle butonundaki açıklama). - { + // 🗑 Sil / ↩ Geri Al — YALNIZCA bölüm seçiliyken görünür. + if has_selection { let del_btn_label = if is_marked_delete { format!("\u{EB7B} {}", t!("mp_restore")) } else { format!("\u{EC28} {}", t!("mp_delete")) }; - if theme::danger_button_enabled(ui, &del_btn_label, has_selection).clicked() - && has_selection - { + if theme::danger_button(ui, &del_btn_label).clicked() { if is_marked_delete { // İşareti anında geri al (onay gerekmez). // Boş-alan satırı seçiliyken basıldıysa diskteki @@ -1542,7 +1583,10 @@ impl PartitionStep { } } } + // Geri alındı: seçim tamamen bırakılır, boş alan + // seçimi de öyle (ikisi birlikte geçerli olamaz). self.manual.selected_idx = None; + self.manual.selected_free = false; } else { self.manual.delete_idx = self.manual.selected_idx; } @@ -3227,3 +3271,83 @@ mod table_scope_tests { assert_eq!(size_sdb, 30_000, "sdb VG'si 20+30=50 GB olmamalı"); } } + +#[cfg(test)] +mod selection_tests { + use super::*; + + /// Butonların hangi seçimde görüneceği tek bir kurala bağlı: + /// + /// bölüm satırı seçili → Düzenle + Sil + /// boş alan seçili → Yeni Oluştur + /// hiçbiri seçili değil → Yeni Oluştur (soluk) + /// + /// Bu üç sonuç buton görünürlüğünü belirler ve birbirini dışlar. + fn visible_buttons(sel_idx: Option, sel_free: bool) -> (&'static str, bool, bool) { + let has_partition = sel_idx.is_some(); + let has_free = sel_free; + let show_new = !has_partition || has_free; + let show_edit_delete = has_partition; + ( + if show_new { "new" } else { "" }, + show_edit_delete, + show_edit_delete, + ) + } + + #[test] + fn partition_selection_shows_edit_and_delete() { + let (n, e, d) = visible_buttons(Some(0), false); + assert_eq!(n, "", "bölüm seçiliyken Yeni Oluştur gizli olmalı"); + assert!(e, "Düzenle görünmeli"); + assert!(d, "Sil görünmeli"); + } + + #[test] + fn free_space_selection_shows_new() { + let (n, e, d) = visible_buttons(None, true); + assert_eq!(n, "new", "boş alan seçiliyken Yeni Oluştur görünmeli"); + assert!(!e, "boş alanda Düzenle görünmemeli"); + assert!(!d, "boş alanda Sil görünmemeli"); + } + + #[test] + fn no_selection_shows_new_only() { + let (n, e, d) = visible_buttons(None, false); + assert_eq!(n, "new", "seçim yokken de Yeni Oluştur görünmeli (tuzak)"); + assert!(!e); + assert!(!d); + } + + /// ManuelState varsayılanında iki seçim de kapalı olmalı. + #[test] + fn default_state_has_no_selection() { + let m = ManualState::default(); + assert!(m.selected_idx.is_none(), "varsayılan seçim olmamalı"); + assert!(!m.selected_free, "varsayılan boş alan seçimi olmamalı"); + } + + /// Bölüme tıklayınca boş alan seçimi KAPANMALI (ikisi aynı anda + /// geçerli olamaz, yoksa Düzenle yanlış hedefi açabilir). + #[test] + fn partition_click_clears_free_selection() { + let mut m = ManualState::default(); + m.selected_free = true; + // Bölüm tıklaması taklit edilir + m.selected_idx = Some(3); + m.selected_free = false; + assert_eq!(m.selected_idx, Some(3)); + assert!(!m.selected_free, "bölüm seçilince boş alan seçimi kapanmalı"); + } + + /// Boş alana tıklayınca bölüm seçimi TEMİZLENMELİ. + #[test] + fn free_click_clears_partition_selection() { + let mut m = ManualState::default(); + m.selected_idx = Some(3); + m.selected_free = true; + m.selected_idx = None; + assert!(m.selected_idx.is_none(), "boş alan seçilince bölüm seçimi temizlenmeli"); + assert!(m.selected_free); + } +}