diff --git a/README.md b/README.md index 9fe5a65..d9c885e 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,8 @@ RuDS is a native Rust blogging desktop application and the successor to bDS2. It manages local projects from authoring through preview, static-site generation, integrity checks, and publishing while preserving the existing bDS filesystem and SQLite formats. +The desktop is a single-window application: closing its window persists UI state and exits RuDS. + The project is under active development. Core blogging workflows are broadly available; remaining core work and optional extensions are tracked separately. ## Available Features diff --git a/crates/bds-ui/src/app.rs b/crates/bds-ui/src/app.rs index f90fba3..d761f81 100644 --- a/crates/bds-ui/src/app.rs +++ b/crates/bds-ui/src/app.rs @@ -186,6 +186,7 @@ pub enum Message { task_id: TaskId, result: Result, }, + WindowCloseRequested, MainWindowLoaded(Option), EmbeddedPreviewReady(Result<(), String>), EmbeddedStylePreviewReady(Result<(), String>), @@ -1363,6 +1364,11 @@ impl BdsApp { pub fn update(&mut self, message: Message) -> Task { match message { + Message::WindowCloseRequested => { + self.persist_project_ui_state(); + iced::exit() + } + // ── Menu event dispatch ── Message::MenuEvent(id) => { if let Some(action) = self.menu_registry.lookup(&id) { @@ -3583,6 +3589,7 @@ impl BdsApp { } _ => None, }); + let window_close_sub = window::close_requests().map(|_| Message::WindowCloseRequested); // Global mouse tracking for sidebar resize dragging. // The 4px drag handle mouse_area only fires on_press; move/release @@ -3637,6 +3644,7 @@ impl BdsApp { domain_event_tick, toast_tick, file_drop_sub, + window_close_sub, drag_sub, menu_interaction_sub, menu_expand_tick, diff --git a/crates/bds-ui/src/main.rs b/crates/bds-ui/src/main.rs index dc656c1..ef33127 100644 --- a/crates/bds-ui/src/main.rs +++ b/crates/bds-ui/src/main.rs @@ -30,15 +30,20 @@ fn main() -> anyhow::Result<()> { iced::application("bDS", BdsApp::update, BdsApp::view) .subscription(BdsApp::subscription) .theme(|_| inputs::app_theme()) - .window(iced::window::Settings { - size: iced::Size::new(1200.0, 800.0), - icon: Some(icon), - ..Default::default() - }) + .window(desktop_window_settings(Some(icon))) .run_with(BdsApp::new)?; Ok(()) } +fn desktop_window_settings(icon: Option) -> iced::window::Settings { + iced::window::Settings { + size: iced::Size::new(1200.0, 800.0), + icon, + exit_on_close_request: false, + ..Default::default() + } +} + fn current_platform() -> Platform { #[cfg(target_os = "macos")] return Platform::MacOs; @@ -49,3 +54,13 @@ fn current_platform() -> Platform { #[allow(unreachable_code)] Platform::Windows } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn single_window_close_is_owned_by_the_application() { + assert!(!desktop_window_settings(None).exit_on_close_request); + } +} diff --git a/specs/layout.allium b/specs/layout.allium index affcf6b..6a2a448 100644 --- a/specs/layout.allium +++ b/specs/layout.allium @@ -19,6 +19,7 @@ surface LayoutControlSurface { TogglePanelRequested() ToggleAssistantSidebarRequested() ActivityClicked(activity_id) + CloseWindowRequested() } surface LayoutRuntimeSurface { @@ -55,6 +56,13 @@ value AppShell { status_bar: StatusBar } +rule CloseSingleWindow { + when: CloseWindowRequested() + ensures: ExitApplication() + -- bDS2 and RuDS are single-window applications. Closing that window + -- persists application-owned state and terminates the process. +} + surface AppShellSurface { context shell: AppShell