feat(partition): butonlar seçime göre görünsün

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) <noreply@anthropic.com>
This commit is contained in:
Erkan IŞIK
2026-09-27 14:26:51 +03:00
parent 618bcbc023
commit b2f35cb4dc
2 changed files with 165 additions and 31 deletions
+12 -2
View File
@@ -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 |
+153 -29
View File
@@ -233,8 +233,19 @@ struct ManualState {
create_over: bool,
/// Düzenlenecek bölüm indeksi
edit_idx: Option<usize>,
/// Seçili satır indeksi
/// Seçili BÖLÜM satırının indeksi (global `custom_partitions` indeksi).
selected_idx: Option<usize>,
/// 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<usize>, 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);
}
}