//! Hata yakalama ve kullanıcıya bildirme ekranı. //! //! Kurulum sırasında kritik bir hata oluştuğunda //! bu ekran tüm merkez paneli kaplar. //! Log detayları açılıp kapanabilir; kullanıcı //! hata raporunu panoya kopyalayabilir. use eframe::egui; use rust_i18n::t; use crate::ui::theme; pub struct ErrorScreen { pub title: String, pub message: String, pub log_lines: Vec, log_expanded: bool, copied: bool, copied_timer: u8, // frame sayacı; 0 olunca "Kopyalandı" etiketi kaybolur } impl ErrorScreen { pub fn new(title: impl Into, message: impl Into, log: Vec) -> Self { Self { title: title.into(), message: message.into(), log_lines: log, log_expanded: false, copied: false, copied_timer: 0, } } /// Ekranı çizer. `on_retry` ve `on_quit` kapanışları buton basımlarında çağrılır. pub fn show( &mut self, ui: &mut egui::Ui, on_retry: impl FnOnce(), on_quit: impl FnOnce(), ) { // "Kopyalandı" zamanlayıcısını azalt if self.copied && self.copied_timer > 0 { self.copied_timer -= 1; if self.copied_timer == 0 { self.copied = false; } ui.ctx().request_repaint(); } ui.add_space(24.0); ui.vertical_centered(|ui| { // ── Büyük hata simgesi ───────────────────────── ui.label( egui::RichText::new("✗") .size(56.0) .color(theme::c_error()), ); ui.add_space(10.0); // ── Başlık ──────────────────────────────────── ui.label( egui::RichText::new(&self.title) .size(22.0) .strong() .color(theme::c_text()), ); ui.add_space(6.0); // ── Hata mesajı ─────────────────────────────── egui::Frame::none() .fill(egui::Color32::from_rgba_unmultiplied(0xF4, 0x43, 0x36, 18)) .stroke(egui::Stroke::new(1.0, theme::c_error())) .rounding(egui::Rounding::same(8.0)) .inner_margin(egui::Margin::same(12.0)) .show(ui, |ui| { ui.set_width(ui.available_width().min(560.0)); ui.colored_label(theme::c_error(), &self.message); }); ui.add_space(16.0); // ── Log bölümü (aç/kapat) ───────────────────── let log_label = if self.log_expanded { t!("error_hide_log") } else { t!("error_show_log") }; if ui.button(log_label).clicked() { self.log_expanded = !self.log_expanded; } if self.log_expanded { ui.add_space(6.0); egui::ScrollArea::vertical() .max_height(180.0) .id_source("error_log") .show(ui, |ui| { egui::Frame::none() .fill(egui::Color32::from_rgb(14, 14, 22)) .rounding(egui::Rounding::same(6.0)) .inner_margin(egui::Margin::same(10.0)) .show(ui, |ui| { ui.set_width(ui.available_width().min(560.0)); for line in &self.log_lines { let color = log_line_color(line); ui.colored_label(color, line); } }); }); ui.add_space(6.0); // Panoya kopyala let copy_label = if self.copied { t!("error_copied") } else { t!("error_copy_log") }; if ui.small_button(copy_label).clicked() && !self.copied { let full_log = self.log_lines.join("\n"); ui.ctx().copy_text(full_log); self.copied = true; self.copied_timer = 120; // ~2 saniye (60fps varsayımı) } } ui.add_space(20.0); // ── Eylem butonları ─────────────────────────── ui.horizontal(|ui| { // Ortalamak için boş genişlik hesapla let btn_w = 140.0; let gap = 16.0; let total = btn_w * 2.0 + gap; 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() { on_quit(); } ui.add_space(gap); if ui.add(theme::primary_button(&t!("error_retry"))).clicked() { on_retry(); } }); }); } } fn log_line_color(line: &str) -> egui::Color32 { if line.starts_with('✗') || line.to_lowercase().contains("hata") || line.to_lowercase().contains("error") { theme::c_error() } else if line.starts_with('✓') { theme::c_success() } else if line.starts_with('▶') { theme::c_accent() } else if line.starts_with('⚠') { theme::c_warning() } else { egui::Color32::from_rgb(180, 180, 200) } }