fix: slider max value = available disk space, partition size input now editable

- slider maximum value calculated as disk size minus used partitions
- editing mode includes current partition capacity in available space
- size text input now writes back to add_size_str
- formatted MB label shown next to text input
- CHANGELOG.md updated
This commit is contained in:
Erkan IŞIK
2026-06-21 04:17:09 +03:00
parent 020dac2773
commit 74fa279ab8
8 changed files with 388 additions and 145 deletions
+13
View File
@@ -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
+1
View File
@@ -0,0 +1 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 122.88 122.88"><defs><style>.cls-1{fill:#ff4141;fill-rule:evenodd;}</style></defs><title>cross</title><path class="cls-1" d="M6,6H6a20.53,20.53,0,0,1,29,0l26.5,26.49L87.93,6a20.54,20.54,0,0,1,29,0h0a20.53,20.53,0,0,1,0,29L90.41,61.44,116.9,87.93a20.54,20.54,0,0,1,0,29h0a20.54,20.54,0,0,1-29,0L61.44,90.41,35,116.9a20.54,20.54,0,0,1-29,0H6a20.54,20.54,0,0,1,0-29L32.47,61.44,6,34.94A20.53,20.53,0,0,1,6,6Z"/></svg>

After

Width:  |  Height:  |  Size: 500 B

+1
View File
@@ -0,0 +1 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 122.88 111.54"><defs><style>.cls-1{fill:#cf1f25;}.cls-2{fill:#fec901;fill-rule:evenodd;}.cls-3{fill:#010101;}</style></defs><title>warning</title><path class="cls-1" d="M2.35,84.42,45.28,10.2l.17-.27h0A23,23,0,0,1,52.5,2.69,17,17,0,0,1,61.57,0a16.7,16.7,0,0,1,9.11,2.69,22.79,22.79,0,0,1,7,7.26q.19.32.36.63l42.23,73.34.24.44h0a22.48,22.48,0,0,1,2.37,10.19,17.63,17.63,0,0,1-2.17,8.35,15.94,15.94,0,0,1-6.93,6.6c-.19.1-.39.18-.58.26a21.19,21.19,0,0,1-9.11,1.75v0H17.61c-.22,0-.44,0-.65,0a18.07,18.07,0,0,1-6.2-1.15A16.42,16.42,0,0,1,3,104.24a17.53,17.53,0,0,1-3-9.57,23,23,0,0,1,1.57-8.74,7.66,7.66,0,0,1,.77-1.51Z"/><path class="cls-2" d="M9,88.75,52.12,14.16c5.24-8.25,13.54-8.46,18.87,0l42.43,73.69c3.39,6.81,1.71,16-9.33,15.77H17.61C10.35,103.8,5.67,97.43,9,88.75Z"/><path class="cls-3" d="M57.57,83.78A5.53,5.53,0,0,1,61,82.2a5.6,5.6,0,0,1,2.4.36,5.7,5.7,0,0,1,2,1.3,5.56,5.56,0,0,1,1.54,5,6.23,6.23,0,0,1-.42,1.35,5.57,5.57,0,0,1-5.22,3.26,5.72,5.72,0,0,1-2.27-.53A5.51,5.51,0,0,1,56.28,90a5.18,5.18,0,0,1-.36-1.27,5.83,5.83,0,0,1-.06-1.31h0a6.53,6.53,0,0,1,.57-2,4.7,4.7,0,0,1,1.14-1.56Zm8.15-10.24c-.19,4.79-8.31,4.8-8.49,0-.82-8.21-2.92-29.34-2.86-37.05.07-2.38,2-3.79,4.56-4.33a12.83,12.83,0,0,1,5,0c2.61.56,4.65,2,4.65,4.44v.24L65.72,73.54Z"/></svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

+3
View File
@@ -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)"
+3
View File
@@ -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)"
+23 -7
View File
@@ -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)
+335 -135
View File
@@ -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<usize>,
/// Seçili satır indeksi
selected_idx: Option<usize>,
/// 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<usize> = None;
let delete_idx: Option<usize> = 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", &current_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::<f64>().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", &current_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<f32> = 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
}
+9 -3
View File
@@ -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);
});
});