fix(start): collapse panels by available space
Collapse Tutorials, Supporters, then Recent Documents as the panels stop fitting. Refs #519
This commit is contained in:
parent
4a30928f5a
commit
dce2533767
1 changed files with 147 additions and 107 deletions
|
|
@ -2607,32 +2607,59 @@ pub(super) fn start_page_view<'a>(
|
||||||
b: 0.085,
|
b: 0.085,
|
||||||
a: 1.0,
|
a: 1.0,
|
||||||
};
|
};
|
||||||
// Three parts — Recent Files · Welcome · Supporters. They sit side by side
|
// Collapse side panels one at a time as width shrinks: Tutorials first,
|
||||||
// while they fit; when the page is too narrow it becomes a tab bar with the
|
// then Supporters, and Recent Documents last. The previous all-or-nothing
|
||||||
// Welcome tab open by default.
|
// threshold reserved a videos-sized empty margin on both sides of the
|
||||||
|
// centred Welcome block and hid every panel around 1664 px.
|
||||||
|
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||||
|
enum StartLayout {
|
||||||
|
AllPanels,
|
||||||
|
WithoutVideos,
|
||||||
|
RecentAndWelcome,
|
||||||
|
Compact,
|
||||||
|
}
|
||||||
let recent_w = 280.0f32;
|
let recent_w = 280.0f32;
|
||||||
let videos_w = 300.0f32;
|
let videos_w = 300.0f32;
|
||||||
let sup_w = 240.0f32;
|
let sup_w = 240.0f32;
|
||||||
let welcome_min = 480.0f32;
|
let welcome_wide_min = 360.0f32;
|
||||||
let avail = (avail_w - 16.0).max(0.0); // minus the page's l/r padding
|
let avail = (avail_w - 16.0).max(0.0); // minus the page's l/r padding
|
||||||
// The videos rail overlays the welcome column's left side, and the welcome
|
let panel_widths = [recent_w, videos_w, sup_w];
|
||||||
// content is CENTRED in that column — so side-by-side only works while the
|
let mut panel_visible = [true, true, true];
|
||||||
// centred `welcome_min` block still leaves a rail-sized gap on its left:
|
let required_width = |visible: &[bool; 3]| {
|
||||||
// the column must fit welcome_min plus a videos_w + spacing margin on BOTH
|
let visible_panels = visible.iter().filter(|&&shown| shown).count();
|
||||||
// sides (centring is symmetric). Below that, the centre content would
|
welcome_wide_min
|
||||||
// slide over the rail — switch to the tabbed layout instead.
|
+ panel_widths
|
||||||
let fits_all = avail
|
.iter()
|
||||||
>= recent_w + sup_w + 2.0 * 16.0 + welcome_min + 2.0 * (videos_w + 16.0);
|
.zip(visible)
|
||||||
|
.filter_map(|(width, shown)| shown.then_some(*width))
|
||||||
|
.sum::<f32>()
|
||||||
|
+ visible_panels as f32 * 16.0
|
||||||
|
};
|
||||||
|
// Re-measure after every collapse. There are no independent breakpoints:
|
||||||
|
// the available width and the panels' preferred widths decide the state.
|
||||||
|
for panel in [1usize, 2, 0] {
|
||||||
|
if required_width(&panel_visible) <= avail {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
panel_visible[panel] = false;
|
||||||
|
}
|
||||||
|
let start_layout = match panel_visible {
|
||||||
|
[true, true, true] => StartLayout::AllPanels,
|
||||||
|
[true, false, true] => StartLayout::WithoutVideos,
|
||||||
|
[true, false, false] => StartLayout::RecentAndWelcome,
|
||||||
|
_ => StartLayout::Compact,
|
||||||
|
};
|
||||||
|
|
||||||
let recent = recent_files_panel(
|
let recent = recent_files_panel(
|
||||||
recents,
|
recents,
|
||||||
thumbs,
|
thumbs,
|
||||||
recent_limit,
|
recent_limit,
|
||||||
recent_limit_input,
|
recent_limit_input,
|
||||||
if fits_all {
|
match start_layout {
|
||||||
iced::Length::Fixed(recent_w)
|
StartLayout::AllPanels
|
||||||
} else {
|
| StartLayout::WithoutVideos
|
||||||
iced::Length::Fill
|
| StartLayout::RecentAndWelcome => iced::Length::Fixed(recent_w),
|
||||||
|
StartLayout::Compact => iced::Length::Fill,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
let welcome = container(content).width(Fill).height(Fill);
|
let welcome = container(content).width(Fill).height(Fill);
|
||||||
|
|
@ -2723,10 +2750,11 @@ pub(super) fn start_page_view<'a>(
|
||||||
Space::new().height(iced::Length::Fixed(12.0)),
|
Space::new().height(iced::Length::Fixed(12.0)),
|
||||||
playlist_btn,
|
playlist_btn,
|
||||||
])
|
])
|
||||||
.width(if fits_all {
|
.width(match start_layout {
|
||||||
iced::Length::Fixed(videos_w)
|
StartLayout::AllPanels => iced::Length::Fixed(videos_w),
|
||||||
} else {
|
StartLayout::WithoutVideos
|
||||||
iced::Length::Fill
|
| StartLayout::RecentAndWelcome
|
||||||
|
| StartLayout::Compact => iced::Length::Fill,
|
||||||
})
|
})
|
||||||
.height(Fill)
|
.height(Fill)
|
||||||
.padding(16)
|
.padding(16)
|
||||||
|
|
@ -2806,10 +2834,12 @@ pub(super) fn start_page_view<'a>(
|
||||||
Space::new().height(iced::Length::Fixed(12.0)),
|
Space::new().height(iced::Length::Fixed(12.0)),
|
||||||
support_btn,
|
support_btn,
|
||||||
])
|
])
|
||||||
.width(if fits_all {
|
.width(match start_layout {
|
||||||
iced::Length::Fixed(sup_w)
|
StartLayout::AllPanels | StartLayout::WithoutVideos => {
|
||||||
} else {
|
iced::Length::Fixed(sup_w)
|
||||||
iced::Length::Fill
|
}
|
||||||
|
StartLayout::RecentAndWelcome
|
||||||
|
| StartLayout::Compact => iced::Length::Fill,
|
||||||
})
|
})
|
||||||
.height(Fill)
|
.height(Fill)
|
||||||
.padding(20)
|
.padding(20)
|
||||||
|
|
@ -2825,91 +2855,101 @@ pub(super) fn start_page_view<'a>(
|
||||||
.into()
|
.into()
|
||||||
};
|
};
|
||||||
|
|
||||||
let body: Element<'a, Message> = if fits_all {
|
let body: Element<'a, Message> = match start_layout {
|
||||||
// The videos rail OVERLAYS the welcome column's empty left side
|
StartLayout::AllPanels => iced::widget::row![
|
||||||
// instead of participating in the row: the welcome content keeps the
|
recent,
|
||||||
// same width and centre as before, so the action buttons don't move.
|
videos_panel,
|
||||||
let base = iced::widget::row![recent, welcome, supporters].spacing(16);
|
welcome,
|
||||||
let rail = container(videos_panel)
|
supporters,
|
||||||
.padding(iced::Padding {
|
|
||||||
left: recent_w + 16.0,
|
|
||||||
..iced::Padding::ZERO
|
|
||||||
})
|
|
||||||
.height(Fill);
|
|
||||||
iced::widget::stack![base, rail].into()
|
|
||||||
} else {
|
|
||||||
let tab_btn = |label: &'static str, section: super::StartSection| {
|
|
||||||
let is_active = active == section;
|
|
||||||
button(text(label).size(14))
|
|
||||||
.on_press(Message::StartSectionSelect(section))
|
|
||||||
.padding([8, 18])
|
|
||||||
.style(move |_: &Theme, status| button::Style {
|
|
||||||
background: Some(Background::Color(match (is_active, status) {
|
|
||||||
(true, _) => Color {
|
|
||||||
r: 0.18,
|
|
||||||
g: 0.18,
|
|
||||||
b: 0.20,
|
|
||||||
a: 1.0,
|
|
||||||
},
|
|
||||||
(false, button::Status::Hovered) => Color {
|
|
||||||
r: 0.14,
|
|
||||||
g: 0.14,
|
|
||||||
b: 0.16,
|
|
||||||
a: 1.0,
|
|
||||||
},
|
|
||||||
_ => Color::TRANSPARENT,
|
|
||||||
})),
|
|
||||||
text_color: if is_active {
|
|
||||||
TEXT
|
|
||||||
} else {
|
|
||||||
Color {
|
|
||||||
r: 0.62,
|
|
||||||
g: 0.62,
|
|
||||||
b: 0.62,
|
|
||||||
a: 1.0,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
border: Border {
|
|
||||||
color: if is_active { BRAND } else { Color::TRANSPARENT },
|
|
||||||
width: if is_active { 1.0 } else { 0.0 },
|
|
||||||
radius: 6.0.into(),
|
|
||||||
},
|
|
||||||
..Default::default()
|
|
||||||
})
|
|
||||||
};
|
|
||||||
let tab_bar = iced::widget::row![
|
|
||||||
tab_btn("Recent Files", super::StartSection::Recent),
|
|
||||||
tab_btn("Videos", super::StartSection::Videos),
|
|
||||||
tab_btn("Welcome", super::StartSection::Welcome),
|
|
||||||
tab_btn("Supporters", super::StartSection::Supporters),
|
|
||||||
]
|
]
|
||||||
.spacing(6);
|
.spacing(16)
|
||||||
let section_body: Element<'a, Message> = match active {
|
|
||||||
super::StartSection::Recent => container(recent)
|
|
||||||
.width(Fill)
|
|
||||||
.height(Fill)
|
|
||||||
.center_x(Fill)
|
|
||||||
.into(),
|
|
||||||
super::StartSection::Videos => container(videos_panel)
|
|
||||||
.width(Fill)
|
|
||||||
.height(Fill)
|
|
||||||
.center_x(Fill)
|
|
||||||
.into(),
|
|
||||||
super::StartSection::Welcome => welcome.into(),
|
|
||||||
super::StartSection::Supporters => container(supporters)
|
|
||||||
.width(Fill)
|
|
||||||
.height(Fill)
|
|
||||||
.center_x(Fill)
|
|
||||||
.into(),
|
|
||||||
};
|
|
||||||
column![
|
|
||||||
container(tab_bar).center_x(Fill),
|
|
||||||
Space::new().height(iced::Length::Fixed(12.0)),
|
|
||||||
section_body,
|
|
||||||
]
|
|
||||||
.width(Fill)
|
|
||||||
.height(Fill)
|
.height(Fill)
|
||||||
.into()
|
.into(),
|
||||||
|
StartLayout::WithoutVideos => {
|
||||||
|
iced::widget::row![recent, welcome, supporters]
|
||||||
|
.spacing(16)
|
||||||
|
.height(Fill)
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
StartLayout::RecentAndWelcome => iced::widget::row![recent, welcome]
|
||||||
|
.spacing(16)
|
||||||
|
.height(Fill)
|
||||||
|
.into(),
|
||||||
|
StartLayout::Compact => {
|
||||||
|
let tab_btn = |label: &'static str, section: super::StartSection| {
|
||||||
|
let is_active = active == section;
|
||||||
|
button(text(label).size(14))
|
||||||
|
.on_press(Message::StartSectionSelect(section))
|
||||||
|
.padding([8, 18])
|
||||||
|
.style(move |_: &Theme, status| button::Style {
|
||||||
|
background: Some(Background::Color(match (is_active, status) {
|
||||||
|
(true, _) => Color {
|
||||||
|
r: 0.18,
|
||||||
|
g: 0.18,
|
||||||
|
b: 0.20,
|
||||||
|
a: 1.0,
|
||||||
|
},
|
||||||
|
(false, button::Status::Hovered) => Color {
|
||||||
|
r: 0.14,
|
||||||
|
g: 0.14,
|
||||||
|
b: 0.16,
|
||||||
|
a: 1.0,
|
||||||
|
},
|
||||||
|
_ => Color::TRANSPARENT,
|
||||||
|
})),
|
||||||
|
text_color: if is_active {
|
||||||
|
TEXT
|
||||||
|
} else {
|
||||||
|
Color {
|
||||||
|
r: 0.62,
|
||||||
|
g: 0.62,
|
||||||
|
b: 0.62,
|
||||||
|
a: 1.0,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
border: Border {
|
||||||
|
color: if is_active { BRAND } else { Color::TRANSPARENT },
|
||||||
|
width: if is_active { 1.0 } else { 0.0 },
|
||||||
|
radius: 6.0.into(),
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
|
};
|
||||||
|
let tab_bar = WrapFlow::new(vec![
|
||||||
|
tab_btn("Recent Files", super::StartSection::Recent).into(),
|
||||||
|
tab_btn("Videos", super::StartSection::Videos).into(),
|
||||||
|
tab_btn("Welcome", super::StartSection::Welcome).into(),
|
||||||
|
tab_btn("Supporters", super::StartSection::Supporters).into(),
|
||||||
|
])
|
||||||
|
.spacing_x(6.0)
|
||||||
|
.row_h(38.0);
|
||||||
|
let section_body: Element<'a, Message> = match active {
|
||||||
|
super::StartSection::Recent => container(recent)
|
||||||
|
.width(Fill)
|
||||||
|
.height(Fill)
|
||||||
|
.center_x(Fill)
|
||||||
|
.into(),
|
||||||
|
super::StartSection::Videos => container(videos_panel)
|
||||||
|
.width(Fill)
|
||||||
|
.height(Fill)
|
||||||
|
.center_x(Fill)
|
||||||
|
.into(),
|
||||||
|
super::StartSection::Welcome => welcome.into(),
|
||||||
|
super::StartSection::Supporters => container(supporters)
|
||||||
|
.width(Fill)
|
||||||
|
.height(Fill)
|
||||||
|
.center_x(Fill)
|
||||||
|
.into(),
|
||||||
|
};
|
||||||
|
column![
|
||||||
|
container(tab_bar).center_x(Fill),
|
||||||
|
Space::new().height(iced::Length::Fixed(12.0)),
|
||||||
|
section_body,
|
||||||
|
]
|
||||||
|
.width(Fill)
|
||||||
|
.height(Fill)
|
||||||
|
.into()
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
container(body)
|
container(body)
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue