diff --git a/CHANGELOG.md b/CHANGELOG.md index 6261196..13f8dd6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -43,6 +43,19 @@ --- +## [4.0.2] — 2026-06-21 + +### Changed +- Sürgü maksimum değeri artık diskteki kullanılabilir alan kadar (diğer bölümler hariç) +- Düzenleme modunda seçili bölümün kapasitesi sürgü aralığına dahil edildi +- `form_input_text` boyut alanı doğrudan `add_size_str`'e bağlandı, yanında formatlı MB gösterimi eklendi +- Boyut etiketi artık `"123 MB"` yerine `"123.456 MB"` formatında (parse edilebilen değerlerde) + +### Fixed +- Boyut input alanına yazılan değer `add_size_str`'e geri yazılmıyordu — text edit salt okunur kalıyordu + +--- + ## [4.0.1] — 2026-06-19 ### Added diff --git a/assets/icons/fail-icon.svg b/assets/icons/fail-icon.svg new file mode 100644 index 0000000..3e337e6 --- /dev/null +++ b/assets/icons/fail-icon.svg @@ -0,0 +1 @@ +cross \ No newline at end of file diff --git a/assets/icons/warning-icon.svg b/assets/icons/warning-icon.svg new file mode 100644 index 0000000..3061c76 --- /dev/null +++ b/assets/icons/warning-icon.svg @@ -0,0 +1 @@ +warning \ No newline at end of file diff --git a/locales/en.toml b/locales/en.toml index 9fa6037..a10fc3c 100644 --- a/locales/en.toml +++ b/locales/en.toml @@ -268,6 +268,9 @@ mp_select_hint = "Select…" mp_add_btn = "✓ Add" mp_size_hint = "Size (MB, 0=remaining):" mp_delete_confirm_yes = "✓ Yes, Delete" +mp_new_partition = "New Partition" +mp_edit_partition = "Edit" +mp_save = "✓ Save" plan_root_suffix = " (root)" disk_min_required = " ↳ %{err} (min. %{n} GB required)" diff --git a/locales/tr.toml b/locales/tr.toml index c488ced..140a547 100644 --- a/locales/tr.toml +++ b/locales/tr.toml @@ -240,6 +240,9 @@ mp_select_hint = "Seç…" mp_add_btn = "✓ Ekle" mp_size_hint = "Boyut (MB, 0=kalan):" mp_delete_confirm_yes = "✓ Evet, Sil" +mp_new_partition = "Yeni Bölüm" +mp_edit_partition = "Düzenle" +mp_save = "✓ Kaydet" plan_root_suffix = " (kök)" disk_min_required = " ↳ %{err} (min. %{n} GB gerekli)" diff --git a/src/installer.rs b/src/installer.rs index 18ef3d0..60e59ff 100644 --- a/src/installer.rs +++ b/src/installer.rs @@ -449,15 +449,31 @@ impl InstallerStep for WelcomeStep { egui::Grid::new("checks_grid") .num_columns(3) .spacing([10.0, 6.0]) - .striped(true) .show(ui, |ui| { for check in &self.checks { - let (icon, color) = match check.status { - CheckStatus::Pass => ("✅", theme::c_success()), - CheckStatus::Warn => ("⚠️", theme::c_warning()), - CheckStatus::Fail => ("❌", theme::c_error()), - }; - ui.colored_label(color, icon); + match check.status { + CheckStatus::Pass => { + ui.add( + egui::Image::new(egui::include_image!("../assets/icons/check.svg")) + .max_width(18.0).max_height(18.0) + .tint(theme::c_success()) + ); + } + CheckStatus::Warn => { + ui.add( + egui::Image::new(egui::include_image!("../assets/icons/warning-icon.svg")) + .max_width(18.0).max_height(18.0) + .tint(theme::c_warning()) + ); + } + CheckStatus::Fail => { + ui.add( + egui::Image::new(egui::include_image!("../assets/icons/fail-icon.svg")) + .max_width(18.0).max_height(18.0) + .tint(theme::c_error()) + ); + } + } ui.label(egui::RichText::new(&check.name).strong().color(theme::c_text())); ui.label( egui::RichText::new(&check.message) diff --git a/src/steps/partition.rs b/src/steps/partition.rs index 716f3ac..4fd7b8d 100644 --- a/src/steps/partition.rs +++ b/src/steps/partition.rs @@ -3,6 +3,7 @@ use eframe::egui; use rust_i18n::t; use crate::ui::theme; +use crate::funct::{form_label, form_input_text}; // sysinfo::Disks mount point başına bir kayıt döndürür (partition'lar), // ham disk listesi için lsblk kullanıyoruz. @@ -220,6 +221,12 @@ const COMMON_MOUNTPOINTS: &[&str] = &["/", "/boot", "/boot/efi", "/home", "/var" struct ManualState { /// "Bölüm Ekle" diyaloğu açık mı? adding: bool, + /// "Bölüm Düzenle" diyaloğı açık mı? + editing: bool, + /// Düzenlenecek bölüm indeksi + edit_idx: Option, + /// Seçili satır indeksi + selected_idx: Option, /// Diyalog: seçili dosya sistemi add_fstype: usize, // FsType listesindeki indeks /// Diyalog: boyut alanı (MB, metin girişi) @@ -363,8 +370,17 @@ impl InstallerStep for PartitionStep { // ── Mod seçimi ───────────────────────────────────────── ui.horizontal(|ui| { - ui.selectable_value(&mut state.erase_disk, true, t!("erase_disk_label")); - ui.selectable_value(&mut state.erase_disk, false, t!("manual_partition_label")); + if state.erase_disk { + let _ = theme::primary_button(ui, &t!("erase_disk_label")); + if theme::secondary_button(ui, &t!("manual_partition_label")).clicked() { + state.erase_disk = false; + } + } else { + if theme::secondary_button(ui, &t!("erase_disk_label")).clicked() { + state.erase_disk = true; + } + let _ = theme::primary_button(ui, &t!("manual_partition_label")); + } }); ui.add_space(10.0); @@ -415,12 +431,10 @@ impl PartitionStep { // arka plan egui::Frame::none() .fill(if selected { - //crate::ui::theme::c_accent_dim() egui::Color32::from_rgba_unmultiplied(237, 237, 224, 1) } else if response.hovered() { egui::Color32::from_rgba_unmultiplied(60, 60, 80, 180) } else { - //crate::ui::theme::c_bg_widget() egui::Color32::from_rgba_unmultiplied(237, 237, 224, 1) }) .stroke(egui::Stroke::new( @@ -512,7 +526,6 @@ impl PartitionStep { let child_ui = &mut ui.child_ui(rect, egui::Layout::top_down(egui::Align::LEFT)); egui::Frame::none() .fill(header_color) - .stroke(egui::Stroke::new(1.0, header_color)) .inner_margin(egui::Margin::symmetric(8.0, 4.0)) .show(child_ui, |ui| { ui.label(egui::RichText::new(h) @@ -629,7 +642,6 @@ impl PartitionStep { impl PartitionStep { fn show_manual_mode(&mut self, ui: &mut egui::Ui, state: &mut GlobalState) { - let accent = crate::ui::theme::c_accent(); let kart_genislik = 180.0; let kart_yukseklik = 160.0; @@ -654,12 +666,10 @@ impl PartitionStep { egui::Frame::none() .fill(if selected { - //crate::ui::theme::c_accent_dim() egui::Color32::from_rgba_unmultiplied(237, 237, 224, 1) } else if response.hovered() { egui::Color32::from_rgba_unmultiplied(60, 60, 80, 180) } else { - //crate::ui::theme::c_bg_widget() egui::Color32::from_rgba_unmultiplied(237, 237, 224, 1) }) .stroke(egui::Stroke::new( @@ -683,20 +693,20 @@ impl PartitionStep { } else { &disk.vendor }; - + ui.label( egui::RichText::new(marka) .size(11.0) .color(crate::ui::theme::c_text_dim()), ); - + ui.label( egui::RichText::new(format!("{} GB", disk.size_gb)) .size(14.0) .strong() .color(crate::ui::theme::c_text()), ); - + }); if response.clicked() { @@ -721,12 +731,17 @@ impl PartitionStep { return; } }; + let disk_size_gb = state.available_disks.iter() + .find(|d| d.name == disk) + .map(|d| d.size_gb) + .unwrap_or(256); + let max_mb = (disk_size_gb as f64) * 1024.0; // ── Mevcut bölüm listesi tablosu ─────────────────────── ui.label(egui::RichText::new(t!("mp_plan_title")).strong().color(crate::ui::theme::c_text())); ui.add_space(6.0); - let mut delete_idx: Option = None; + let delete_idx: Option = None; let col_widths = [0.28, 0.12, 0.18, 0.27, 0.15]; let tam_genislik = ui.available_width(); @@ -741,12 +756,11 @@ impl PartitionStep { grid.show(ui, |ui| { // ── Başlık satırı ───────────────────────────────── - let headers: [String; 5] = [ + let headers: [String; 4] = [ t!("mp_device").to_string(), t!("mp_size").to_string(), t!("mp_fstype").to_string(), - t!("mp_mountpoint").to_string(), - t!("mp_action").to_string(), + t!("mp_mountpoint").to_string(), ]; for (j, h) in headers.into_iter().enumerate() { let (rect, _) = ui.allocate_exact_size( @@ -766,7 +780,10 @@ impl PartitionStep { // ── Veri satırları ──────────────────────────────── for (row_idx, part) in state.custom_partitions.iter().enumerate() { - let row_bg = if row_idx % 2 == 0 { + let is_selected = self.manual.selected_idx == Some(row_idx); + let row_bg = if is_selected { + egui::Color32::from_rgba_unmultiplied(80, 100, 180, 80) + } else if row_idx % 2 == 0 { egui::Color32::from_rgba_unmultiplied(60, 60, 80, 60) } else { egui::Color32::TRANSPARENT @@ -794,22 +811,32 @@ impl PartitionStep { ]; for (j, (deger, is_action)) in hucreler.iter().enumerate() { - let (rect, _response) = ui.allocate_exact_size( + let sense = if *is_action { + egui::Sense::click() + } else { + egui::Sense::click() + }; + let (rect, response) = ui.allocate_exact_size( egui::vec2(tam_genislik * col_widths[j], 34.0), - egui::Sense::click(), + sense, ); + + if !*is_action { + if response.clicked() { + self.manual.selected_idx = Some(row_idx); + } + response.on_hover_cursor(egui::CursorIcon::PointingHand); + } + let child_ui = &mut ui.child_ui(rect, egui::Layout::top_down(egui::Align::Center)); if *is_action { egui::Frame::none() - .fill(row_bg) - .stroke(egui::Stroke::new(0.0, egui::Color32::TRANSPARENT)) + .fill(egui::Color32::TRANSPARENT) .inner_margin(egui::Margin::symmetric(8.0, 4.0)) .show(child_ui, |ui| { ui.vertical_centered(|ui| { - if ui.small_button("🗑").on_hover_text(t!("mp_delete")).clicked() { - delete_idx = Some(row_idx); - } + ui.label(egui::RichText::new(deger).size(13.0).color(crate::ui::theme::c_text())); }); }); } else { @@ -831,6 +858,16 @@ impl PartitionStep { } }); + // ── Bölümleme Planı (manuel) ────────────────────────── + if !state.custom_partitions.is_empty() { + ui.add_space(12.0); + ui.separator(); + ui.add_space(8.0); + ui.label(egui::RichText::new(t!("partition_plan_title")).strong().color(crate::ui::theme::c_text())); + ui.add_space(6.0); + manual_partition_bar(ui, &state.custom_partitions); + } + // Silme onayı if let Some(idx) = delete_idx { self.manual.delete_idx = Some(idx); @@ -847,13 +884,14 @@ impl PartitionStep { ui.label(t!("mp_confirm_delete", dev = dev)); ui.add_space(8.0); ui.horizontal(|ui| { - if ui.button(t!("mp_delete_confirm_yes")).clicked() { + if theme::danger_button(ui, &t!("mp_delete_confirm_yes")).clicked() { if idx < state.custom_partitions.len() { state.custom_partitions.remove(idx); } self.manual.delete_idx = None; + self.manual.selected_idx = None; } - if ui.button(t!("cancel")).clicked() { + if theme::secondary_button(ui, &t!("cancel")).clicked() { self.manual.delete_idx = None; } }); @@ -862,126 +900,199 @@ impl PartitionStep { ui.add_space(8.0); - // ── "Bölüm Ekle" butonu ──────────────────────────────── - let btn = egui::Button::new(format!("➕ {}", t!("mp_add"))) - .fill(accent.gamma_multiply(0.15)); - if ui.add(btn).clicked() { - self.manual.adding = true; - let next_num = state.custom_partitions.len() + 1; - self.manual.add_size_str.clear(); - self.manual.add_mountpoint.clear(); - self.manual.add_fstype = 0; - // Akıllı varsayılan bağlama noktası - self.manual.add_mountpoint = match next_num { - 1 if self.is_uefi => "/boot/efi".to_string(), - 1 => "/".to_string(), - 2 if self.is_uefi => "swap".to_string(), - 2 => "swap".to_string(), - 3 => "/".to_string(), - _ => String::new(), - }; - } + // ── Butonlar: Yeni Bölüm / Düzenle / Sil ─────────── + ui.horizontal(|ui| { + let has_selection = self.manual.selected_idx.is_some(); + let selected = self.manual.selected_idx + .and_then(|i| state.custom_partitions.get(i)); - // ── "Bölüm Ekle" diyaloğu ────────────────────────────── - if self.manual.adding { - egui::Window::new(t!("mp_add")) + // Yeni Bölüm + if theme::primary_button(ui, &format!("➕ {}", t!("mp_new_partition"))).clicked() { + self.manual.adding = true; + self.manual.editing = false; + self.manual.edit_idx = None; + let next_num = state.custom_partitions.len() + 1; + self.manual.add_size_str.clear(); + self.manual.add_mountpoint.clear(); + self.manual.add_fstype = 0; + self.manual.add_encrypt = false; + self.manual.add_luks_password.clear(); + self.manual.add_mountpoint = match next_num { + 1 if self.is_uefi => "/boot/efi".to_string(), + 1 => "/".to_string(), + 2 if self.is_uefi => "swap".to_string(), + 2 => "swap".to_string(), + 3 => "/".to_string(), + _ => String::new(), + }; + } + + // Düzenle + ui.add_enabled_ui(has_selection, |ui| { + if theme::secondary_button(ui, &format!("✏️ {}", t!("mp_edit_partition"))).clicked() { + if let Some(part) = selected { + self.manual.editing = true; + self.manual.adding = false; + self.manual.edit_idx = self.manual.selected_idx; + self.manual.add_size_str = if part.size_mb == 0 { + String::new() + } else { + part.size_mb.to_string() + }; + self.manual.add_mountpoint = part.mountpoint.clone(); + self.manual.add_fstype = FS_OPTIONS.iter() + .position(|(_, ft)| *ft == part.fstype) + .unwrap_or(0); + self.manual.add_encrypt = part.encrypt; + self.manual.add_luks_password = part.luks_password.clone(); + } + } + }); + + // Sil + ui.add_enabled_ui(has_selection, |ui| { + if theme::danger_button(ui, &format!("🗑 {}", t!("mp_delete"))).clicked() { + self.manual.delete_idx = self.manual.selected_idx; + } + }); + }); + + // ── Bölüm Ekle / Düzenle diyaloğu ───────────────── + let dialog_open = self.manual.adding || self.manual.editing; + if dialog_open { + let is_edit = self.manual.editing; + let title = if is_edit { t!("mp_edit_partition") } else { t!("mp_new_partition") }; + egui::Window::new(title) .collapsible(false) .resizable(false) .anchor(egui::Align2::CENTER_CENTER, [0.0, 0.0]) .show(ui.ctx(), |ui| { - egui::Grid::new("add_part_grid") - .num_columns(2) - .spacing([12.0, 8.0]) - .show(ui, |ui| { - - // Dosya sistemi (boyut önerisi ile) - ui.label(t!("mp_fstype")); - let prev_fs = self.manual.add_fstype; - let fs_label = 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| { - for (i, (label, _)) in FS_OPTIONS.iter().enumerate() { - theme::selectable_value(ui, &mut self.manual.add_fstype, i, *label, w); - } - }); - // FS tipi değişince akıllı boyut öner - 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); - match fs { - Some(FsType::Fat32) => self.manual.add_size_str = "300".to_string(), - Some(FsType::Swap) => self.manual.add_size_str = format!("{}", recommended_swap_mb()), - _ => {} - } - } - ui.end_row(); - - // Boyut - ui.label(t!("mp_size_hint")); - ui.text_edit_singleline(&mut self.manual.add_size_str); - ui.end_row(); - - // Bağlama noktası (2 aşamalı: combo + input) - ui.label(t!("mp_mountpoint")); - ui.horizontal(|ui| { - let current_mp = if self.manual.add_mountpoint.is_empty() { - t!("mp_select_hint").to_string() - } else { - self.manual.add_mountpoint.clone() - }; - theme::show_combo_box(ui, "mp_combo", ¤t_mp, 120.0, |ui, w| { - for mp in COMMON_MOUNTPOINTS { - theme::selectable_value(ui, &mut self.manual.add_mountpoint, mp.to_string(), *mp, w); - } - }); - let mp_field = egui::TextEdit::singleline(&mut self.manual.add_mountpoint) - .hint_text(t!("mp_add_dialog_hint")); - ui.add(mp_field); - }); - ui.end_row(); - - // LUKS şifreleme - ui.label(t!("luks_enable_root")); - let fstype = FS_OPTIONS.get(self.manual.add_fstype).map(|x| x.1.clone()).unwrap_or(FsType::Ext4); - let can_encrypt = matches!(fstype, FsType::Ext4 | FsType::Ext3 | FsType::Btrfs | FsType::Xfs); - ui.add_enabled_ui(can_encrypt, |ui| { - ui.checkbox(&mut self.manual.add_encrypt, ""); - }); - if !can_encrypt { self.manual.add_encrypt = false; } - ui.end_row(); - - if self.manual.add_encrypt { - ui.label(t!("luks_password")); - let pw = &mut self.manual.add_luks_password; - ui.horizontal(|ui| { - if self.show_luks_password { - ui.text_edit_singleline(pw); - } else { - ui.add(egui::TextEdit::singleline(pw).password(true)); - } - let eye = if self.show_luks_password { "🙈" } else { "👁" }; - if ui.small_button(eye).clicked() { - self.show_luks_password = !self.show_luks_password; - } - }); - ui.end_row(); + ui.label(t!("mp_fstype")); + let prev_fs = self.manual.add_fstype; + let fs_label = 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| { + for (i, (label, _)) in FS_OPTIONS.iter().enumerate() { + theme::selectable_value(ui, &mut self.manual.add_fstype, i, *label, w); } }); + // FS tipi değişince akıllı boyut öner + if !is_edit && 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); + match fs { + Some(FsType::Fat32) => self.manual.add_size_str = "300".to_string(), + Some(FsType::Swap) => self.manual.add_size_str = format!("{}", recommended_swap_mb()), + _ => {} + } + } + + ui.add_space(6.0); + form_label(ui, &t!("mp_size_hint")); + ui.horizontal(|ui| { + form_input_text(ui, &mut self.manual.add_size_str); + let parsed: u64 = self.manual.add_size_str.trim().parse().unwrap_or(0); + if parsed > 0 { + ui.label(format!("{} MB", format_number_thousands(parsed))); + } else { + ui.label("MB"); + } + }); + // Slider — kullanılabilir alan kadar + let used_mb: u64 = state.custom_partitions.iter() + .enumerate() + .filter(|(i, p)| p.size_mb > 0 && Some(*i) != self.manual.edit_idx) + .map(|(_, p)| p.size_mb) + .sum(); + let avail_mb = (max_mb as u64).saturating_sub(used_mb).max(1); + let mut size_val = self.manual.add_size_str.trim().parse::().unwrap_or(0.0); + ui.add_space(2.0); + if ui.add(egui::Slider::new(&mut size_val, 0.0..=avail_mb as f64) + .show_value(false) + .max_decimals(0) + ).changed() { + if size_val == 0.0 { + self.manual.add_size_str = "0".to_string(); + } else { + self.manual.add_size_str = format!("{}", size_val as u64); + } + } + + ui.add_space(6.0); + ui.label(t!("mp_mountpoint")); + ui.horizontal(|ui| { + let current_mp = if self.manual.add_mountpoint.is_empty() { + t!("mp_select_hint").to_string() + } else { + self.manual.add_mountpoint.clone() + }; + theme::show_combo_box(ui, "mp_combo", ¤t_mp, 120.0, |ui, w| { + for mp in COMMON_MOUNTPOINTS { + theme::selectable_value(ui, &mut self.manual.add_mountpoint, mp.to_string(), *mp, w); + } + }); + form_input_text(ui, &mut self.manual.add_mountpoint); + }); + + ui.add_space(6.0); + ui.label(t!("luks_enable_root")); + let fstype = FS_OPTIONS.get(self.manual.add_fstype).map(|x| x.1.clone()).unwrap_or(FsType::Ext4); + let can_encrypt = matches!(fstype, FsType::Ext4 | FsType::Ext3 | FsType::Btrfs | FsType::Xfs); + ui.add_enabled_ui(can_encrypt, |ui| { + ui.checkbox(&mut self.manual.add_encrypt, ""); + }); + if !can_encrypt { self.manual.add_encrypt = false; } + + if self.manual.add_encrypt { + ui.add_space(6.0); + ui.label(t!("luks_password")); + let pw = &mut self.manual.add_luks_password; + ui.horizontal(|ui| { + if self.show_luks_password { + ui.text_edit_singleline(pw); + } else { + ui.add(egui::TextEdit::singleline(pw).password(true)); + } + let eye = if self.show_luks_password { "🙈" } else { "👁" }; + if ui.small_button(eye).clicked() { + self.show_luks_password = !self.show_luks_password; + } + }); + } ui.add_space(8.0); ui.horizontal(|ui| { - let ok_btn = egui::Button::new(t!("mp_add_btn")).fill(accent.gamma_multiply(0.2)); - if ui.add(ok_btn).clicked() { + let ok_label = if is_edit { t!("mp_save") } else { t!("mp_add_btn") }; + if theme::primary_button(ui, &ok_label).clicked() { let size_mb: u64 = self.manual.add_size_str .trim().parse().unwrap_or(0); let mp = self.manual.add_mountpoint.trim().to_string(); let fstype = FS_OPTIONS.get(self.manual.add_fstype).map(|x| x.1.clone()).unwrap_or(FsType::Ext4); - let part_num = state.custom_partitions.len() + 1; - let device = crate::installer::part_path(&disk, part_num as u32); + let encrypt = self.manual.add_encrypt; + let luks_password = if encrypt { self.manual.add_luks_password.clone() } else { String::new() }; if mp.is_empty() { self.manual.error = Some(t!("mp_error_empty_mountpoint").to_string()); + } else if is_edit { + if let Some(idx) = self.manual.edit_idx { + if idx < state.custom_partitions.len() { + let device = state.custom_partitions[idx].device.clone(); + state.custom_partitions[idx] = CustomPartition { + device, + size_mb, + fstype, + mountpoint: mp, + encrypt, + luks_password, + luks_name: String::new(), + }; + } + } + self.manual.editing = false; + self.manual.edit_idx = None; + self.manual.selected_idx = None; + self.manual.error = None; } else { - let encrypt = self.manual.add_encrypt; - let luks_password = if encrypt { self.manual.add_luks_password.clone() } else { String::new() }; + let part_num = state.custom_partitions.len() + 1; + let device = crate::installer::part_path(&disk, part_num as u32); state.custom_partitions.push(CustomPartition { device, size_mb, @@ -997,8 +1108,10 @@ impl PartitionStep { self.manual.adding = false; } } - if ui.button(t!("cancel")).clicked() { + if theme::secondary_button(ui, &t!("cancel")).clicked() { self.manual.adding = false; + self.manual.editing = false; + self.manual.edit_idx = None; } }); }); @@ -1111,9 +1224,7 @@ impl PartitionStep { ui.add_space(8.0); // "Logical Volume Ekle" butonu - let lv_btn = egui::Button::new(format!("➕ {}", t!("lvm_lv_add"))) - .fill(accent.gamma_multiply(0.15)); - if ui.add(lv_btn).clicked() { + if theme::primary_button(ui, &format!("➕ {}", t!("lvm_lv_add"))).clicked() { self.manual.adding_lv = true; self.manual.add_lv_name.clear(); self.manual.add_lv_vg_idx = 0; @@ -1189,8 +1300,7 @@ impl PartitionStep { ui.add_space(8.0); ui.horizontal(|ui| { - let ok_btn = egui::Button::new(t!("mp_add_btn")).fill(accent.gamma_multiply(0.2)); - if ui.add(ok_btn).clicked() { + if theme::primary_button(ui, &t!("mp_add_btn")).clicked() { let size_mb: u64 = self.manual.add_lv_size_str.trim().parse().unwrap_or(0); let mp = self.manual.add_lv_mountpoint.trim().to_string(); let lv_name = self.manual.add_lv_name.trim().to_string(); @@ -1223,7 +1333,7 @@ impl PartitionStep { self.manual.error = Some(t!("lvm_lv_error_vg").to_string()); } } - if ui.button(t!("cancel")).clicked() { + if theme::secondary_button(ui, &t!("cancel")).clicked() { self.manual.adding_lv = false; } }); @@ -1249,7 +1359,84 @@ impl PartitionStep { } // ───────────────────────────────────────────── -// Yardımcı: renkli bölüm şeridi +// Yardımcı: renkli bölüm şeridi (manuel) +// ───────────────────────────────────────────── + +fn manual_partition_bar(ui: &mut egui::Ui, parts: &[CustomPartition]) { + let bar_height = 28.0; + let (rect, _) = ui.allocate_exact_size( + egui::vec2(ui.available_width(), bar_height), + egui::Sense::hover(), + ); + let painter = ui.painter(); + + if parts.is_empty() { + return; + } + + // Each partition gets at least MIN_VISIBLE of the bar so all are visible + let sized_total: f32 = parts.iter().map(|p| p.size_mb as f32).sum(); + let min_visible = 0.04; + + let mut raw: Vec = parts.iter().map(|p| { + if sized_total > 0.0 { + (p.size_mb as f32 / sized_total).max(min_visible) + } else { + min_visible + } + }).collect(); + + // Normalize to sum 1.0 + let sum: f32 = raw.iter().sum(); + for v in raw.iter_mut() { + *v /= sum; + } + + let mut segments: Vec<(&str, f32, egui::Color32)> = Vec::new(); + + for (i, part) in parts.iter().enumerate() { + let label = part.mountpoint.as_str(); + let color = if part.fstype == FsType::Fat32 { + egui::Color32::from_rgb(80, 160, 220) + } else if part.fstype == FsType::Swap { + egui::Color32::from_rgb(220, 160, 40) + } else if part.mountpoint == "/" { + egui::Color32::from_rgb(80, 190, 100) + } else { + egui::Color32::from_rgb(160, 130, 200) + }; + segments.push((label, raw[i], color)); + } + + let mut x = rect.left(); + let rounding = egui::Rounding::same(4.0); + for (i, (label, frac, color)) in segments.iter().enumerate() { + let w = frac * rect.width(); + let seg_rect = egui::Rect::from_min_size( + egui::pos2(x, rect.top()), + egui::vec2(w - 1.0, bar_height), + ); + let seg_rounding = if i == 0 { + egui::Rounding { nw: rounding.nw, sw: rounding.sw, ne: 0.0, se: 0.0 } + } else if i == segments.len() - 1 { + egui::Rounding { nw: 0.0, sw: 0.0, ne: rounding.ne, se: rounding.se } + } else { + egui::Rounding::ZERO + }; + painter.rect_filled(seg_rect, seg_rounding, *color); + painter.text( + seg_rect.center(), + egui::Align2::CENTER_CENTER, + *label, + egui::FontId::proportional(12.0), + egui::Color32::WHITE, + ); + x += w; + } +} + +// ───────────────────────────────────────────── +// Yardımcı: renkli bölüm şeridi (otomatik) // ───────────────────────────────────────────── fn partition_bar(ui: &mut egui::Ui, plan: &PartitionPlan) { @@ -1308,3 +1495,16 @@ fn partition_bar(ui: &mut egui::Ui, plan: &PartitionPlan) { x += w; } } + +/// Sayıyı nokta ile binlik ayraçlı formata çevirir (örn. 7000000 → 7.000.000). +fn format_number_thousands(n: u64) -> String { + let s = n.to_string(); + let mut result = String::new(); + for (i, c) in s.chars().enumerate() { + if i > 0 && (s.len() - i) % 3 == 0 { + result.push('.'); + } + result.push(c); + } + result +} diff --git a/src/ui/theme.rs b/src/ui/theme.rs index 504d741..b52802e 100644 --- a/src/ui/theme.rs +++ b/src/ui/theme.rs @@ -272,7 +272,9 @@ pub fn error_box(ui: &mut egui::Ui, msg: &str) { .inner_margin(egui::Margin::same(10.0)) .show(ui, |ui| { ui.horizontal(|ui| { - ui.colored_label(c_error(), "❌"); + ui.add(egui::Image::new(egui::include_image!("../../assets/icons/fail-icon.svg")) + .max_width(18.0).max_height(18.0) + .tint(c_error())); ui.colored_label(c_error(), msg); }); }); @@ -287,7 +289,9 @@ pub fn warning_box(ui: &mut egui::Ui, msg: &str) { .inner_margin(egui::Margin::same(10.0)) .show(ui, |ui| { ui.horizontal(|ui| { - ui.colored_label(c_warning(), "⚠️"); + ui.add(egui::Image::new(egui::include_image!("../../assets/icons/warning-icon.svg")) + .max_width(18.0).max_height(18.0) + .tint(c_warning())); ui.colored_label(c_warning(), msg); }); }); @@ -302,7 +306,9 @@ pub fn success_box(ui: &mut egui::Ui, msg: &str) { .inner_margin(egui::Margin::same(10.0)) .show(ui, |ui| { ui.horizontal(|ui| { - ui.colored_label(c_success(), "✅"); + ui.add(egui::Image::new(egui::include_image!("../../assets/icons/check.svg")) + .max_width(18.0).max_height(18.0) + .tint(c_success())); ui.colored_label(c_success(), msg); }); });