fix(start): complete Start page workflow

Track Save As paths, persist the selected section, and size tabbed
panels from their actual available width.

Refs #443
This commit is contained in:
Hakan Seven 2026-07-24 17:44:07 +03:00
commit 658aa4a4a5
5 changed files with 61 additions and 19 deletions

View file

@ -20,6 +20,8 @@ pub struct AppConfig {
pub settings: UserSettings,
/// Recent-files list + retained count.
pub recent: RecentConfig,
/// Last selected section on the tabbed Start page.
pub start: StartConfig,
/// Which status-bar pills the user has hidden.
pub statusbar: StatusBarConfig,
/// Ribbon collapse density.
@ -34,6 +36,7 @@ impl Default for AppConfig {
Self {
settings: UserSettings::default(),
recent: RecentConfig::default(),
start: StartConfig::default(),
statusbar: StatusBarConfig::default(),
ribbon: RibbonConfig::default(),
plot: PlotDialogState::default(),
@ -59,6 +62,12 @@ impl Default for RecentConfig {
}
}
#[derive(Clone, PartialEq, Serialize, Deserialize, Default)]
#[serde(default)]
pub struct StartConfig {
pub section: super::StartSection,
}
#[derive(Clone, PartialEq, Serialize, Deserialize, Default)]
#[serde(default)]
pub struct RibbonConfig {

View file

@ -211,7 +211,9 @@ struct AddSelectedRestore {
}
/// Which Start-page section a narrow (tabbed) Start page is showing.
#[derive(Clone, Copy, PartialEq, Eq, Default, Debug)]
#[derive(
Clone, Copy, PartialEq, Eq, Default, Debug, serde::Serialize, serde::Deserialize,
)]
pub enum StartSection {
Recent,
Videos,

View file

@ -292,6 +292,9 @@ impl OpenCADStudio {
.collect(),
limit: self.recent_limit,
},
start: crate::app::config::StartConfig {
section: self.start_section,
},
statusbar: self.statusbar_config.clone(),
ribbon: crate::app::config::RibbonConfig {
collapse: self.ribbon.collapse_mode(),
@ -317,6 +320,7 @@ impl OpenCADStudio {
self.recent_limit_input = self.recent_limit.to_string();
// Thumbnails are decoded by a background task queued at boot
// (`refresh_recent_thumbs`) — never here on the boot path.
self.start_section = cfg.start.section;
self.statusbar_config = cfg.statusbar;
self.ribbon.set_collapse_mode(cfg.ribbon.collapse);
self.plot_dialog = cfg.plot;
@ -952,9 +956,14 @@ pub(super) fn on_open_file(&mut self) -> Task<Message> {
self.tabs[i].current_path = Some(path.clone());
self.tabs[i].dirty = false;
let _ = std::fs::remove_file(path.with_extension("sv$"));
let recent = self.push_recent(path.clone());
if self.save_dialog_for_unsaved {
return self.update(Message::UnsavedPickedSavePath(Some(path)));
return Task::batch([
recent,
self.update(Message::UnsavedPickedSavePath(Some(path))),
]);
}
return recent;
}
Err(e) => self.command_line.push_error(&format!("Save failed: {e}")),
}

View file

@ -224,6 +224,7 @@ impl OpenCADStudio {
Message::StartSectionSelect(section) => {
self.start_section = section;
self.save_config();
Task::none()
}

View file

@ -115,18 +115,21 @@ impl OpenCADStudio {
// viewport draws the layout's own geometry (white sheet + entities +
// borders) and the floating content viewports blit on top.
let viewport_3d: Element<'_, Message> = if tab.is_start {
start_page_view(
&self.patrons,
&self.videos,
self.videos_loading,
&self.video_thumbs,
&self.recent_files,
&self.recent_thumbs,
self.recent_limit,
&self.recent_limit_input,
self.win_size.0,
self.start_section,
)
responsive(|size| {
start_page_view(
&self.patrons,
&self.videos,
self.videos_loading,
&self.video_thumbs,
&self.recent_files,
&self.recent_thumbs,
self.recent_limit,
&self.recent_limit_input,
size.width,
self.start_section,
)
})
.into()
} else if is_paper {
shader(ViewportPane::model(
&tab.scene,
@ -2504,7 +2507,17 @@ pub(super) fn start_page_view<'a>(
let fits_all = avail
>= recent_w + sup_w + 2.0 * 16.0 + welcome_min + 2.0 * (videos_w + 16.0);
let recent = recent_files_panel(recents, thumbs, recent_limit, recent_limit_input);
let recent = recent_files_panel(
recents,
thumbs,
recent_limit,
recent_limit_input,
if fits_all {
iced::Length::Fixed(recent_w)
} else {
iced::Length::Fill
},
);
let welcome = container(content).width(Fill).height(Fill);
// Tutorial-videos rail: the official playlist, fetched at boot (cached on
@ -2593,7 +2606,11 @@ pub(super) fn start_page_view<'a>(
Space::new().height(iced::Length::Fixed(12.0)),
playlist_btn,
])
.width(iced::Length::Fixed(videos_w))
.width(if fits_all {
iced::Length::Fixed(videos_w)
} else {
iced::Length::Fill
})
.height(Fill)
.padding(16)
.style(|_: &Theme| container::Style {
@ -2672,7 +2689,11 @@ pub(super) fn start_page_view<'a>(
Space::new().height(iced::Length::Fixed(12.0)),
support_btn,
])
.width(iced::Length::Fixed(240.0))
.width(if fits_all {
iced::Length::Fixed(sup_w)
} else {
iced::Length::Fill
})
.height(Fill)
.padding(20)
.style(|_: &Theme| container::Style {
@ -2804,6 +2825,7 @@ pub(super) fn recent_files_panel<'a>(
>,
limit: usize,
limit_input: &'a str,
width: iced::Length,
) -> Element<'a, Message> {
// Card chrome matches the Supporters rail (the canonical start-page card).
const PANEL_BG: Color = Color {
@ -2993,7 +3015,7 @@ pub(super) fn recent_files_panel<'a>(
]
.width(Fill),
)
.width(iced::Length::Fixed(280.0))
.width(width)
.height(Fill)
.padding(20)
.style(|_: &Theme| container::Style {
@ -3007,4 +3029,3 @@ pub(super) fn recent_files_panel<'a>(
})
.into()
}