//! 下载页:**单列**版面的用例(列宽 ≤504,水平居中)。 //! //! 这一页永远单列,理由不是「简单」,而是官方对「一次只做一件事」的入口(下载队列)的口径: //! 内容是一串线性进度项,分栏没有第二个阅读对象。窗口变宽时**正文列不变宽**(撞行宽上限), //! 多出来的宽度留白——这就是 M68「全宽单卡」的反面。 //! //! 页头 1 个操作 + 空态 3 个快捷入口(12b §15.5:≤3)。 use iced::widget::{column, container, row}; use iced::{Alignment, Element, Length}; use unlucky_design::color::Role; use unlucky_design::icons::IconKind; use unlucky_design::spacing::Space; use unlucky_design::typography::TypeRole; use crate::theme::Ui; use super::frame::{self, Header}; use super::layout::FrameMetrics; use super::{Action, Message}; /// 整页。 pub fn view<'a>(ui: &'a Ui, metrics: FrameMetrics) -> Element<'a, Message> { let header = Header::new("Download") .subtitle("Versions, mod loaders and modpacks") .action(frame::text_action( ui, "Refresh sources", Action::RefreshSources, )); let empty = frame::empty_state( ui, IconKind::Download, "No downloads in progress", "Pick a version or drop a modpack file here. Sources are probed per region and can be pinned \ in Settings → Downloads.", vec![ frame::text_action(ui, "Browse versions", Action::BrowseVersions), frame::text_action(ui, "Import modpack", Action::ImportModpack), frame::text_action(ui, "Check sources", Action::CheckSources), ], ); let body = frame::single_column( metrics, column![source_status(ui), empty] .spacing(ui.space(Space::X4)) .width(Length::Fill) .height(Length::Fill), ); frame::page(ui, metrics, header, body) } /// 来源状态行(12pt,`OnSurfaceVariant`):说明这一页的数据从哪来,不是业务数据。 fn source_status<'a>(ui: &'a Ui) -> Element<'a, Message> { container( row![ frame::glyph( IconKind::Check, ui.color(Role::Secondary), frame::ICON_SMALL_PX ), frame::text_role( ui, TypeRole::BodySmall, false, "3 sources healthy · BMCLAPI pinned by region · 2 min ago", ui.color(Role::OnSurfaceVariant), ), ] .spacing(ui.space(Space::X2)) .align_y(Alignment::Center), ) .width(Length::Fill) .into() } #[cfg(test)] mod tests { use super::super::layout::{content_column_width, frame_metrics, measure_cap_px, PaneKit}; /// 单列页:任何窗口宽度都不许出现第二列,正文列不许超过行宽上限。 #[test] fn download_is_single_column_at_every_width() { for width in [600.0, 840.0, 900.0, 1180.0, 2400.0] { let metrics = frame_metrics(PaneKit::Single, width); assert!(metrics.secondary.is_none(), "{width} 宽下载页出现了第二列"); assert!(metrics.column <= measure_cap_px() + f32::EPSILON); assert_eq!(metrics.column, content_column_width(width)); } } /// 宽窗下多出来的宽度必须留白(不是把卡片拉宽)——1180 的正文列 < 可用宽度。 #[test] fn extra_width_goes_to_margin_not_to_the_column() { let metrics = frame_metrics(PaneKit::Single, 1180.0); assert_eq!(metrics.column, measure_cap_px()); assert!( metrics.available - metrics.column >= 400.0, "1180 宽下应留出大片留白(M68 的全宽卡就是没有这段留白)" ); } }