forked from 1b-agent/opencadstudio-upstream
fix(start): refine responsive page layout
This commit is contained in:
1 parent
13123d5b34
commit
a30f72f627
3 files changed
+40
-32
No files matched your search
@@ -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
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in new issue
Block a user