//! Modal overlay shown while a CAD file is being loaded. //! //! Displays the file name, size, current phase, measured progress, and a //! Cancel button. use iced::time::Instant; use iced::widget::{button, column, container, row, stack, text, Space}; use iced::{Background, Border, Element, Fill, Length, Theme}; use crate::t; use std::borrow::Cow; use std::sync::atomic::Ordering; use crate::app::{ Message, OpenProgress, OPEN_PHASE_CACHING, OPEN_PHASE_FINALIZING, OPEN_PHASE_PARSING, OPEN_PHASE_READING, OPEN_PHASE_XREF, }; const CARD_WIDTH: f32 = 420.0; const BAR_TRACK_WIDTH: f32 = 380.0; const BAR_TRACK_HEIGHT: f32 = 6.0; fn phase_label(phase: u8) -> Cow<'static, str> { match phase { OPEN_PHASE_READING => t!("Reading file…"), OPEN_PHASE_PARSING => t!("Parsing entities…"), OPEN_PHASE_XREF => t!("Loading references…"), OPEN_PHASE_CACHING => t!("Building scene caches…"), OPEN_PHASE_FINALIZING => t!("Finalizing…"), _ => t!("Working…"), } } fn format_size(bytes: u64) -> String { const KB: f64 = 1024.0; const MB: f64 = KB * 1024.0; const GB: f64 = MB * 1024.0; let b = bytes as f64; if b >= GB { format!("{:.2} GB", b / GB) } else if b >= MB { format!("{:.1} MB", b / MB) } else if b >= KB { format!("{:.1} KB", b / KB) } else { format!("{bytes} B") } } pub fn view<'a>(progress: &'a OpenProgress, _now: Instant) -> Element<'a, Message> { let phase = progress.state.phase.load(Ordering::Acquire); let basis_points = progress .state .basis_points .load(Ordering::Relaxed) .min(10000); let fraction = basis_points as f32 / 10000.0; let fill_width = BAR_TRACK_WIDTH * fraction; let trailing = (BAR_TRACK_WIDTH - fill_width).max(0.0); let bar_fill: Element<'_, Message> = container( Space::new() .width(Length::Fixed(fill_width)) .height(Length::Fixed(BAR_TRACK_HEIGHT)), ) .style(|theme: &Theme| container::Style { background: Some(Background::Color( theme.palette().primary.base.color )), border: Border { radius: 3.0.into(), ..Default::default() }, ..Default::default() }) .into(); let bar_value: Element<'_, Message> = row![ bar_fill, Space::new() .width(Length::Fixed(trailing)) .height(Length::Fixed(BAR_TRACK_HEIGHT)), ] .into(); let bar_track: Element<'_, Message> = container( stack![ container(Space::new().width(Length::Fixed(BAR_TRACK_WIDTH)).height(Length::Fixed(BAR_TRACK_HEIGHT))) .style(|theme: &Theme| container::Style { background: Some(Background::Color( theme.palette().background.strong.color )), border: Border { radius: 3.0.into(), ..Default::default() }, ..Default::default() }), bar_value, ] .width(Length::Fixed(BAR_TRACK_WIDTH)) .height(Length::Fixed(BAR_TRACK_HEIGHT)), ) .into(); // ── Card body ──────────────────────────────────────────────────────── let title = text(t!("Opening file")).size(15); let name_line = text(format!( "{} ({})", progress.name, format_size(progress.size_bytes) )) .size(13) .style(|theme: &Theme| iced::widget::text::Style { color: Some(theme.palette().background.base.text.scale_alpha(0.82)), }); let phase_line = text(format!( "{} {:.1}%", phase_label(phase), basis_points as f32 / 100.0 )) .size(12) .style(|theme: &Theme| iced::widget::text::Style { color: Some(theme.palette().primary.base.color), }); let cancel_btn: Element<'_, Message> = button(text(t!("Cancel")).size(12)) .on_press(Message::OpenCancel) .style(button::danger) .padding([4, 14]) .into(); let cancel_row: Element<'_, Message> = container(cancel_btn).align_right(Fill).into(); let card = container( column![title, name_line, bar_track, phase_line, cancel_row] .spacing(10) .width(Length::Fixed(CARD_WIDTH)), ) .padding([18, 22]) .style(|theme: &Theme| { let palette = theme.palette(); container::Style { background: Some(Background::Color(palette.background.weak.color)), border: Border { color: palette.background.neutral.color, width: 1.0, radius: 6.0.into(), }, ..Default::default() } }); // ── Backdrop (click-blocker + dim) ──────────────────────────────────── let backdrop: Element<'_, Message> = container(Space::new().width(Fill).height(Fill)) .style(|theme: &Theme| container::Style { background: Some(Background::Color( theme.palette().background.strong.color.scale_alpha(0.72) )), ..Default::default() }) .width(Fill) .height(Fill) .into(); let centered: Element<'_, Message> = container(card) .center_x(Fill) .center_y(Fill) .width(Fill) .height(Fill) .into(); stack![backdrop, centered].width(Fill).height(Fill).into() }