forked from pisilinux-rs/yali-rs
feat: gradient buttons, language selector with flags, SVG support, and UI improvements
- Add linear gradient backgrounds to primary/secondary/danger buttons - Add language selector with flag icons in ComboBox - Add SVG support via egui_extras feature - Add cancel confirmation dialog on exit - Update Yali logo to SVG icon - Add flag SVGs for all countries - Improve users step layout with form helpers - Refactor button API (ui::theme::*_button now takes ui param)
This commit is contained in:
@@ -125,7 +125,9 @@ pub fn apply_to_state(af: &AnswerFile, state: &mut crate::installer::GlobalState
|
||||
let model = parts.collect::<Vec<_>>().join(" ");
|
||||
Some(DiskInfo {
|
||||
name: format!("/dev/{}", name),
|
||||
vendor: String::new(),
|
||||
model: if model.is_empty() { "Unknown".to_string() } else { model },
|
||||
drive_type: String::new(),
|
||||
size_gb: bytes / 1_073_741_824,
|
||||
size_bytes: bytes,
|
||||
})
|
||||
|
||||
+1
-1
@@ -136,7 +136,7 @@ pub struct OemConfig {
|
||||
|
||||
fn default_title() -> String { "YALI Installation Tool".to_string() }
|
||||
fn default_subtitle() -> String { "PiSi GNU/LINUX".to_string() }
|
||||
fn default_yali_logo() -> String { "assets/yali.png".to_string() }
|
||||
fn default_yali_logo() -> String { "assets/icons/YALI_icon_LIGHT.svg".to_string() }
|
||||
fn default_pisi_logo_dark() -> String { "assets/pisi-logo-dark.png".to_string() }
|
||||
fn default_pisi_logo_light() -> String { "assets/pisi-logo-light.png".to_string() }
|
||||
fn default_interval() -> u64 { 6 }
|
||||
|
||||
+135
@@ -0,0 +1,135 @@
|
||||
use eframe::egui;
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! draw_icon {
|
||||
($ui:expr, $path:literal, $size:expr, $color:expr) => {
|
||||
$ui.add(
|
||||
eframe::egui::Image::new(eframe::egui::include_image!($path))
|
||||
.max_width($size)
|
||||
.max_height($size)
|
||||
.tint($color),
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
pub fn form_input_text(
|
||||
ui: &mut eframe::egui::Ui,
|
||||
text: &mut String,
|
||||
)-> eframe::egui::Response {
|
||||
|
||||
let width_percent = 0.90; // Mevcut genişliğin %90'ı
|
||||
let height = 32.0; // Standart yükseklik
|
||||
let hesaplanan_genislik = ui.available_width() * width_percent;
|
||||
let boyut = eframe::egui::vec2(hesaplanan_genislik, height);
|
||||
|
||||
ui.spacing_mut().button_padding = eframe::egui::vec2(10.0, 10.0);
|
||||
ui.add_sized(
|
||||
boyut,
|
||||
eframe::egui::TextEdit::singleline(text)
|
||||
.vertical_align(eframe::egui::Align::Center)
|
||||
)
|
||||
}
|
||||
|
||||
pub fn form_input_password(
|
||||
ui: &mut eframe::egui::Ui,
|
||||
text: &mut String,
|
||||
show_password: &mut bool,
|
||||
) -> eframe::egui::Response {
|
||||
let width_percent = 0.90;
|
||||
let height = 32.0;
|
||||
let toplam_genislik = ui.available_width() * width_percent;
|
||||
let dugme_genislik = 40.0;
|
||||
let alan_genislik = toplam_genislik - dugme_genislik - 4.0;
|
||||
let boyut = eframe::egui::vec2(alan_genislik, height);
|
||||
|
||||
ui.spacing_mut().button_padding = eframe::egui::vec2(10.0, 10.0);
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
let r = ui.add_sized(
|
||||
boyut,
|
||||
eframe::egui::TextEdit::singleline(text)
|
||||
.password(!*show_password)
|
||||
.vertical_align(eframe::egui::Align::Center),
|
||||
);
|
||||
let göz = if *show_password { "🙈" } else { "👁" };
|
||||
if ui.add(
|
||||
eframe::egui::Button::new(göz)
|
||||
.min_size(eframe::egui::vec2(dugme_genislik, height))
|
||||
).clicked() {
|
||||
*show_password = !*show_password;
|
||||
}
|
||||
r
|
||||
}).inner
|
||||
}
|
||||
|
||||
pub fn form_label(ui: &mut eframe::egui::Ui, text: &str) {
|
||||
ui.label(
|
||||
eframe::egui::RichText::new(text)
|
||||
.strong()
|
||||
.color(crate::ui::theme::c_text()) // Atomic temandan rengi çekiyor
|
||||
);
|
||||
}
|
||||
|
||||
pub fn form_checkbox(ui: &mut eframe::egui::Ui, checked: &mut bool, text: &str) {
|
||||
ui.checkbox(checked, eframe::egui::RichText::new(text).strong().color(crate::ui::theme::c_text()));
|
||||
}
|
||||
|
||||
|
||||
|
||||
pub fn language_selector_ui(ui: &mut eframe::egui::Ui, state: &mut crate::installer::GlobalState) {
|
||||
ui.horizontal(|ui| {
|
||||
// Satır yüksekliğini tam olarak 32px'e eşitliyoruz
|
||||
ui.set_height(32.0);
|
||||
|
||||
// 1. Sol taraftaki ikon alanı
|
||||
ui.vertical(|ui| {
|
||||
ui.add_space(4.0);
|
||||
ui.add(
|
||||
eframe::egui::Image::new(eframe::egui::include_image!("../assets/icons/language-icon.svg"))
|
||||
.max_width(40.0)
|
||||
);
|
||||
});
|
||||
|
||||
ui.add_space(6.0);
|
||||
|
||||
// 2. ComboBox alanı (%90 genişlik payı)
|
||||
let available_width = ui.available_width() * 0.90;
|
||||
let current_lang_label = if state.language == "tr" { "Türkçe" } else { "English" };
|
||||
|
||||
let languages = [
|
||||
("tr", "Türkçe", eframe::egui::include_image!("../assets/flags/tr.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
|
||||
eframe::egui::ComboBox::from_id_source("language_select_combo")
|
||||
.selected_text(current_lang_label)
|
||||
.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
|
||||
ui.add_sized([available_width, item_height], |ui: &mut egui::Ui| {
|
||||
ui.horizontal(|ui| {
|
||||
ui.add_space(6.0); // İç sol boşluk
|
||||
ui.add(eframe::egui::Image::new(image_source.clone()).max_width(18.0));
|
||||
ui.add_space(4.0);
|
||||
|
||||
let mut current_lang = state.language.clone();
|
||||
let resp = ui.selectable_value(&mut current_lang, code.to_string(), *label);
|
||||
|
||||
if resp.clicked() {
|
||||
state.language = code.to_string();
|
||||
}
|
||||
resp
|
||||
}).response
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
+58
-45
@@ -74,7 +74,10 @@ pub trait InstallerStep {
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct DiskInfo {
|
||||
pub name: String, // ör. "/dev/sda"
|
||||
pub vendor: String, // "ATA", "INTEL", vb.
|
||||
pub model: String,
|
||||
#[allow(dead_code)]
|
||||
pub drive_type: String, // "NVMe SSD", "SSD", "HDD", "USB", "MMC/SD", vb.
|
||||
pub size_gb: u64,
|
||||
pub size_bytes: u64,
|
||||
}
|
||||
@@ -274,7 +277,7 @@ impl Default for GlobalState {
|
||||
install_log: Vec::new(),
|
||||
install_current_file: None,
|
||||
demo_mode: false,
|
||||
is_dark: true,
|
||||
is_dark: false,
|
||||
rescue_mode: false,
|
||||
bootloader_device: String::new(),
|
||||
bootloader_timeout: 5,
|
||||
@@ -313,6 +316,7 @@ impl Default for GlobalState {
|
||||
// WELCOME STEP (sistem gereksinimleri + dil seçimi)
|
||||
// ─────────────────────────────────────────────
|
||||
|
||||
use crate::funct::language_selector_ui;
|
||||
use crate::autoinstall::checker::{CheckResult, CheckStatus, run_all, all_passed};
|
||||
|
||||
pub struct WelcomeStep {
|
||||
@@ -404,16 +408,17 @@ impl InstallerStep for WelcomeStep {
|
||||
ui.add_space(12.0);
|
||||
|
||||
// Dil seçimi (Açılır Liste / ComboBox)
|
||||
ui.horizontal(|ui| {
|
||||
ui.label(t!("select_language"));
|
||||
let current_lang_label = if state.language == "tr" { "🇹🇷 Türkçe" } else { "🇺🇸 English" };
|
||||
egui::ComboBox::from_id_source("language_select")
|
||||
.selected_text(current_lang_label)
|
||||
.show_ui(ui, |ui| {
|
||||
ui.selectable_value(&mut state.language, "tr".to_string(), "🇹🇷 Türkçe");
|
||||
ui.selectable_value(&mut state.language, "en".to_string(), "🇺🇸 English");
|
||||
});
|
||||
});
|
||||
// ui.horizontal(|ui| {
|
||||
// ui.label(t!("select_language"));
|
||||
// let current_lang_label = if state.language == "tr" { "🇹🇷 Türkçe" } else { "🇺🇸 English" };
|
||||
// egui::ComboBox::from_id_source("language_select")
|
||||
// .selected_text(current_lang_label)
|
||||
// .show_ui(ui, |ui| {
|
||||
// ui.selectable_value(&mut state.language, "tr".to_string(), "🇹🇷 Türkçe");
|
||||
// ui.selectable_value(&mut state.language, "en".to_string(), "🇺🇸 English");
|
||||
// });
|
||||
// });
|
||||
language_selector_ui(ui, state);
|
||||
|
||||
ui.add_space(14.0);
|
||||
ui.separator();
|
||||
@@ -425,7 +430,20 @@ impl InstallerStep for WelcomeStep {
|
||||
.strong()
|
||||
.color(theme::c_text()),
|
||||
);
|
||||
ui.add_space(6.0);
|
||||
ui.add_space(10.0);
|
||||
|
||||
// Hata varsa uyarı
|
||||
ui.add_space(10.0);
|
||||
let has_fail = self.checks.iter().any(|c| c.status == CheckStatus::Fail);
|
||||
if has_fail {
|
||||
theme::error_box(ui, &t!("check_fail_warning"));
|
||||
} else if self.checks.iter().any(|c| c.status == CheckStatus::Warn) {
|
||||
theme::warning_box(ui, &t!("check_warn_notice"));
|
||||
} else if !self.checks.is_empty() {
|
||||
theme::success_box(ui, &t!("check_all_pass"));
|
||||
}
|
||||
|
||||
ui.add_space(10.0);
|
||||
|
||||
// Kontrol tablosu
|
||||
egui::Grid::new("checks_grid")
|
||||
@@ -452,42 +470,37 @@ impl InstallerStep for WelcomeStep {
|
||||
|
||||
// Rescue mode butonu
|
||||
ui.add_space(10.0);
|
||||
ui.horizontal(|ui| {
|
||||
let rescue_available = detect_existing_linux();
|
||||
if rescue_available {
|
||||
ui.colored_label(
|
||||
egui::Color32::from_rgb(80, 170, 80),
|
||||
"🛟",
|
||||
);
|
||||
if ui.add(theme::secondary_button(&t!("rescue_button"))).clicked() {
|
||||
state.rescue_mode = true;
|
||||
}
|
||||
} else {
|
||||
ui.label(
|
||||
egui::RichText::new(t!("rescue_not_available"))
|
||||
.size(12.0)
|
||||
.color(egui::Color32::from_rgb(120, 120, 140)),
|
||||
);
|
||||
|
||||
ui.allocate_ui(
|
||||
egui::vec2(ui.available_width(), 30.0), // Genişlik tam, Yükseklik sadece 24px
|
||||
|ui| {
|
||||
// Şimdi dikeyde ortalayarak soldan sağa diziyoruz
|
||||
ui.with_layout(egui::Layout::left_to_right(egui::Align::Center), |ui| {
|
||||
let rescue_available = detect_existing_linux();
|
||||
if rescue_available {
|
||||
if theme::secondary_button(ui, &t!("rescue_button")).clicked() {
|
||||
state.rescue_mode = true;
|
||||
}
|
||||
|
||||
if theme::secondary_button(ui, &t!("recheck")).clicked() {
|
||||
self.checked = false;
|
||||
self.on_enter(state);
|
||||
}
|
||||
} else {
|
||||
ui.label(
|
||||
egui::RichText::new(t!("rescue_not_available"))
|
||||
.size(12.0)
|
||||
.color(egui::Color32::from_rgb(120, 120, 140)),
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
);
|
||||
|
||||
// Hata varsa uyarı
|
||||
ui.add_space(10.0);
|
||||
let has_fail = self.checks.iter().any(|c| c.status == CheckStatus::Fail);
|
||||
if has_fail {
|
||||
theme::error_box(ui, &t!("check_fail_warning"));
|
||||
} else if self.checks.iter().any(|c| c.status == CheckStatus::Warn) {
|
||||
theme::warning_box(ui, &t!("check_warn_notice"));
|
||||
} else if !self.checks.is_empty() {
|
||||
theme::success_box(ui, &t!("check_all_pass"));
|
||||
}
|
||||
|
||||
// Yeniden tara butonu
|
||||
ui.add_space(8.0);
|
||||
if ui.add(theme::secondary_button(&t!("recheck"))).clicked() {
|
||||
self.checked = false;
|
||||
self.on_enter(state);
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
fn is_complete(&self, state: &GlobalState) -> bool {
|
||||
|
||||
+8
-5
@@ -922,7 +922,7 @@ impl Job for PartitionJob {
|
||||
let end_mb = next_mb + self.plan.efi_mb;
|
||||
ui.log(format!("EFI bölümü oluşturuluyor: {}–{} MB", next_mb, end_mb));
|
||||
run_parted(&[
|
||||
"-s", disk, "mkpart", "primary", "fat32",
|
||||
"-s", disk, "mkpart", "fat32",
|
||||
&format!("{}MB", next_mb), &format!("{}MB", end_mb),
|
||||
]).await?;
|
||||
run_parted(&["-s", disk, "set", "1", "esp", "on"]).await?;
|
||||
@@ -934,7 +934,7 @@ impl Job for PartitionJob {
|
||||
let swap_num: u32 = if self.plan.efi_mb > 0 { 2 } else { 1 };
|
||||
ui.log(format!("Swap bölümü oluşturuluyor: {}–{} MB", next_mb, swap_end));
|
||||
run_parted(&[
|
||||
"-s", disk, "mkpart", "primary", "linux-swap",
|
||||
"-s", disk, "mkpart", "linux-swap",
|
||||
&format!("{}MB", next_mb), &format!("{}MB", swap_end),
|
||||
]).await?;
|
||||
next_mb = swap_end;
|
||||
@@ -943,7 +943,7 @@ impl Job for PartitionJob {
|
||||
let root_num: u32 = if self.plan.efi_mb > 0 { 3 } else { 2 };
|
||||
ui.log(format!("Kök bölümü oluşturuluyor: {}MB – disk sonu", next_mb));
|
||||
run_parted(&[
|
||||
"-s", disk, "mkpart", "primary", "ext4",
|
||||
"-s", disk, "mkpart", "ext4",
|
||||
&format!("{}MB", next_mb), "100%",
|
||||
]).await?;
|
||||
|
||||
@@ -1945,6 +1945,10 @@ impl Job for CustomPartitionJob {
|
||||
// 0. Diski hazırla: bağlamaları ayır, kernel tablosunu temizle
|
||||
prepare_disk(&self.disk, ui).await;
|
||||
|
||||
let is_gpt = is_uefi; // Genellikle UEFI ise GPT kullanılır
|
||||
// MBR'de "primary" zorunludur, GPT'de ise bu alan isim (label) alanıdır.
|
||||
let part_type_or_name = if is_gpt { "" } else { "primary" };
|
||||
|
||||
// 1. Bölüm tablosu oluştur
|
||||
let table_label = if is_uefi { "gpt" } else { "msdos" };
|
||||
ui.log(format!("parted {} mktable {}", self.disk, table_label));
|
||||
@@ -1981,7 +1985,7 @@ impl Job for CustomPartitionJob {
|
||||
part_num, start_str, end_str, fs_label
|
||||
));
|
||||
run_parted(&[
|
||||
"-s", &self.disk, "mkpart", "primary", fs_label,
|
||||
"-s", &self.disk, "mkpart", part_type_or_name, fs_label,
|
||||
&start_str, &end_str
|
||||
]).await?;
|
||||
|
||||
@@ -2389,4 +2393,3 @@ pub fn build_custom_job_queue(
|
||||
|
||||
JobQueue::new(jobs)
|
||||
}
|
||||
|
||||
|
||||
+78
-53
@@ -4,6 +4,7 @@ mod jobs;
|
||||
mod ui;
|
||||
mod autoinstall;
|
||||
mod branding;
|
||||
mod funct;
|
||||
|
||||
use installer::{GlobalState, InstallerStep, WelcomeStep, LocationStep};
|
||||
use steps::{KeyboardStep, UsersStep, SummaryStep, ExecutionStep, FinishStep, PartitionStep, BootloaderStep, NetworkStep, DisplayManagerStep};
|
||||
@@ -16,6 +17,7 @@ fn main() -> eframe::Result<()> {
|
||||
// ── CLI: otomatik kurulum modu ───────────────────────
|
||||
let args: Vec<String> = std::env::args().collect();
|
||||
let demo_mode = args.contains(&"--demo".to_string());
|
||||
|
||||
|
||||
if let Some(pos) = args.iter().position(|a| a == "--auto-install") {
|
||||
if let Some(path) = args.get(pos + 1) {
|
||||
@@ -31,6 +33,7 @@ fn main() -> eframe::Result<()> {
|
||||
|
||||
/// Grafik arayüzü başlatır (normal mod).
|
||||
fn run_gui(demo_mode: bool) -> eframe::Result<()> {
|
||||
|
||||
let options = eframe::NativeOptions {
|
||||
viewport: egui::ViewportBuilder::default()
|
||||
.with_inner_size([1200.0, 900.0])
|
||||
@@ -45,8 +48,8 @@ fn run_gui(demo_mode: bool) -> eframe::Result<()> {
|
||||
Box::new(move |cc| {
|
||||
// Görsel yükleyicileri etkinleştir (PNG desteği)
|
||||
egui_extras::install_image_loaders(&cc.egui_ctx);
|
||||
|
||||
ui::theme::apply(&cc.egui_ctx, true);
|
||||
ui::theme::set_theme_mode(false);
|
||||
ui::theme::apply(&cc.egui_ctx, false);
|
||||
let mut app = YaliApp::default();
|
||||
app.state.demo_mode = demo_mode;
|
||||
Box::new(app)
|
||||
@@ -202,12 +205,13 @@ fn run_auto_install(path: &str, demo_mode: bool) -> eframe::Result<()> {
|
||||
}
|
||||
|
||||
struct YaliApp {
|
||||
state: GlobalState,
|
||||
steps: Vec<Box<dyn InstallerStep>>,
|
||||
rescue_step: Option<Box<dyn InstallerStep>>,
|
||||
last_step: usize,
|
||||
branding: branding::BrandingConfig,
|
||||
loaded_language: String,
|
||||
state: GlobalState,
|
||||
steps: Vec<Box<dyn InstallerStep>>,
|
||||
rescue_step: Option<Box<dyn InstallerStep>>,
|
||||
last_step: usize,
|
||||
branding: branding::BrandingConfig,
|
||||
loaded_language: String,
|
||||
show_cancel_dialog: bool,
|
||||
}
|
||||
|
||||
impl Default for YaliApp {
|
||||
@@ -233,6 +237,7 @@ impl Default for YaliApp {
|
||||
last_step: usize::MAX,
|
||||
branding: branding.clone(),
|
||||
loaded_language: String::new(),
|
||||
show_cancel_dialog: false,
|
||||
};
|
||||
|
||||
// OEM modu: varsayılan değerleri ata ve step engelleme
|
||||
@@ -347,17 +352,17 @@ impl eframe::App for YaliApp {
|
||||
);
|
||||
}
|
||||
ui.add_space(8.0);
|
||||
ui.label(
|
||||
egui::RichText::new(t!("app_name"))
|
||||
.strong()
|
||||
.size(16.0)
|
||||
.color(ui::theme::c_text_dim()),
|
||||
);
|
||||
// ui.label(
|
||||
// egui::RichText::new(t!("app_name"))
|
||||
// .strong()
|
||||
// .size(16.0)
|
||||
// .color(ui::theme::c_text_dim()),
|
||||
// );
|
||||
|
||||
ui.colored_label(
|
||||
egui::Color32::from_rgb(60, 60, 90),
|
||||
format!("{}: {}", t!("version"), env!("CARGO_PKG_VERSION")),
|
||||
);
|
||||
// ui.colored_label(
|
||||
// egui::Color32::from_rgb(60, 60, 90),
|
||||
// format!("{}: {}", t!("version"), env!("CARGO_PKG_VERSION")),
|
||||
// );
|
||||
});
|
||||
|
||||
ui.add_space(16.0);
|
||||
@@ -366,7 +371,8 @@ impl eframe::App for YaliApp {
|
||||
|
||||
// Adım listesi
|
||||
for (i, step) in self.steps.iter().enumerate() {
|
||||
let label = format!("{}. {}", i + 1, step.name());
|
||||
//let label = format!("{}. {}", i + 1, step.name());
|
||||
let label = step.name();
|
||||
|
||||
egui::Frame::none()
|
||||
.fill(if i == cur {
|
||||
@@ -374,23 +380,23 @@ impl eframe::App for YaliApp {
|
||||
} else {
|
||||
egui::Color32::TRANSPARENT
|
||||
})
|
||||
.rounding(egui::Rounding::same(6.0))
|
||||
//.rounding(egui::Rounding::same(6.0))
|
||||
.inner_margin(egui::Margin::symmetric(10.0, 5.0))
|
||||
.show(ui, |ui| {
|
||||
ui.set_width(175.0);
|
||||
if i == cur {
|
||||
ui.horizontal(|ui| {
|
||||
ui.colored_label(ui::theme::c_accent(), "▶");
|
||||
ui.colored_label(ui::theme::c_text(), label);
|
||||
ui.colored_label(ui::theme::c_sidebar_text(), label);
|
||||
});
|
||||
} else if i < cur {
|
||||
ui.horizontal(|ui| {
|
||||
ui.colored_label(ui::theme::c_success(), "✅");
|
||||
ui.colored_label(ui::theme::c_text_dim(), label);
|
||||
ui.colored_label(ui::theme::c_sidebar_text(), label);
|
||||
});
|
||||
} else {
|
||||
ui.colored_label(
|
||||
egui::Color32::from_rgb(80, 80, 100),
|
||||
egui::Color32::from_rgb(255, 255, 255),
|
||||
format!(" {}", label),
|
||||
);
|
||||
}
|
||||
@@ -400,39 +406,35 @@ impl eframe::App for YaliApp {
|
||||
}
|
||||
|
||||
// Alt kısım: tema değiştirme
|
||||
ui.with_layout(egui::Layout::bottom_up(egui::Align::Center), |ui| {
|
||||
ui.add_space(6.0);
|
||||
// ui.with_layout(egui::Layout::bottom_up(egui::Align::Center), |ui| {
|
||||
// ui.add_space(6.0);
|
||||
|
||||
let theme_icon = if self.state.is_dark { "☀" } else { "🌙" };
|
||||
let theme_label = if self.state.is_dark { "Light" } else { "Dark" };
|
||||
if ui.add(
|
||||
egui::Button::new(
|
||||
egui::RichText::new(format!("{} {}", theme_icon, theme_label))
|
||||
.size(12.0)
|
||||
)
|
||||
.rounding(egui::Rounding::same(6.0))
|
||||
.min_size(egui::Vec2::new(100.0, 28.0))
|
||||
).clicked() {
|
||||
self.state.is_dark = !self.state.is_dark;
|
||||
ui::theme::set_theme_mode(self.state.is_dark);
|
||||
ui::theme::apply(ctx, self.state.is_dark);
|
||||
}
|
||||
});
|
||||
// let theme_icon = if self.state.is_dark { "☀" } else { "🌙" };
|
||||
// let theme_label = if self.state.is_dark { "Light" } else { "Dark" };
|
||||
// if ui.add(
|
||||
// egui::Button::new(
|
||||
// egui::RichText::new(format!("{} {}", theme_icon, theme_label))
|
||||
// .size(12.0)
|
||||
// )
|
||||
// .rounding(egui::Rounding::same(6.0))
|
||||
// .min_size(egui::Vec2::new(100.0, 28.0))
|
||||
// ).clicked() {
|
||||
// self.state.is_dark = !self.state.is_dark;
|
||||
// ui::theme::set_theme_mode(self.state.is_dark);
|
||||
// ui::theme::apply(ctx, self.state.is_dark);
|
||||
// }
|
||||
// });
|
||||
});
|
||||
|
||||
// ── Alt panel: navigasyon ────────────────────────────
|
||||
egui::TopBottomPanel::bottom("footer")
|
||||
.frame(egui::Frame::none()
|
||||
.fill(ui::theme::c_bg_panel())
|
||||
.stroke(egui::Stroke::new(1.0, ui::theme::c_border())))
|
||||
.fill(ui::theme::c_footer_bg())
|
||||
.stroke(egui::Stroke::new(1.0, ui::theme::c_footer_bg())))
|
||||
.show(ctx, |ui| {
|
||||
ui.add_space(10.0);
|
||||
ui.horizontal(|ui| {
|
||||
ui.add_space(8.0);
|
||||
|
||||
if !is_exec && !is_finish && ui.add(ui::theme::secondary_button(&t!("cancel"))).clicked() {
|
||||
ctx.send_viewport_cmd(egui::ViewportCommand::Close);
|
||||
}
|
||||
ui.add_space(8.0);
|
||||
|
||||
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
|
||||
ui.add_space(8.0);
|
||||
@@ -449,19 +451,22 @@ impl eframe::App for YaliApp {
|
||||
} else {
|
||||
t!("next")
|
||||
};
|
||||
let btn = if is_valid {
|
||||
ui::theme::primary_button(&next_label)
|
||||
} else {
|
||||
ui::theme::secondary_button(&next_label)
|
||||
if !is_exec && !is_finish && ui::theme::danger_button(ui, &t!("cancel")).clicked() {
|
||||
self.show_cancel_dialog = true;
|
||||
};
|
||||
if ui.add_enabled(is_valid, btn).clicked() {
|
||||
let next_clicked = if is_valid {
|
||||
ui::theme::primary_button(ui, &next_label).clicked()
|
||||
} else {
|
||||
ui::theme::secondary_button(ui, &next_label).clicked()
|
||||
};
|
||||
if next_clicked {
|
||||
self.state.current_step += 1;
|
||||
}
|
||||
}
|
||||
|
||||
if cur > 0 && !is_exec {
|
||||
ui.add_space(8.0);
|
||||
if ui.add(ui::theme::secondary_button(&t!("back"))).clicked() {
|
||||
if ui::theme::secondary_button(ui, &t!("back")).clicked() {
|
||||
self.state.current_step -= 1;
|
||||
}
|
||||
}
|
||||
@@ -471,6 +476,26 @@ impl eframe::App for YaliApp {
|
||||
ui.add_space(10.0);
|
||||
});
|
||||
|
||||
// ── İptal onay diyaloğu ───────────────────────────────
|
||||
if self.show_cancel_dialog {
|
||||
egui::Window::new(t!("cancel_confirm_title"))
|
||||
.collapsible(false)
|
||||
.resizable(false)
|
||||
.anchor(egui::Align2::CENTER_CENTER, [0.0, 0.0])
|
||||
.show(ctx, |ui| {
|
||||
ui.label(t!("cancel_confirm_msg"));
|
||||
ui.add_space(12.0);
|
||||
ui.horizontal(|ui| {
|
||||
if ui.button(t!("cancel_confirm_yes")).clicked() {
|
||||
ctx.send_viewport_cmd(egui::ViewportCommand::Close);
|
||||
}
|
||||
if ui.button(t!("cancel_confirm_no")).clicked() {
|
||||
self.show_cancel_dialog = false;
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// ── Merkez panel ─────────────────────────────────────
|
||||
egui::CentralPanel::default()
|
||||
.frame(egui::Frame::none()
|
||||
|
||||
+7
-15
@@ -24,7 +24,7 @@ impl InstallerStep for FinishStep {
|
||||
.size(24.0)
|
||||
.strong()
|
||||
.color(theme::c_text()),
|
||||
);
|
||||
);
|
||||
ui.add_space(8.0);
|
||||
ui.label(
|
||||
egui::RichText::new(t!("finish_description"))
|
||||
@@ -35,26 +35,18 @@ impl InstallerStep for FinishStep {
|
||||
ui.add_space(32.0);
|
||||
|
||||
// Yeniden Başlat — ana eylem butonu
|
||||
if ui.add(
|
||||
theme::primary_button(&t!("finish_restart"))
|
||||
.min_size(egui::vec2(200.0, 44.0)),
|
||||
).clicked() {
|
||||
if theme::primary_button(ui, &t!("finish_restart")).clicked() {
|
||||
let _ = std::process::Command::new("sync").status();
|
||||
let _ = std::process::Command::new("reboot")
|
||||
.arg("-f")
|
||||
.spawn();
|
||||
ui.ctx().send_viewport_cmd(egui::ViewportCommand::Close);
|
||||
std::thread::spawn(|| {
|
||||
std::thread::sleep(std::time::Duration::from_millis(500));
|
||||
let _ = std::process::Command::new("reboot")
|
||||
.arg("-f")
|
||||
.spawn();
|
||||
});
|
||||
}
|
||||
|
||||
ui.add_space(12.0);
|
||||
|
||||
// Canlı masaüstüne dön — ikincil eylem
|
||||
if ui.add(
|
||||
theme::secondary_button(&t!("finish_live"))
|
||||
.min_size(egui::vec2(200.0, 36.0)),
|
||||
).clicked() {
|
||||
if theme::secondary_button(ui, &t!("finish_live")).clicked() {
|
||||
ui.ctx().send_viewport_cmd(egui::ViewportCommand::Close);
|
||||
}
|
||||
});
|
||||
|
||||
+398
-108
@@ -81,10 +81,36 @@ fn read_existing_partitions(disk_path: &str) -> Vec<crate::installer::CustomPart
|
||||
}
|
||||
|
||||
/// `/sys/block` altındaki gerçek blok aygıtlarını (partition değil) listeler.
|
||||
/// Aygıt adına (`sda`, `nvme0n1`) bakarak türünü belirler.
|
||||
fn detect_drive_type(name: &str) -> String {
|
||||
if name.starts_with("nvme") {
|
||||
return "NVMe SSD".into();
|
||||
}
|
||||
if name.starts_with("mmcblk") {
|
||||
return "MMC/SD".into();
|
||||
}
|
||||
// removable ortam (USB flash bellek)
|
||||
let removable_path = format!("/sys/block/{}/removable", name);
|
||||
if let Ok(val) = std::fs::read_to_string(&removable_path) {
|
||||
if val.trim() == "1" {
|
||||
return "USB".into();
|
||||
}
|
||||
}
|
||||
// döner disk mi? (rotational=1 → HDD, 0 → SSD)
|
||||
let rotational_path = format!("/sys/block/{}/queue/rotational", name);
|
||||
if let Ok(val) = std::fs::read_to_string(&rotational_path) {
|
||||
if val.trim() == "0" {
|
||||
return "SSD".into();
|
||||
}
|
||||
}
|
||||
"HDD".into()
|
||||
}
|
||||
|
||||
/// lsblk'dan gelen çıktı akıllıca ayrıştırılır; başarısız olursa /sys/block fallback devreye girer.
|
||||
fn list_block_devices() -> Vec<crate::installer::DiskInfo> {
|
||||
pub fn list_block_devices() -> Vec<crate::installer::DiskInfo> {
|
||||
// Önce -P (key=value pairs) ile dene, boşluklu MODEL/VENDOR güvenli
|
||||
let out = std::process::Command::new("lsblk")
|
||||
.args(["-d", "-o", "NAME,SIZE,MODEL", "-b", "--noheadings"])
|
||||
.args(["-d", "-o", "NAME,SIZE,MODEL,VENDOR", "-b", "--noheadings", "-P"])
|
||||
.output();
|
||||
|
||||
let mut disks = Vec::new();
|
||||
@@ -93,45 +119,46 @@ fn list_block_devices() -> Vec<crate::installer::DiskInfo> {
|
||||
if o.status.success() {
|
||||
let text = String::from_utf8_lossy(&o.stdout);
|
||||
for line in text.lines() {
|
||||
// split_whitespace ardışık tüm boşlukları tek bir ayırıcı gibi yakalar
|
||||
let mut tokens = line.split_whitespace();
|
||||
|
||||
let name_raw = match tokens.next() {
|
||||
Some(n) => n,
|
||||
None => continue,
|
||||
};
|
||||
// NAME="sda" SIZE="42949672960" MODEL="VBOX HARDDISK" VENDOR="ATA"
|
||||
let mut name_raw = String::new();
|
||||
let mut vendor = String::new();
|
||||
let mut model = String::new();
|
||||
let mut size = 0u64;
|
||||
|
||||
for pair in line.split_ascii_whitespace() {
|
||||
let mut parts = pair.splitn(2, '=');
|
||||
let key = parts.next().unwrap_or("");
|
||||
let val = parts.next().unwrap_or("").trim_matches('"');
|
||||
match key {
|
||||
"NAME" => name_raw = val.to_string(),
|
||||
"SIZE" => size = val.parse().unwrap_or(0),
|
||||
"VENDOR"=> vendor = val.to_string(),
|
||||
"MODEL" => model = val.to_string(),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
if name_raw.is_empty() || size == 0 { continue; }
|
||||
|
||||
// loop, ram, sr (CD-ROM) ve zram aygıtlarını atla
|
||||
if name_raw.starts_with("loop")
|
||||
|| name_raw.starts_with("ram")
|
||||
|| name_raw.starts_with("sr")
|
||||
|| name_raw.starts_with("zram")
|
||||
{ continue; }
|
||||
|
||||
let size_str = match tokens.next() {
|
||||
Some(s) => s,
|
||||
None => continue,
|
||||
};
|
||||
|
||||
let size_bytes: u64 = size_str.parse().unwrap_or(0);
|
||||
// Eğer boyut 0 ise ekleme yapma ki lsblk patlarsa fallback çalışabilsin
|
||||
if size_bytes == 0 { continue; }
|
||||
|
||||
// Kalan tüm token'lar model ismidir (örn: ["VBOX", "HARDDISK"])
|
||||
let model_tokens: Vec<&str> = tokens.collect();
|
||||
let model = model_tokens.join(" ");
|
||||
|
||||
disks.push(crate::installer::DiskInfo {
|
||||
name: format!("/dev/{}", name_raw),
|
||||
vendor,
|
||||
model,
|
||||
size_gb: size_bytes / 1_073_741_824,
|
||||
size_bytes,
|
||||
drive_type: detect_drive_type(&name_raw),
|
||||
size_gb: size / 1_073_741_824,
|
||||
size_bytes: size,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// lsblk başarısız olduysa veya geçerli disk bulamadıysa temiz /sys/block fallback'i çalışır
|
||||
// lsblk başarısız olduysa /sys/block fallback
|
||||
if disks.is_empty() {
|
||||
if let Ok(rd) = std::fs::read_dir("/sys/block") {
|
||||
for entry in rd.flatten() {
|
||||
@@ -149,13 +176,19 @@ fn list_block_devices() -> Vec<crate::installer::DiskInfo> {
|
||||
let size_bytes = size_sectors * 512;
|
||||
if size_bytes == 0 { continue; }
|
||||
|
||||
let vendor_path = format!("/sys/block/{}/device/vendor", name);
|
||||
let vendor = std::fs::read_to_string(&vendor_path)
|
||||
.unwrap_or_default().trim().to_string();
|
||||
|
||||
let model_path = format!("/sys/block/{}/device/model", name);
|
||||
let model = std::fs::read_to_string(&model_path)
|
||||
.unwrap_or_default().trim().to_string();
|
||||
|
||||
disks.push(crate::installer::DiskInfo {
|
||||
name: format!("/dev/{}", name),
|
||||
vendor,
|
||||
model,
|
||||
drive_type: detect_drive_type(&name),
|
||||
size_gb: size_bytes / 1_073_741_824,
|
||||
size_bytes,
|
||||
});
|
||||
@@ -359,22 +392,91 @@ impl PartitionStep {
|
||||
fn show_auto_mode(&mut self, ui: &mut egui::Ui, state: &mut GlobalState) {
|
||||
ui.group(|ui| {
|
||||
ui.label(t!("select_disk_label"));
|
||||
ui.add_space(4.0);
|
||||
ui.add_space(8.0);
|
||||
let disks = state.available_disks.clone();
|
||||
for disk in &disks {
|
||||
let too_small = disk.size_gb < MIN_DISK_GB;
|
||||
let label = egui::RichText::new(format!("{} — {} GB ({})", disk.name, disk.size_gb, disk.model)).strong().color(crate::ui::theme::c_accent());
|
||||
ui.add_enabled_ui(!too_small, |ui| {
|
||||
let selected = state.selected_disk.as_deref() == Some(&disk.name);
|
||||
if ui.radio(selected, label).clicked() {
|
||||
state.selected_disk = Some(disk.name.clone());
|
||||
state.partition_plan = make_plan(disk, self.is_uefi);
|
||||
let kart_genislik = 180.0;
|
||||
let kart_yukseklik = 160.0;
|
||||
|
||||
egui::Grid::new("disk_grid")
|
||||
.min_col_width(kart_genislik + 12.0)
|
||||
.spacing([12.0, 12.0])
|
||||
.show(ui, |ui| {
|
||||
for (i, disk) in disks.iter().enumerate() {
|
||||
let too_small = disk.size_gb < MIN_DISK_GB;
|
||||
let selected = state.selected_disk.as_deref() == Some(&disk.name);
|
||||
|
||||
let (rect, response) = ui.allocate_exact_size(
|
||||
egui::vec2(kart_genislik, kart_yukseklik),
|
||||
egui::Sense::click(),
|
||||
);
|
||||
let child_ui = &mut ui.child_ui(rect, egui::Layout::top_down(egui::Align::Center));
|
||||
|
||||
// arka plan
|
||||
egui::Frame::none()
|
||||
.fill(if selected {
|
||||
crate::ui::theme::c_accent_dim()
|
||||
} else if response.hovered() {
|
||||
egui::Color32::from_rgba_unmultiplied(60, 60, 80, 180)
|
||||
} else if too_small {
|
||||
egui::Color32::from_rgba_unmultiplied(60, 30, 30, 180)
|
||||
} else {
|
||||
crate::ui::theme::c_bg_widget()
|
||||
})
|
||||
.stroke(egui::Stroke::new(
|
||||
if selected { 2.0 } else { 1.0 },
|
||||
if selected { crate::ui::theme::c_accent() } else { crate::ui::theme::c_border() },
|
||||
))
|
||||
.rounding(egui::Rounding::same(8.0))
|
||||
.inner_margin(egui::Margin::same(8.0))
|
||||
.show(child_ui, |ui| {
|
||||
ui.set_min_size(egui::vec2(kart_genislik - 16.0, kart_yukseklik - 16.0));
|
||||
ui.add_space(8.0);
|
||||
ui.add(
|
||||
egui::Image::new(egui::include_image!("../../assets/disk.png"))
|
||||
.max_width(64.0)
|
||||
.max_height(64.0)
|
||||
.rounding(egui::Rounding::same(6.0))
|
||||
.tint(if too_small {
|
||||
egui::Color32::from_gray(80)
|
||||
} else {
|
||||
egui::Color32::WHITE
|
||||
}),
|
||||
);
|
||||
ui.add_space(6.0);
|
||||
let marka = if disk.vendor.is_empty() {
|
||||
&disk.model
|
||||
} else {
|
||||
&disk.vendor
|
||||
};
|
||||
ui.label(
|
||||
egui::RichText::new(format!("{} GB", disk.size_gb))
|
||||
.size(14.0)
|
||||
.strong()
|
||||
.color(crate::ui::theme::c_text()),
|
||||
);
|
||||
ui.label(
|
||||
egui::RichText::new(marka)
|
||||
.size(11.0)
|
||||
.color(crate::ui::theme::c_text_dim()),
|
||||
);
|
||||
if too_small {
|
||||
ui.colored_label(
|
||||
crate::ui::theme::c_error(),
|
||||
t!("disk_too_small"),
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
if response.clicked() && !too_small {
|
||||
state.selected_disk = Some(disk.name.clone());
|
||||
state.partition_plan = make_plan(disk, self.is_uefi);
|
||||
}
|
||||
|
||||
if (i + 1) % 3 == 0 {
|
||||
ui.end_row();
|
||||
}
|
||||
}
|
||||
});
|
||||
if too_small {
|
||||
ui.small(t!("disk_min_required", err = t!("disk_too_small"), n = MIN_DISK_GB.to_string()));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if let Some(ref plan) = state.partition_plan {
|
||||
@@ -386,34 +488,96 @@ impl PartitionStep {
|
||||
partition_bar(ui, plan);
|
||||
ui.add_space(8.0);
|
||||
|
||||
egui::Grid::new("plan_grid")
|
||||
// Bootstrap 5 tarzı tablo
|
||||
let col_widths = [0.40, 0.25, 0.35];
|
||||
let tam_genislik = ui.available_width();
|
||||
let header_color = egui::Color32::from_rgb(33, 37, 41); // dark bg
|
||||
let header_text = egui::Color32::WHITE;
|
||||
let _border_stroke = egui::Stroke::new(1.0, crate::ui::theme::c_border());
|
||||
|
||||
let grid = egui::Grid::new("plan_grid")
|
||||
.num_columns(3)
|
||||
.spacing([20.0, 6.0])
|
||||
.striped(true)
|
||||
.show(ui, |ui| {
|
||||
ui.label(egui::RichText::new(t!("plan_col_part")).strong().color(crate::ui::theme::c_text()));
|
||||
ui.label(egui::RichText::new(t!("plan_col_fs")).strong().color(crate::ui::theme::c_text()));
|
||||
ui.label(egui::RichText::new(t!("plan_col_size")).strong().color(crate::ui::theme::c_text()));
|
||||
ui.end_row();
|
||||
.spacing([0.0, 0.0])
|
||||
.min_col_width(0.0);
|
||||
|
||||
if plan.efi_mb > 0 {
|
||||
ui.label(format!("{}1 (EFI)", plan.disk));
|
||||
ui.label("FAT32");
|
||||
ui.label(format!("{} MB", plan.efi_mb));
|
||||
ui.end_row();
|
||||
}
|
||||
let swap_part = if plan.efi_mb > 0 { 2 } else { 1 };
|
||||
ui.label(format!("{}{} (swap)", plan.disk, swap_part));
|
||||
ui.label("swap");
|
||||
ui.label(format!("{} MB", plan.swap_mb));
|
||||
ui.end_row();
|
||||
grid.show(ui, |ui| {
|
||||
// ── Başlık satırı ─────────────────────────────────
|
||||
let headers = [t!("plan_col_part"), t!("plan_col_fs"), t!("plan_col_size")];
|
||||
for (j, h) in headers.into_iter().enumerate() {
|
||||
let (rect, _) = ui.allocate_exact_size(
|
||||
egui::vec2(tam_genislik * col_widths[j], 30.0),
|
||||
egui::Sense::hover(),
|
||||
);
|
||||
let child_ui = &mut ui.child_ui(rect, egui::Layout::top_down(egui::Align::Center));
|
||||
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.vertical_centered(|ui| {
|
||||
ui.label(egui::RichText::new(h).strong().color(header_text).size(13.0));
|
||||
});
|
||||
});
|
||||
}
|
||||
ui.end_row();
|
||||
|
||||
let root_part = swap_part + 1;
|
||||
ui.label(format!("{}{}{}", plan.disk, root_part, t!("plan_root_suffix")));
|
||||
ui.label("ext4");
|
||||
ui.label(format!("{:.1} GB", plan.root_mb as f64 / 1024.0));
|
||||
ui.end_row();
|
||||
// ── Veri satırları ────────────────────────────────
|
||||
struct Satir {
|
||||
part: String,
|
||||
fs: &'static str,
|
||||
size: String,
|
||||
}
|
||||
let mut satirlar = Vec::new();
|
||||
if plan.efi_mb > 0 {
|
||||
satirlar.push(Satir {
|
||||
part: format!("{}1 (EFI)", plan.disk),
|
||||
fs: "FAT32",
|
||||
size: format!("{} MB", plan.efi_mb),
|
||||
});
|
||||
}
|
||||
let swap_part = if plan.efi_mb > 0 { 2 } else { 1 };
|
||||
satirlar.push(Satir {
|
||||
part: format!("{}{} (swap)", plan.disk, swap_part),
|
||||
fs: "swap",
|
||||
size: format!("{} MB", plan.swap_mb),
|
||||
});
|
||||
let root_part = swap_part + 1;
|
||||
satirlar.push(Satir {
|
||||
part: format!("{}{}{}", plan.disk, root_part, t!("plan_root_suffix")),
|
||||
fs: "ext4",
|
||||
size: format!("{:.1} GB", plan.root_mb as f64 / 1024.0),
|
||||
});
|
||||
|
||||
for (row_idx, satir) in satirlar.iter().enumerate() {
|
||||
let row_bg = if row_idx % 2 == 0 {
|
||||
egui::Color32::from_rgba_unmultiplied(60, 60, 80, 60)
|
||||
} else {
|
||||
egui::Color32::TRANSPARENT
|
||||
};
|
||||
let hucreler: [&str; 3] = [&satir.part, satir.fs, &satir.size];
|
||||
for (j, deger) in hucreler.iter().enumerate() {
|
||||
let (rect, _) = ui.allocate_exact_size(
|
||||
egui::vec2(tam_genislik * col_widths[j], 34.0),
|
||||
egui::Sense::hover(),
|
||||
);
|
||||
let child_ui = &mut ui.child_ui(rect, egui::Layout::top_down(egui::Align::Center));
|
||||
egui::Frame::none()
|
||||
.fill(row_bg)
|
||||
.stroke(egui::Stroke::new(0.0, egui::Color32::TRANSPARENT))
|
||||
.inner_margin(egui::Margin::symmetric(8.0, 4.0))
|
||||
.show(child_ui, |ui| {
|
||||
ui.vertical_centered(|ui| {
|
||||
ui.label(egui::RichText::new(*deger).size(13.0).color(crate::ui::theme::c_text()));
|
||||
});
|
||||
});
|
||||
}
|
||||
// satır alt çizgisi
|
||||
if row_idx < satirlar.len() - 1 {
|
||||
ui.separator();
|
||||
}
|
||||
ui.end_row();
|
||||
}
|
||||
});
|
||||
|
||||
ui.add_space(8.0);
|
||||
ui.label(format!("{}: {}", t!("partition_table_type"), plan.table_type));
|
||||
@@ -464,23 +628,81 @@ 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;
|
||||
|
||||
// ── Disk seçimi (hedef disk) ────────────────────────────
|
||||
ui.group(|ui| {
|
||||
ui.label(t!("select_disk_label"));
|
||||
ui.add_space(4.0);
|
||||
ui.add_space(8.0);
|
||||
let disks = state.available_disks.clone();
|
||||
for disk in &disks {
|
||||
let selected = state.selected_disk.as_deref() == Some(&disk.name);
|
||||
let label = egui::RichText::new(format!("{} — {} GB ({})", disk.name, disk.size_gb, disk.model)).strong().color(crate::ui::theme::c_accent());
|
||||
if ui.radio(selected, label).clicked() {
|
||||
state.selected_disk = Some(disk.name.clone());
|
||||
// Disk değişince mevcut bölümleri oku ve listeyi güncelle.
|
||||
// Böylece kullanıcı diskin mevcut düzenini görebilir.
|
||||
state.custom_partitions = read_existing_partitions(&disk.name);
|
||||
self.manual.error = None;
|
||||
}
|
||||
}
|
||||
|
||||
egui::Grid::new("manual_disk_grid")
|
||||
.min_col_width(kart_genislik + 12.0)
|
||||
.spacing([12.0, 12.0])
|
||||
.show(ui, |ui| {
|
||||
for (i, disk) in disks.iter().enumerate() {
|
||||
let selected = state.selected_disk.as_deref() == Some(&disk.name);
|
||||
|
||||
let (rect, response) = ui.allocate_exact_size(
|
||||
egui::vec2(kart_genislik, kart_yukseklik),
|
||||
egui::Sense::click(),
|
||||
);
|
||||
let child_ui = &mut ui.child_ui(rect, egui::Layout::top_down(egui::Align::Center));
|
||||
|
||||
egui::Frame::none()
|
||||
.fill(if selected {
|
||||
crate::ui::theme::c_accent_dim()
|
||||
} else if response.hovered() {
|
||||
egui::Color32::from_rgba_unmultiplied(60, 60, 80, 180)
|
||||
} else {
|
||||
crate::ui::theme::c_bg_widget()
|
||||
})
|
||||
.stroke(egui::Stroke::new(
|
||||
if selected { 2.0 } else { 1.0 },
|
||||
if selected { crate::ui::theme::c_accent() } else { crate::ui::theme::c_border() },
|
||||
))
|
||||
.rounding(egui::Rounding::same(8.0))
|
||||
.inner_margin(egui::Margin::same(8.0))
|
||||
.show(child_ui, |ui| {
|
||||
ui.set_min_size(egui::vec2(kart_genislik - 16.0, kart_yukseklik - 16.0));
|
||||
ui.add_space(8.0);
|
||||
ui.add(
|
||||
egui::Image::new(egui::include_image!("../../assets/disk.png"))
|
||||
.max_width(64.0)
|
||||
.max_height(64.0)
|
||||
.rounding(egui::Rounding::same(6.0)),
|
||||
);
|
||||
ui.add_space(6.0);
|
||||
let marka = if disk.vendor.is_empty() {
|
||||
&disk.model
|
||||
} else {
|
||||
&disk.vendor
|
||||
};
|
||||
ui.label(
|
||||
egui::RichText::new(format!("{} GB", disk.size_gb))
|
||||
.size(14.0)
|
||||
.strong()
|
||||
.color(crate::ui::theme::c_text()),
|
||||
);
|
||||
ui.label(
|
||||
egui::RichText::new(marka)
|
||||
.size(11.0)
|
||||
.color(crate::ui::theme::c_text_dim()),
|
||||
);
|
||||
});
|
||||
|
||||
if response.clicked() {
|
||||
state.selected_disk = Some(disk.name.clone());
|
||||
state.custom_partitions = read_existing_partitions(&disk.name);
|
||||
self.manual.error = None;
|
||||
}
|
||||
|
||||
if (i + 1) % 3 == 0 {
|
||||
ui.end_row();
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
ui.add_space(8.0);
|
||||
@@ -495,46 +717,114 @@ impl PartitionStep {
|
||||
|
||||
// ── 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(4.0);
|
||||
ui.add_space(6.0);
|
||||
|
||||
let mut delete_idx: Option<usize> = None;
|
||||
|
||||
egui::Grid::new("manual_parts_grid")
|
||||
let col_widths = [0.28, 0.12, 0.18, 0.27, 0.15];
|
||||
let tam_genislik = ui.available_width();
|
||||
let header_color = egui::Color32::from_rgb(33, 37, 41);
|
||||
let header_text = egui::Color32::WHITE;
|
||||
let _border_stroke = egui::Stroke::new(1.0, crate::ui::theme::c_border());
|
||||
|
||||
let grid = egui::Grid::new("manual_parts_grid")
|
||||
.num_columns(5)
|
||||
.spacing([12.0, 6.0])
|
||||
.striped(true)
|
||||
.show(ui, |ui| {
|
||||
// Başlık
|
||||
ui.label(egui::RichText::new(t!("mp_device")).strong().color(crate::ui::theme::c_text()));
|
||||
ui.label(egui::RichText::new(t!("mp_size")).strong().color(crate::ui::theme::c_text()));
|
||||
ui.label(egui::RichText::new(t!("mp_fstype")).strong().color(crate::ui::theme::c_text()));
|
||||
ui.label(egui::RichText::new(t!("mp_mountpoint")).strong().color(crate::ui::theme::c_text()));
|
||||
ui.label(egui::RichText::new(t!("mp_action")).strong().color(crate::ui::theme::c_text()));
|
||||
ui.end_row();
|
||||
.spacing([0.0, 0.0])
|
||||
.min_col_width(0.0);
|
||||
|
||||
for (i, part) in state.custom_partitions.iter().enumerate() {
|
||||
let size_str = if part.size_mb == 0 {
|
||||
t!("mp_size_remaining").to_string()
|
||||
} else if part.size_mb >= 1024 {
|
||||
format!("{:.1} GB", part.size_mb as f64 / 1024.0)
|
||||
} else {
|
||||
format!("{} MB", part.size_mb)
|
||||
};
|
||||
grid.show(ui, |ui| {
|
||||
// ── Başlık satırı ─────────────────────────────────
|
||||
let headers: [String; 5] = [
|
||||
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(),
|
||||
];
|
||||
for (j, h) in headers.into_iter().enumerate() {
|
||||
let (rect, _) = ui.allocate_exact_size(
|
||||
egui::vec2(tam_genislik * col_widths[j], 30.0),
|
||||
egui::Sense::hover(),
|
||||
);
|
||||
let child_ui = &mut ui.child_ui(rect, egui::Layout::top_down(egui::Align::Center));
|
||||
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.vertical_centered(|ui| {
|
||||
ui.label(egui::RichText::new(h).strong().color(header_text).size(13.0));
|
||||
});
|
||||
});
|
||||
}
|
||||
ui.end_row();
|
||||
|
||||
ui.label(&part.device);
|
||||
let mut fstype_label = part.fstype.label().to_string();
|
||||
if part.encrypt {
|
||||
fstype_label = format!("🔐 {}", fstype_label);
|
||||
}
|
||||
ui.label(&size_str);
|
||||
ui.label(fstype_label);
|
||||
ui.label(&part.mountpoint);
|
||||
if ui.small_button("🗑").on_hover_text(t!("mp_delete")).clicked() {
|
||||
delete_idx = Some(i);
|
||||
}
|
||||
ui.end_row();
|
||||
// ── Veri satırları ────────────────────────────────
|
||||
for (row_idx, part) in state.custom_partitions.iter().enumerate() {
|
||||
let row_bg = if row_idx % 2 == 0 {
|
||||
egui::Color32::from_rgba_unmultiplied(60, 60, 80, 60)
|
||||
} else {
|
||||
egui::Color32::TRANSPARENT
|
||||
};
|
||||
|
||||
let size_str = if part.size_mb == 0 {
|
||||
t!("mp_size_remaining").to_string()
|
||||
} else if part.size_mb >= 1024 {
|
||||
format!("{:.1} GB", part.size_mb as f64 / 1024.0)
|
||||
} else {
|
||||
format!("{} MB", part.size_mb)
|
||||
};
|
||||
|
||||
let mut fstype_label = part.fstype.label().to_string();
|
||||
if part.encrypt {
|
||||
fstype_label = format!("🔐 {}", fstype_label);
|
||||
}
|
||||
});
|
||||
|
||||
let hucreler: [(String, bool); 5] = [
|
||||
(part.device.clone(), false),
|
||||
(size_str, false),
|
||||
(fstype_label, false),
|
||||
(part.mountpoint.clone(), false),
|
||||
(String::new(), true), // action column - button
|
||||
];
|
||||
|
||||
for (j, (deger, is_action)) in hucreler.iter().enumerate() {
|
||||
let (rect, _response) = ui.allocate_exact_size(
|
||||
egui::vec2(tam_genislik * col_widths[j], 34.0),
|
||||
egui::Sense::click(),
|
||||
);
|
||||
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))
|
||||
.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);
|
||||
}
|
||||
});
|
||||
});
|
||||
} else {
|
||||
egui::Frame::none()
|
||||
.fill(row_bg)
|
||||
.stroke(egui::Stroke::new(0.0, egui::Color32::TRANSPARENT))
|
||||
.inner_margin(egui::Margin::symmetric(8.0, 4.0))
|
||||
.show(child_ui, |ui| {
|
||||
ui.vertical_centered(|ui| {
|
||||
ui.label(egui::RichText::new(deger).size(13.0).color(crate::ui::theme::c_text()));
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
if row_idx < state.custom_partitions.len() - 1 {
|
||||
ui.separator();
|
||||
}
|
||||
ui.end_row();
|
||||
}
|
||||
});
|
||||
|
||||
// Silme onayı
|
||||
if let Some(idx) = delete_idx {
|
||||
|
||||
+12
-16
@@ -238,18 +238,15 @@ impl RescueStep {
|
||||
// İşlem butonları
|
||||
ui.horizontal(|ui| {
|
||||
// 1. Bağla
|
||||
if ui.add_enabled(
|
||||
self.selected.is_some() && !self.mount_done,
|
||||
theme::secondary_button("📂 /mnt'a Bağla"),
|
||||
).clicked() {
|
||||
let cond = self.selected.is_some() && !self.mount_done;
|
||||
let resp = theme::secondary_button(ui, "📂 /mnt'a Bağla");
|
||||
if cond && resp.clicked() {
|
||||
self.mount_selected();
|
||||
}
|
||||
|
||||
// 2. Chroot
|
||||
if ui.add_enabled(
|
||||
self.mount_done,
|
||||
theme::secondary_button(&t!("rescue_chroot")),
|
||||
).clicked() {
|
||||
let resp = theme::secondary_button(ui, &t!("rescue_chroot"));
|
||||
if self.mount_done && resp.clicked() {
|
||||
self.log.push(t!("rescue_chroot_start").to_string());
|
||||
match Self::run_rescue_cmd("chroot", &["/mnt", "/bin/bash", "-c", "echo 'chroot OK'"]) {
|
||||
Ok(msg) => self.log.push(format!("✓ {}", msg)),
|
||||
@@ -258,10 +255,8 @@ impl RescueStep {
|
||||
}
|
||||
|
||||
// 3. GRUB yeniden yükle
|
||||
if ui.add_enabled(
|
||||
self.mount_done,
|
||||
theme::secondary_button(&t!("rescue_grub")),
|
||||
).clicked() {
|
||||
let resp = theme::secondary_button(ui, &t!("rescue_grub"));
|
||||
if self.mount_done && resp.clicked() {
|
||||
self.log.push(t!("rescue_grub_start").to_string());
|
||||
let boot_dev = state.bootloader_device.clone();
|
||||
let steps: &[(&str, &[&str])] = &[
|
||||
@@ -311,7 +306,7 @@ impl RescueStep {
|
||||
|
||||
// Tara butonu
|
||||
ui.horizontal(|ui| {
|
||||
if ui.add(theme::secondary_button(&t!("rescue_pisi_history_scan"))).clicked() {
|
||||
if theme::secondary_button(ui, &t!("rescue_pisi_history_scan")).clicked() {
|
||||
self.scan_pisi_history();
|
||||
}
|
||||
if self.pisi.scanned {
|
||||
@@ -392,7 +387,8 @@ impl RescueStep {
|
||||
|
||||
// Geri al butonu
|
||||
let can_takeback = self.pisi.selected.is_some() && !self.pisi.running;
|
||||
if ui.add_enabled(can_takeback, theme::primary_button(&t!("rescue_pisi_takeback"))).clicked() {
|
||||
let resp = theme::primary_button(ui, &t!("rescue_pisi_takeback"));
|
||||
if can_takeback && resp.clicked() {
|
||||
if let Some(idx) = self.pisi.selected {
|
||||
let op = self.pisi.entries[idx].op;
|
||||
self.run_takeback(op);
|
||||
@@ -430,7 +426,7 @@ impl InstallerStep for RescueStep {
|
||||
if self.detected.is_empty() {
|
||||
theme::warning_box(ui, &t!("rescue_no_installations"));
|
||||
ui.add_space(6.0);
|
||||
if ui.add(theme::secondary_button(&t!("rescue_scan"))).clicked() {
|
||||
if theme::secondary_button(ui, &t!("rescue_scan")).clicked() {
|
||||
self.detect_installations();
|
||||
}
|
||||
return;
|
||||
@@ -505,7 +501,7 @@ impl InstallerStep for RescueStep {
|
||||
|
||||
// Geri dön butonu
|
||||
ui.add_space(8.0);
|
||||
if ui.add(theme::secondary_button(&t!("rescue_return"))).clicked() {
|
||||
if theme::secondary_button(ui, &t!("rescue_return")).clicked() {
|
||||
state.rescue_mode = false;
|
||||
}
|
||||
}
|
||||
|
||||
+100
-132
@@ -1,24 +1,21 @@
|
||||
use eframe::egui;
|
||||
use rust_i18n::t;
|
||||
use crate::installer::{GlobalState, InstallerStep};
|
||||
use crate::funct::{ form_input_text, form_input_password, form_label, form_checkbox };
|
||||
|
||||
/// Kullanıcı adı, şifre, hostname ve yönetici (root) şifresi ayarları adımı.
|
||||
pub struct UsersStep {
|
||||
use_same_for_admin: bool,
|
||||
show_password: bool,
|
||||
show_password_confirm: bool,
|
||||
show_root_password: bool,
|
||||
show_root_password_confirm: bool,
|
||||
use_same_for_admin: bool,
|
||||
}
|
||||
|
||||
impl Default for UsersStep {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
use_same_for_admin: true,
|
||||
show_password: false,
|
||||
show_password_confirm: false,
|
||||
show_root_password: false,
|
||||
show_root_password_confirm: false,
|
||||
use_same_for_admin: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -152,47 +149,47 @@ impl InstallerStep for UsersStep {
|
||||
);
|
||||
ui.add_space(12.0);
|
||||
|
||||
// ─── Kullanıcı Bilgileri Grubu ──────────────────────────────────
|
||||
// ─── Kullanıcı Bilgileri Formu ──────────────────────────────────
|
||||
egui::Frame::group(ui.style())
|
||||
.fill(theme::c_bg_widget())
|
||||
.rounding(6.0)
|
||||
.inner_margin(egui::Margin::same(12.0))
|
||||
.inner_margin(egui::Margin::symmetric(10.0, 10.0))
|
||||
.show(ui, |ui| {
|
||||
ui.set_width(ui.available_width());
|
||||
egui::Grid::new("users_grid")
|
||||
.num_columns(2)
|
||||
.spacing([12.0, 10.0])
|
||||
.show(ui, |ui| {
|
||||
// Gerçek ad
|
||||
ui.label(egui::RichText::new(t!("real_name_label")).strong().color(theme::c_text()));
|
||||
ui.horizontal(|ui| {
|
||||
let prev = state.real_name.clone();
|
||||
ui.text_edit_singleline(&mut state.real_name);
|
||||
if state.real_name != prev && !state.real_name.is_empty() {
|
||||
state.username = derive_username(&state.real_name);
|
||||
}
|
||||
if !state.real_name.is_empty() {
|
||||
ui.colored_label(theme::c_success(), "✓");
|
||||
}
|
||||
});
|
||||
ui.end_row();
|
||||
ui.horizontal(|ui| {
|
||||
ui.vertical(|ui| {
|
||||
ui.add_space(10.0);
|
||||
|
||||
// Gerçek ad
|
||||
form_label(ui, &t!("real_name_label"));
|
||||
{
|
||||
let prev = state.real_name.clone();
|
||||
form_input_text(ui, &mut state.real_name);
|
||||
if state.real_name != prev && !state.real_name.is_empty() {
|
||||
state.username = derive_username(&state.real_name);
|
||||
}
|
||||
}
|
||||
if !state.real_name.is_empty() {
|
||||
ui.colored_label(theme::c_success(), "✅");
|
||||
} else {
|
||||
ui.colored_label(theme::c_error(), "❌");
|
||||
}
|
||||
ui.add_space(6.0);
|
||||
|
||||
// Kullanıcı adı (otomatik türetilir, düzenlenebilir)
|
||||
ui.label(egui::RichText::new(t!("username_label")).strong().color(theme::c_text()));
|
||||
ui.horizontal(|ui| {
|
||||
ui.text_edit_singleline(&mut state.username);
|
||||
if !state.username.is_empty() {
|
||||
if is_valid_username(&state.username) {
|
||||
ui.colored_label(theme::c_success(), "✓");
|
||||
} else {
|
||||
ui.colored_label(theme::c_error(), "✗");
|
||||
}
|
||||
}
|
||||
});
|
||||
ui.end_row();
|
||||
// Kullanıcı adı (otomatik türetilir, düzenlenebilir)
|
||||
form_label(ui, &t!("username_label"));
|
||||
form_input_text(ui, &mut state.username);
|
||||
if !state.username.is_empty() {
|
||||
if is_valid_username(&state.username) {
|
||||
ui.colored_label(theme::c_success(), "✅");
|
||||
} else {
|
||||
ui.colored_label(theme::c_warning(), t!("username_invalid"));
|
||||
}
|
||||
}
|
||||
ui.add_space(6.0);
|
||||
|
||||
// Şifre
|
||||
ui.label(egui::RichText::new(t!("password_label")).strong().color(theme::c_text()));
|
||||
form_label(ui, &t!("password_label"));
|
||||
// OEM modunda varsayılan şifre uyarısı göster
|
||||
if state.oem_mode && !state.oem_password_changed {
|
||||
ui.colored_label(
|
||||
@@ -200,68 +197,56 @@ impl InstallerStep for UsersStep {
|
||||
t!("oem_change_password_warning"),
|
||||
);
|
||||
}
|
||||
ui.horizontal(|ui| {
|
||||
{
|
||||
let show_pwd = &mut self.show_password;
|
||||
let before = state.password.clone();
|
||||
if self.show_password {
|
||||
ui.text_edit_singleline(&mut state.password);
|
||||
} else {
|
||||
ui.add(egui::TextEdit::singleline(&mut state.password).password(true));
|
||||
}
|
||||
form_input_password(ui, &mut state.password, show_pwd);
|
||||
// Kullanıcı şifreyi değiştirdiyse OEM bayrağını kaldır
|
||||
if state.oem_mode && state.password != before {
|
||||
state.oem_password_changed = true;
|
||||
}
|
||||
if ui.small_button(if self.show_password { "🙈" } else { "👁" }).clicked() {
|
||||
self.show_password = !self.show_password;
|
||||
}
|
||||
if !state.password.is_empty() {
|
||||
let (label, color) = password_strength(&state.password);
|
||||
ui.colored_label(color, label);
|
||||
}
|
||||
});
|
||||
ui.end_row();
|
||||
}
|
||||
if !state.password.is_empty() {
|
||||
let (label, color) = password_strength(&state.password);
|
||||
ui.colored_label(color, label);
|
||||
}
|
||||
ui.add_space(6.0);
|
||||
|
||||
// Şifre doğrulama
|
||||
ui.label(egui::RichText::new(t!("password_confirm_label")).strong().color(theme::c_text()));
|
||||
ui.horizontal(|ui| {
|
||||
if self.show_password_confirm {
|
||||
ui.text_edit_singleline(&mut state.password_confirm);
|
||||
form_label(ui, &t!("password_confirm_label"));
|
||||
{
|
||||
let show_pwd = &mut self.show_password_confirm;
|
||||
form_input_password(ui, &mut state.password_confirm, show_pwd);
|
||||
}
|
||||
if !state.password_confirm.is_empty() {
|
||||
if state.password == state.password_confirm {
|
||||
ui.colored_label(theme::c_success(), "✅");
|
||||
} else {
|
||||
ui.add(egui::TextEdit::singleline(&mut state.password_confirm).password(true));
|
||||
ui.colored_label(theme::c_warning(), t!("passwords_no_match"));
|
||||
}
|
||||
if ui.small_button(if self.show_password_confirm { "🙈" } else { "👁" }).clicked() {
|
||||
self.show_password_confirm = !self.show_password_confirm;
|
||||
}
|
||||
if !state.password_confirm.is_empty() {
|
||||
if state.password == state.password_confirm {
|
||||
ui.colored_label(theme::c_success(), "✓");
|
||||
} else {
|
||||
ui.colored_label(theme::c_error(), t!("passwords_no_match"));
|
||||
}
|
||||
}
|
||||
});
|
||||
ui.end_row();
|
||||
}
|
||||
ui.add_space(6.0);
|
||||
|
||||
// Hostname
|
||||
ui.label(egui::RichText::new(t!("hostname_label")).strong().color(theme::c_text()));
|
||||
ui.horizontal(|ui| {
|
||||
ui.text_edit_singleline(&mut state.hostname);
|
||||
if !state.hostname.is_empty() {
|
||||
if is_valid_hostname(&state.hostname) {
|
||||
ui.colored_label(theme::c_success(), "✓");
|
||||
} else {
|
||||
ui.colored_label(theme::c_error(), "✗");
|
||||
}
|
||||
form_label(ui, &t!("hostname_label"));
|
||||
form_input_text(ui, &mut state.hostname);
|
||||
if !state.hostname.is_empty() {
|
||||
if is_valid_hostname(&state.hostname) {
|
||||
ui.colored_label(theme::c_success(), "✅");
|
||||
} else {
|
||||
ui.colored_label(theme::c_warning(), t!("hostname_invalid"));
|
||||
}
|
||||
});
|
||||
ui.end_row();
|
||||
}
|
||||
|
||||
});
|
||||
ui.add_space(10.0);
|
||||
});
|
||||
});
|
||||
|
||||
ui.add_space(14.0);
|
||||
|
||||
// ─── Yönetici (root) Ayarları ─────────────────────────────────────
|
||||
ui.checkbox(&mut self.use_same_for_admin, egui::RichText::new(t!("use_same_for_admin")).strong().color(theme::c_text()));
|
||||
form_checkbox(ui, &mut self.use_same_for_admin, &t!("use_same_for_admin"));
|
||||
|
||||
if self.use_same_for_admin {
|
||||
state.root_password = state.password.clone();
|
||||
@@ -277,59 +262,42 @@ impl InstallerStep for UsersStep {
|
||||
ui.label(egui::RichText::new(t!("admin_description")).size(12.0).color(theme::c_text_dim()));
|
||||
ui.add_space(8.0);
|
||||
|
||||
egui::Grid::new("admin_grid")
|
||||
.num_columns(2)
|
||||
.spacing([12.0, 10.0])
|
||||
.show(ui, |ui| {
|
||||
// Root şifresi
|
||||
ui.label(egui::RichText::new(t!("admin_password_label")).strong().color(theme::c_text()));
|
||||
ui.horizontal(|ui| {
|
||||
if self.show_root_password {
|
||||
ui.text_edit_singleline(&mut state.root_password);
|
||||
} else {
|
||||
ui.add(egui::TextEdit::singleline(&mut state.root_password).password(true));
|
||||
}
|
||||
if ui.small_button(if self.show_root_password { "🙈" } else { "👁" }).clicked() {
|
||||
self.show_root_password = !self.show_root_password;
|
||||
}
|
||||
if !state.root_password.is_empty() && state.root_password.len() < 8 {
|
||||
ui.colored_label(theme::c_error(), t!("admin_password_short"));
|
||||
}
|
||||
});
|
||||
ui.end_row();
|
||||
// Root şifresi
|
||||
|
||||
ui.label(egui::RichText::new(t!("admin_password_label")).strong().color(theme::c_text()));
|
||||
ui.horizontal(|ui| {
|
||||
ui.add(
|
||||
eframe::egui::TextEdit::singleline(&mut state.root_password)
|
||||
.password(true)
|
||||
.desired_width(f32::INFINITY)
|
||||
.min_size(eframe::egui::vec2(0.0, 32.0))
|
||||
);
|
||||
|
||||
// Şifre doğrulama
|
||||
ui.label(egui::RichText::new(t!("admin_password_confirm_label")).strong().color(theme::c_text()));
|
||||
ui.horizontal(|ui| {
|
||||
if self.show_root_password_confirm {
|
||||
ui.text_edit_singleline(&mut state.root_password_confirm);
|
||||
} else {
|
||||
ui.add(egui::TextEdit::singleline(&mut state.root_password_confirm).password(true));
|
||||
}
|
||||
if ui.small_button(if self.show_root_password_confirm { "🙈" } else { "👁" }).clicked() {
|
||||
self.show_root_password_confirm = !self.show_root_password_confirm;
|
||||
}
|
||||
if !state.root_password_confirm.is_empty() {
|
||||
if state.root_password == state.root_password_confirm {
|
||||
ui.colored_label(theme::c_success(), "✓");
|
||||
} else {
|
||||
ui.colored_label(theme::c_error(), t!("passwords_no_match"));
|
||||
}
|
||||
}
|
||||
});
|
||||
ui.end_row();
|
||||
});
|
||||
if !state.root_password.is_empty() && state.root_password.len() < 8 {
|
||||
ui.colored_label(theme::c_error(), t!("admin_password_short"));
|
||||
}
|
||||
});
|
||||
ui.add_space(8.0);
|
||||
|
||||
// Şifre doğrulama
|
||||
ui.label(egui::RichText::new(t!("admin_password_confirm_label")).strong().color(theme::c_text()));
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
ui.add(
|
||||
eframe::egui::TextEdit::singleline(&mut state.root_password_confirm)
|
||||
.password(true)
|
||||
.desired_width(f32::INFINITY)
|
||||
.min_size(eframe::egui::vec2(0.0, 32.0))
|
||||
);
|
||||
});
|
||||
if state.root_password == state.root_password_confirm {
|
||||
ui.colored_label(theme::c_success(), "✅");
|
||||
} else {
|
||||
ui.colored_label(theme::c_error(), t!("passwords_no_match"));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Hata mesajları gösterimi
|
||||
ui.add_space(8.0);
|
||||
if !state.username.is_empty() && !is_valid_username(&state.username) {
|
||||
ui.colored_label(theme::c_error(), t!("username_invalid"));
|
||||
}
|
||||
if !state.hostname.is_empty() && !is_valid_hostname(&state.hostname) {
|
||||
ui.colored_label(theme::c_error(), t!("hostname_invalid"));
|
||||
}
|
||||
}
|
||||
|
||||
fn is_complete(&self, state: &GlobalState) -> bool {
|
||||
|
||||
@@ -136,13 +136,13 @@ impl ErrorScreen {
|
||||
let side = (ui.available_width() - total) / 2.0;
|
||||
ui.add_space(side.max(0.0));
|
||||
|
||||
if ui.add(theme::danger_button(&t!("error_quit"))).clicked() {
|
||||
if theme::danger_button(ui, &t!("error_quit")).clicked() {
|
||||
on_quit();
|
||||
}
|
||||
|
||||
ui.add_space(gap);
|
||||
|
||||
if ui.add(theme::primary_button(&t!("error_retry"))).clicked() {
|
||||
if theme::primary_button(ui, &t!("error_retry")).clicked() {
|
||||
on_retry();
|
||||
}
|
||||
});
|
||||
|
||||
+212
-57
@@ -23,6 +23,10 @@ static WARNING: AtomicU32 = AtomicU32::new(0xFF9800);
|
||||
static ERROR: AtomicU32 = AtomicU32::new(0xF44336);
|
||||
static SIDEBAR: AtomicU32 = AtomicU32::new(0x0B0B0D);
|
||||
static ACCENT_COLOR: AtomicU32 = AtomicU32::new(0xd9005b); // Pisi Premium Vurgu (#d9005b)
|
||||
static SIDEBAR_TEXT: AtomicU32 = AtomicU32::new(0x000000);
|
||||
|
||||
|
||||
|
||||
|
||||
// Helper to convert hex to Color32
|
||||
fn color_from_hex(rgb: u32) -> Color32 {
|
||||
@@ -39,6 +43,8 @@ pub fn c_success() -> Color32 { color_from_hex(SUCCESS.load(Ordering::Relaxed)
|
||||
pub fn c_warning() -> Color32 { color_from_hex(WARNING.load(Ordering::Relaxed)) }
|
||||
pub fn c_error() -> Color32 { color_from_hex(ERROR.load(Ordering::Relaxed)) }
|
||||
pub fn c_sidebar() -> Color32 { color_from_hex(SIDEBAR.load(Ordering::Relaxed)) }
|
||||
pub fn c_sidebar_text() -> Color32 { color_from_hex(SIDEBAR_TEXT.load(Ordering::Relaxed)) }
|
||||
pub fn c_footer_bg() -> Color32 { color_from_hex(0xf7f7f7) }
|
||||
|
||||
pub fn set_theme_mode(is_dark: bool) {
|
||||
if is_dark {
|
||||
@@ -50,13 +56,19 @@ pub fn set_theme_mode(is_dark: bool) {
|
||||
TEXT_DIM.store(0x9E9EAF, Ordering::Relaxed);
|
||||
SIDEBAR.store(0x0B0B0D, Ordering::Relaxed);
|
||||
} else {
|
||||
BG_DARK.store(0xF0F0F5, Ordering::Relaxed);
|
||||
BG_PANEL.store(0xFFFFFF, Ordering::Relaxed);
|
||||
BG_WIDGET.store(0xE8E8EE, Ordering::Relaxed);
|
||||
BORDER.store(0xD0D0DA, Ordering::Relaxed);
|
||||
TEXT.store(0x20202A, Ordering::Relaxed);
|
||||
TEXT_DIM.store(0x60606A, Ordering::Relaxed);
|
||||
SIDEBAR.store(0xE4E4EA, Ordering::Relaxed);
|
||||
BG_DARK.store(0xf7f7f7, Ordering::Relaxed);
|
||||
SIDEBAR.store(0x292f34, Ordering::Relaxed);
|
||||
TEXT.store(0x000000, Ordering::Relaxed);
|
||||
TEXT_DIM.store(0x000000, Ordering::Relaxed);
|
||||
SIDEBAR_TEXT.store(0xffffff, Ordering::Relaxed);
|
||||
|
||||
// BG_DARK.store(0xF0F0F5, Ordering::Relaxed);
|
||||
// BG_PANEL.store(0xFFFFFF, Ordering::Relaxed);
|
||||
// BG_WIDGET.store(0xE8E8EE, Ordering::Relaxed);
|
||||
// BORDER.store(0xD0D0DA, Ordering::Relaxed);
|
||||
// TEXT.store(0x20202A, Ordering::Relaxed);
|
||||
// TEXT_DIM.store(0x60606A, Ordering::Relaxed);
|
||||
// SIDEBAR.store(0xE4E4EA, Ordering::Relaxed);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,32 +97,59 @@ pub fn apply(ctx: &egui::Context, is_dark: bool) {
|
||||
fn setup_fonts(ctx: &egui::Context) {
|
||||
let mut fonts = egui::FontDefinitions::default();
|
||||
|
||||
// egui'nin yerleşik fontlarına ek olarak sistem fontunu dene.
|
||||
// ISO ortamında genellikle DejaVu veya Noto bulunur.
|
||||
// Bulunamazsa egui varsayılanı kullanılır.
|
||||
for path in [
|
||||
"/usr/share/fonts/exo-2/Exo2-Medium.ttf",
|
||||
"/usr/share/fonts/exo-2/Exo2-Regular.ttf",
|
||||
"/usr/share/fonts/google-exo2/Exo2-Medium.ttf",
|
||||
"/usr/share/fonts/google-exo2/Exo2-Regular.ttf",
|
||||
// 1. ADIM: Sadece Metin (Text) Fontunu Bul ve Yükle
|
||||
let text_font_paths = [
|
||||
"/usr/share/fonts/TTF/OpenSans-Regular.ttf",
|
||||
"/usr/share/fonts/noto/NotoSans-Regular.ttf",
|
||||
"/usr/share/fonts/exo-1/Exo-Regular.otf",
|
||||
"/usr/share/fonts/exo-1/Exo2-Medium.ttf",
|
||||
"/usr/share/fonts/exo-1/Exo-Medium.otf",
|
||||
"/usr/share/fonts/exo-1/Exo-Regular.otf",
|
||||
"/usr/share/fonts/truetype/exo2/Exo2-Medium.ttf",
|
||||
"/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf",
|
||||
"/usr/share/fonts/noto/NotoSans-Regular.ttf",
|
||||
"/usr/share/fonts/TTF/DejaVuSans.ttf",
|
||||
] {
|
||||
];
|
||||
|
||||
for path in text_font_paths {
|
||||
if let Ok(data) = std::fs::read(path) {
|
||||
fonts.font_data.insert(
|
||||
"system_font".to_owned(),
|
||||
"system_text_font".to_owned(),
|
||||
egui::FontData::from_owned(data),
|
||||
);
|
||||
|
||||
// Metin fontunu en başa (0. indeks) koyuyoruz ki varsayılan olarak bu kullanılsın
|
||||
fonts
|
||||
.families
|
||||
.entry(egui::FontFamily::Proportional)
|
||||
.or_default()
|
||||
.insert(0, "system_font".to_owned());
|
||||
.insert(0, "system_text_font".to_owned());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 2. ADIM: Renkli Emoji Fontunu Ara ve "Fallback" (Yedek) Olarak Sona Ekle
|
||||
// ISO ortamlarında Noto Color Emoji genellikle bu yollardan birinde olur
|
||||
let emoji_font_paths = [
|
||||
"/usr/share/fonts/noto/NotoColorEmoji.ttf",
|
||||
];
|
||||
|
||||
for path in emoji_font_paths {
|
||||
if let Ok(data) = std::fs::read(path) {
|
||||
fonts.font_data.insert(
|
||||
"system_emoji_font".to_owned(),
|
||||
egui::FontData::from_owned(data),
|
||||
);
|
||||
|
||||
// ÖNEMLİ: Emojiyi listenin SONUNA (push ile) ekliyoruz.
|
||||
// Böylece egui normal harfleri sistem fontuyla yazacak,
|
||||
// harf sistem fontunda yoksa (yani emojiyse) gidip bu fonttan alacak.
|
||||
fonts
|
||||
.families
|
||||
.entry(egui::FontFamily::Proportional)
|
||||
.or_default()
|
||||
.push("system_emoji_font".to_owned());
|
||||
|
||||
fonts
|
||||
.families
|
||||
.entry(egui::FontFamily::Monospace)
|
||||
.or_default()
|
||||
.insert(0, "system_emoji_font".to_owned());
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -118,6 +157,57 @@ fn setup_fonts(ctx: &egui::Context) {
|
||||
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 {
|
||||
let mut v = if is_dark { Visuals::dark() } else { Visuals::light() };
|
||||
|
||||
@@ -131,25 +221,31 @@ fn build_visuals(is_dark: bool) -> Visuals {
|
||||
// Metin
|
||||
v.override_text_color = None;
|
||||
|
||||
// Kenarlıklar
|
||||
// ─── KENARLIKLAR (Calamares Uyumu için İnce İnce Yumuşatıldı) ───────────────────
|
||||
v.widgets.noninteractive.bg_stroke = Stroke::new(1.0, c_border());
|
||||
v.widgets.inactive.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 dolguları
|
||||
// ─── WIDGET DOLGULARI (Burası ComboBox ve Butonların Kaderini Belirliyor) ───────
|
||||
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();
|
||||
|
||||
// Pasif Hali: Calamares Mavisi (İstiyorsan c_accent() ile Pisi Vişne Rengine de Çekebilirsin)
|
||||
v.widgets.inactive.bg_fill = Color32::from_rgb(46, 124, 219);
|
||||
|
||||
// Üzerine Gelme Hali: Parlayan Açık Mavi
|
||||
v.widgets.hovered.bg_fill = Color32::from_rgb(59, 144, 240);
|
||||
|
||||
// Tıklanma/Açık Kalma Hali: Koyu Lacivert/Mavi
|
||||
v.widgets.active.bg_fill = Color32::from_rgb(20, 70, 140);
|
||||
|
||||
// Metin renkleri
|
||||
// ─── METİN RENKLERİ ────────────────────────────────────────────────────────────
|
||||
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.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
|
||||
// ─── YUVARLAK KÖŞELER (Zaten 6.0'dı, Tam İstediğin Calamares Kıvamında) ─────────
|
||||
v.widgets.noninteractive.rounding = Rounding::same(6.0);
|
||||
v.widgets.inactive.rounding = Rounding::same(6.0);
|
||||
v.widgets.hovered.rounding = Rounding::same(6.0);
|
||||
@@ -193,31 +289,90 @@ fn build_style() -> egui::Style {
|
||||
|
||||
// ─── Yardımcı widget'lar ─────────────────────────────────────────
|
||||
|
||||
/// Vurgulu "ana eylem" butonu (mavi dolgu).
|
||||
pub fn primary_button(text: &str) -> egui::Button<'_> {
|
||||
egui::Button::new(egui::RichText::new(text).color(Color32::WHITE))
|
||||
.fill(c_accent())
|
||||
.stroke(Stroke::new(1.0, c_accent()))
|
||||
.rounding(Rounding::same(6.0))
|
||||
.min_size(Vec2::new(100.0, 32.0))
|
||||
fn lerp_color(a: Color32, b: Color32, t: f32) -> Color32 {
|
||||
Color32::from_rgba_premultiplied(
|
||||
(a.r() as f32 + (b.r() as f32 - a.r() as f32) * t) as u8,
|
||||
(a.g() as f32 + (b.g() as f32 - a.g() as f32) * t) as u8,
|
||||
(a.b() as f32 + (b.b() as f32 - a.b() as f32) * t) as u8,
|
||||
(a.a() as f32 + (b.a() as f32 - a.a() as f32) * t) as u8,
|
||||
)
|
||||
}
|
||||
|
||||
/// İkincil "iptal/geri" butonu (şeffaf dolgu).
|
||||
pub fn secondary_button(text: &str) -> egui::Button<'_> {
|
||||
egui::Button::new(egui::RichText::new(text).color(c_text_dim()))
|
||||
.fill(Color32::TRANSPARENT)
|
||||
.stroke(Stroke::new(1.0, c_border()))
|
||||
.rounding(Rounding::same(6.0))
|
||||
.min_size(Vec2::new(80.0, 32.0))
|
||||
fn gradient_button(ui: &mut egui::Ui, text: &str, top_color: Color32, bottom_color: Color32, text_color: Color32) -> egui::Response {
|
||||
let rounding = Rounding::same(6.0);
|
||||
let min_size = Vec2::new(100.0, 32.0);
|
||||
let padding = Vec2::new(12.0, 6.0);
|
||||
|
||||
let font_id = ui.style().text_styles.get(&egui::TextStyle::Button)
|
||||
.cloned().unwrap_or(FontId::proportional(14.0));
|
||||
let galley = ui.painter().layout_no_wrap(
|
||||
text.to_string(),
|
||||
font_id,
|
||||
text_color,
|
||||
);
|
||||
|
||||
let size = Vec2::new(
|
||||
min_size.x.max(galley.size().x + 2.0 * padding.x),
|
||||
min_size.y.max(galley.size().y + 2.0 * padding.y),
|
||||
);
|
||||
|
||||
let (rect, response) = ui.allocate_exact_size(size, egui::Sense::click());
|
||||
|
||||
// Gradient arka plan (16 yatay şerit)
|
||||
let n_strips = 16;
|
||||
for i in 0..n_strips {
|
||||
let t = i as f32 / n_strips as f32;
|
||||
let t2 = (i + 1) as f32 / n_strips as f32;
|
||||
let c = lerp_color(top_color, bottom_color, (t + t2) * 0.5);
|
||||
let y0 = rect.top() + rect.height() * t;
|
||||
let y1 = rect.top() + rect.height() * t2;
|
||||
let strip_rect = egui::Rect::from_min_max(
|
||||
egui::pos2(rect.left(), y0),
|
||||
egui::pos2(rect.right(), y1),
|
||||
);
|
||||
ui.painter().rect(strip_rect, Rounding::ZERO, c, Stroke::NONE);
|
||||
}
|
||||
|
||||
// Yuvarlak köşe efekti için border
|
||||
ui.painter().rect(rect, rounding, Color32::TRANSPARENT, Stroke::new(1.0, bottom_color));
|
||||
|
||||
// Hover efekti
|
||||
if response.hovered() {
|
||||
ui.painter().rect(rect, rounding, Color32::WHITE.linear_multiply(0.10), Stroke::NONE);
|
||||
}
|
||||
|
||||
// Metin
|
||||
ui.painter().galley(
|
||||
egui::pos2(rect.center().x - galley.size().x / 2.0, rect.center().y - galley.size().y / 2.0),
|
||||
galley,
|
||||
text_color,
|
||||
);
|
||||
|
||||
response
|
||||
}
|
||||
|
||||
/// Kırmızı "tehlikeli eylem" butonu (disk silme onayı vb.).
|
||||
pub fn danger_button(text: &str) -> egui::Button<'_> {
|
||||
egui::Button::new(egui::RichText::new(text).color(Color32::WHITE))
|
||||
.fill(Color32::from_rgb(0xC6, 0x28, 0x28))
|
||||
.stroke(Stroke::new(1.0, c_error()))
|
||||
.rounding(Rounding::same(6.0))
|
||||
.min_size(Vec2::new(100.0, 32.0))
|
||||
/// Ana eylem butonu (mavi gradient).
|
||||
pub fn primary_button(ui: &mut egui::Ui, text: &str) -> egui::Response {
|
||||
gradient_button(ui, text,
|
||||
Color32::from_rgb(50, 130, 255), // üst: açık mavi
|
||||
Color32::from_rgb(13, 110, 253), // alt: koyu mavi
|
||||
Color32::WHITE)
|
||||
}
|
||||
|
||||
/// İkincil buton (gri gradient).
|
||||
pub fn secondary_button(ui: &mut egui::Ui, text: &str) -> egui::Response {
|
||||
gradient_button(ui, text,
|
||||
Color32::from_rgb(130, 140, 150), // üst: açık gri
|
||||
Color32::from_rgb(100, 110, 120), // alt: koyu gri
|
||||
c_text_dim())
|
||||
}
|
||||
|
||||
/// İptal/Danger butonu (kırmızı gradient).
|
||||
pub fn danger_button(ui: &mut egui::Ui, text: &str) -> egui::Response {
|
||||
gradient_button(ui, text,
|
||||
Color32::from_rgb(240, 83, 99), // üst: açık kırmızı
|
||||
Color32::from_rgb(200, 40, 60), // alt: koyu kırmızı
|
||||
Color32::WHITE)
|
||||
}
|
||||
|
||||
/// Bölüm başlığı — büyük, açık renkli, alt çizgisiz.
|
||||
@@ -246,7 +401,7 @@ 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.colored_label(c_error(), "❌");
|
||||
ui.colored_label(c_error(), msg);
|
||||
});
|
||||
});
|
||||
@@ -261,7 +416,7 @@ 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.colored_label(c_warning(), "⚠️");
|
||||
ui.colored_label(c_warning(), msg);
|
||||
});
|
||||
});
|
||||
@@ -276,7 +431,7 @@ 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.colored_label(c_success(), "✅");
|
||||
ui.colored_label(c_success(), msg);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user