fix(start): refine responsive page layout

This commit is contained in:
Hakan Seven committed 2026-07-28 12:48:39 +03:00
1 parent 13123d5b34
commit a30f72f627
3 files changed
+40 -32

No files matched your search

+4
View File
@@ -271,6 +271,9 @@ pub(super) struct OpenCADStudio {
/// Which Start-page section is shown when the page is too narrow for all
/// three side by side and falls back to a tab bar.
start_section: StartSection,
/// Widest natural single-row width of the Start-page action buttons,
/// measured by `WrapFlow` so side lists collapse before those buttons wrap.
start_action_w: std::sync::Arc<std::sync::atomic::AtomicU32>,
/// When the window is too narrow the properties panel collapses to a
/// vertical bar; this is the user's toggle to expand it back out.
props_expanded: bool,
@@ -2494,6 +2497,7 @@ impl OpenCADStudio {
discussions_loading: false,
props_asym_scale: std::collections::HashSet::new(),
start_section: StartSection::default(),
start_action_w: std::sync::Arc::new(std::sync::atomic::AtomicU32::new(0)),
props_expanded: false,
history_content: iced::widget::text_editor::Content::new(),
status_bar: StatusBar::new(),
+22 -32
View File
@@ -135,6 +135,7 @@ impl OpenCADStudio {
self.recent_limit,
&self.recent_limit_input,
size.width,
self.start_action_w.clone(),
self.start_section,
)
})
@@ -2191,6 +2192,7 @@ pub(super) fn start_page_view<'a>(
recent_limit: usize,
recent_limit_input: &'a str,
avail_w: f32,
action_width_out: std::sync::Arc<std::sync::atomic::AtomicU32>,
active: super::StartSection,
) -> Element<'a, Message> {
let headline = text("Open CAD Studio").size(40).style(start_primary_style);
@@ -2243,7 +2245,8 @@ pub(super) fn start_page_view<'a>(
donate_btn.into(),
])
.spacing_x(12.0)
.row_h(48.0);
.row_h(48.0)
.report_natural_width(action_width_out.clone());
#[cfg_attr(target_arch = "wasm32", allow(unused_mut))]
let mut secondary_items: Vec<Element<'a, Message>> = vec![
@@ -2276,34 +2279,14 @@ pub(super) fn start_page_view<'a>(
}
let secondary_row = WrapFlow::new(secondary_items)
.spacing_x(12.0)
.row_h(44.0);
// Buttons sit on a transparent container with a large, primary-tinted
// ambient shadow (offset = 0, big blur) — produces a soft halo behind
// the action row, matching the Thunderbird coloured-glow look against
// the dark page.
let primary_glow = container(primary_row)
.padding(iced::Padding {
top: 4.0,
right: 8.0,
bottom: 4.0,
left: 8.0,
})
.style(|theme: &Theme| container::Style {
background: Some(Background::Color(Color::TRANSPARENT)),
shadow: iced::Shadow {
color: theme.extended_palette().primary.base.color.scale_alpha(0.45),
offset: iced::Vector::ZERO,
blur_radius: 80.0,
},
..Default::default()
});
.row_h(44.0)
.report_natural_width(action_width_out.clone());
let content = column![
Space::new().height(iced::Length::Fixed(28.0)),
container(headline).center_x(Fill),
Space::new().height(iced::Length::Fixed(22.0)),
container(primary_glow).center_x(Fill),
container(primary_row).center_x(Fill),
Space::new().height(iced::Length::Fixed(10.0)),
container(secondary_row).center_x(Fill),
Space::new().height(Fill),
@@ -2323,7 +2306,12 @@ pub(super) fn start_page_view<'a>(
Compact,
}
let panel_w = 280.0f32;
let welcome_wide_min = 360.0f32;
const VIDEO_PANEL_PADDING: f32 = 16.0;
const VIDEO_SCROLL_GUTTER: f32 = 14.0;
let measured_action_w = f32::from_bits(
action_width_out.load(std::sync::atomic::Ordering::Relaxed)
);
let welcome_wide_min = measured_action_w.max(360.0);
let avail = (avail_w - 16.0).max(0.0); // minus the page's l/r padding
let panel_widths = [panel_w; 4];
let mut panel_visible = [true, true, true, true];
@@ -2371,14 +2359,16 @@ pub(super) fn start_page_view<'a>(
// Tutorial-videos rail: the official playlist, fetched at boot (cached on
// disk) — thumbnail card + title per video, click opens the browser.
let videos_panel: Element<'a, Message> = {
// Inner width = panel 300 − padding 2×16 − scrollbar gutter 14.
const THUMB_H: f32 = (300.0 - 32.0 - 14.0) * 9.0 / 16.0;
// Derive the 16:9 cover box from the actual shared list width so the
// whole thumbnail remains visible when that width changes.
let thumb_h =
(panel_w - VIDEO_PANEL_PADDING * 2.0 - VIDEO_SCROLL_GUTTER) * 9.0 / 16.0;
let mut list = column![text("Tutorials").size(15)]
.spacing(10)
.width(Fill)
// Keep the scrollbar off the thumbnails.
.padding(iced::Padding {
right: 14.0,
right: VIDEO_SCROLL_GUTTER,
..iced::Padding::ZERO
});
for v in videos {
@@ -2388,11 +2378,11 @@ pub(super) fn start_page_view<'a>(
container(
iced::widget::image(handle.clone())
.width(Fill)
.height(iced::Length::Fixed(THUMB_H))
.content_fit(iced::ContentFit::Cover),
.height(iced::Length::Fixed(thumb_h))
.content_fit(iced::ContentFit::Contain),
)
.width(Fill)
.height(iced::Length::Fixed(THUMB_H))
.height(iced::Length::Fixed(thumb_h))
.style(|theme: &Theme| container::Style {
border: Border {
color: theme.extended_palette().background.neutral.color,
@@ -2453,7 +2443,7 @@ pub(super) fn start_page_view<'a>(
| StartLayout::Compact => iced::Length::Fill,
})
.height(Fill)
.padding(16)
.padding(VIDEO_PANEL_PADDING)
.style(|theme: &Theme| {
let palette = theme.extended_palette();
container::Style {
+14
View File
@@ -482,6 +482,9 @@ pub struct WrapFlow<'a> {
row_h: f32,
/// Align every wrapped row to the right edge of the available width.
justify_end: bool,
/// Receives the natural single-row width (as `f32` bits). Multiple flows
/// may share one output; the widest measured value wins.
natural_width_out: Option<Arc<AtomicU32>>,
}
impl<'a> WrapFlow<'a> {
@@ -493,6 +496,7 @@ impl<'a> WrapFlow<'a> {
spacing_y: 0.0,
row_h: 28.0,
justify_end: false,
natural_width_out: None,
}
}
@@ -517,6 +521,11 @@ impl<'a> WrapFlow<'a> {
self.justify_end = justify;
self
}
pub fn report_natural_width(mut self, out: Arc<AtomicU32>) -> Self {
self.natural_width_out = Some(out);
self
}
}
impl<'a> Widget<Message, Theme, Renderer> for WrapFlow<'a> {
@@ -557,6 +566,11 @@ impl<'a> Widget<Message, Theme, Renderer> for WrapFlow<'a> {
// bit closer before wrapping. Only fires when max_w is a real bound
// (i.e. the flow is already width-constrained, e.g. overflowing tabs).
let n = measured.len();
if let Some(out) = &self.natural_width_out {
let natural_width =
sum_w + n.saturating_sub(1) as f32 * self.spacing_x;
out.fetch_max(natural_width.to_bits(), Ordering::Relaxed);
}
let mut eff_gap = self.spacing_x;
if self.min_spacing_x < self.spacing_x && max_w.is_finite() && n > 1 {
let line_w = sum_w + (n - 1) as f32 * self.spacing_x;