This commit is contained in:
2026-05-22 23:35:27 +03:00
parent 70c0304fea
commit a1ae4efede
45 changed files with 6769 additions and 93 deletions
+405
View File
@@ -0,0 +1,405 @@
mod installer;
mod steps;
mod jobs;
mod ui;
mod autoinstall;
use installer::{GlobalState, InstallerStep, WelcomeStep, LocationStep};
use steps::{KeyboardStep, UsersStep, SummaryStep, ExecutionStep, FinishStep, PartitionStep};
use eframe::egui;
use rust_i18n::t;
rust_i18n::i18n!("locales");
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) {
return run_auto_install(path, demo_mode);
} else {
eprintln!("Hata: --auto-install <dosya.toml> şeklinde kullanın.");
std::process::exit(1);
}
}
run_gui(demo_mode)
}
/// 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([1100.0, 760.0])
.with_min_inner_size([900.0, 650.0])
.with_title(t!("installer").to_string()),
..Default::default()
};
eframe::run_native(
&t!("installer").to_string(),
options,
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);
let mut app = YaliApp::default();
app.state.demo_mode = demo_mode;
Box::new(app)
}),
)
}
/// Answer file ile tam otomatik, UI-siz kurulum.
fn run_auto_install(path: &str, demo_mode: bool) -> eframe::Result<()> {
use autoinstall::{load, apply_to_state};
use std::path::Path;
println!("=== Yali-RS Otomatik Kurulum ===");
println!("{}: {}", t!("answer_file"), path);
// Dosyayı yükle ve doğrula
let af = match load(Path::new(path)) {
Ok(af) => af,
Err(e) => {
eprintln!("{}: {}", t!("error"), e);
std::process::exit(1);
}
};
println!("{}: {}", t!("disk"), af.install.disk);
println!("{}: {}", t!("timezone"), af.install.timezone);
println!("{}: {}", t!("username"), af.user.username);
println!("{}: {}", t!("hostname"), af.user.hostname);
// Sistem kontrollerini çalıştır
println!("\n--- {} ---", t!("system_checks"));
let checks = autoinstall::checker::run_all();
for c in &checks {
let sym = match c.status {
autoinstall::checker::CheckStatus::Pass => "✓",
autoinstall::checker::CheckStatus::Warn => "⚠",
autoinstall::checker::CheckStatus::Fail => "✗",
};
println!("[{}] {}: {}", sym, c.name, c.message);
}
if !autoinstall::checker::all_passed(&checks) {
eprintln!("{}: {}", t!("error"), "Critical check error. Installation stopped.");
std::process::exit(2);
}
// State'i hazırla
let mut state = GlobalState::default();
state.demo_mode = demo_mode;
apply_to_state(&af, &mut state);
if state.partition_plan.is_none() {
eprintln!("{}: {}", t!("error"), format!("{}: {}", t!("partition_plan_could_not_be_created"), af.install.disk));
std::process::exit(3);
}
println!("\n--- {} ---", t!("installation_started"));
// JobQueue'yu oluştur ve tokio'da çalıştır
let locale = af.install.locale.clone();
let timezone = af.install.timezone.clone();
let username = af.user.username.clone();
let password = af.user.password.clone();
let hostname = af.user.hostname.clone();
let kb_layout = af.install.keyboard.clone();
let kb_variant = af.install.keyboard_variant.clone();
let source = af.install.source.clone();
let mount = af.install.mount.clone();
let plan = state.partition_plan.unwrap();
let rt = tokio::runtime::Runtime::new().expect("tokio runtime");
rt.block_on(async {
use std::sync::mpsc;
use jobs::{InstallMessage, UiSender};
// CLI modunda egui Context yok; basit bir mpsc kanal kullanıyoruz.
// UiSender'a dummy bir context gerekeceğinden terminal çıktısı için
// doğrudan kanalı dinliyoruz.
let (tx, rx) = mpsc::channel::<InstallMessage>();
// Arka plan iş parçacığı — gerçek job kuyruğunu çalıştırır.
// egui::Context::default() CLI modunda request_repaint()'i no-op yapar;
// mesajlar mpsc kanalı üzerinden terminale aktarılır.
let cli_ctx = eframe::egui::Context::default();
let ui_sender = UiSender::new(tx, cli_ctx);
let plan_c = plan.clone();
let locale_c = locale.clone();
let timezone_c = timezone.clone();
let username_c = username.clone();
let password_c = password.clone();
let hostname_c = hostname.clone();
let kb_layout_c = kb_layout.clone();
let kb_variant_c= kb_variant.clone();
let source_c = source.clone();
let mount_c = mount.clone();
std::thread::spawn(move || {
let rt = tokio::runtime::Runtime::new().expect("tokio runtime");
let mut queue = jobs::build_full_job_queue(
plan_c,
&mount_c,
&source_c,
&locale_c,
&timezone_c,
&username_c,
&password_c,
&hostname_c,
&kb_layout_c,
&kb_variant_c,
demo_mode,
);
rt.block_on(queue.run_all(ui_sender));
});
// Kanalı oku ve terminale yaz
loop {
match rx.recv() {
Ok(InstallMessage::Log(line)) => println!(" {}", line),
Ok(InstallMessage::Progress(p)) => {
print!("\r {}: [{:50}] {:.0}%", t!("progress"), "=".repeat((p * 50.0) as usize), p * 100.0);
let _ = std::io::Write::flush(&mut std::io::stdout());
}
Ok(InstallMessage::Done) => {
println!("\n\n{}: {}", t!("installation_done"), t!("installation_done_description"));
break;
}
Ok(InstallMessage::Error(e)) => {
eprintln!("\n{}: {}", t!("error"), e);
std::process::exit(4);
}
Err(_) => break,
}
}
});
// eframe::Result döndürmek için Ok
Ok(())
}
struct YaliApp {
state: GlobalState,
steps: Vec<Box<dyn InstallerStep>>,
last_step: usize,
}
impl Default for YaliApp {
fn default() -> Self {
let mut app = Self {
state: GlobalState::default(),
steps: vec![
Box::new(WelcomeStep::default()),
Box::new(LocationStep::default()),
Box::new(KeyboardStep::default()),
Box::new(PartitionStep::default()),
Box::new(UsersStep::default()),
Box::new(SummaryStep),
Box::new(ExecutionStep::default()),
Box::new(FinishStep),
],
last_step: usize::MAX,
};
app.steps[0].on_enter(&mut app.state);
app.last_step = 0;
app
}
}
impl eframe::App for YaliApp {
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
rust_i18n::set_locale(&self.state.language);
let cur = self.state.current_step;
let n_steps = self.steps.len();
let is_exec = cur == n_steps - 2;
let is_finish = cur == n_steps - 1;
// Adım değişmişse on_enter çağır
if cur != self.last_step {
if let Some(step) = self.steps.get_mut(cur) {
step.on_enter(&mut self.state);
}
self.last_step = cur;
}
// ── Sol panel: adım listesi ──────────────────────────
egui::SidePanel::left("sidebar")
.exact_width(195.0)
.frame(egui::Frame::none().fill(ui::theme::c_sidebar()))
.show(ctx, |ui| {
ui.add_space(20.0);
// PisiLinux logo
ui.vertical_centered(|ui| {
let logo = if self.state.is_dark {
egui::include_image!("../assets/pisi-logo-dark.png")
} else {
egui::include_image!("../assets/pisi-logo-light.png")
};
ui.add(
egui::Image::new(logo)
.max_width(120.0)
.rounding(egui::Rounding::same(8.0)),
);
ui.add_space(8.0);
ui.label(
egui::RichText::new(t!("os_name"))
.size(11.0)
.color(ui::theme::c_text_dim()),
);
// get desktop environment
let desktop = std::env::var("XDG_CURRENT_DESKTOP").unwrap_or_default();
ui.label(
egui::RichText::new(&format!("{} Desktop", desktop))
.size(11.0)
.color(ui::theme::c_text_dim()),
);
});
ui.add_space(16.0);
ui.separator();
ui.add_space(12.0);
// Adım listesi
for (i, step) in self.steps.iter().enumerate() {
let label = format!("{}. {}", i + 1, step.name());
egui::Frame::none()
.fill(if i == cur {
egui::Color32::from_rgba_unmultiplied(0x21, 0x96, 0xF3, 30)
} else {
egui::Color32::TRANSPARENT
})
.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);
});
} else if i < cur {
ui.horizontal(|ui| {
ui.colored_label(ui::theme::c_success(), "✓");
ui.colored_label(ui::theme::c_text_dim(), label);
});
} else {
ui.colored_label(
egui::Color32::from_rgb(80, 80, 100),
format!(" {}", label),
);
}
});
ui.add_space(2.0);
}
// Alt kısım: tema değiştirme + sürüm bilgisi
ui.with_layout(egui::Layout::bottom_up(egui::Align::Center), |ui| {
ui.add_space(12.0);
ui.colored_label(
egui::Color32::from_rgb(60, 60, 90),
"Yali-RS v0.2.0",
);
ui.add_space(6.0);
// Tema değiştirme butonu
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())))
.show(ctx, |ui| {
ui.add_space(10.0);
ui.horizontal(|ui| {
ui.add_space(8.0);
// İptal — kurulum ve bitiş ekranında gizle
if !is_exec && !is_finish {
if ui.add(ui::theme::secondary_button(&t!("cancel"))).clicked() {
ctx.send_viewport_cmd(egui::ViewportCommand::Close);
}
}
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
ui.add_space(8.0);
let is_valid = self.steps
.get(cur)
.map(|s| s.is_complete(&self.state))
.unwrap_or(false);
if !is_finish {
// İleri / Tamamlandı butonu
if !is_exec || is_valid {
let next_label = if is_exec {
t!("finish_next")
} else {
t!("next")
};
let btn = if is_valid {
ui::theme::primary_button(&next_label)
} else {
// Devre dışı görünüm için secondary kullan
ui::theme::secondary_button(&next_label)
};
if ui.add_enabled(is_valid, btn).clicked() {
self.state.current_step += 1;
}
}
// Geri — kurulum ekranında gizle
if cur > 0 && !is_exec {
ui.add_space(8.0);
if ui.add(ui::theme::secondary_button(&t!("back"))).clicked() {
self.state.current_step -= 1;
}
}
}
});
});
ui.add_space(10.0);
});
// ── Merkez panel ─────────────────────────────────────
egui::CentralPanel::default()
.frame(egui::Frame::none()
.fill(ui::theme::c_bg_dark())
.inner_margin(egui::Margin::same(20.0)))
.show(ctx, |ui| {
egui::ScrollArea::vertical()
.id_source("main_scroll")
.show(ui, |ui| {
if let Some(step) = self.steps.get_mut(cur) {
step.show(ui, &mut self.state);
}
});
});
}
}