Exit when the desktop window closes
This commit is contained in:
@@ -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.
|
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.
|
The project is under active development. Core blogging workflows are broadly available; remaining core work and optional extensions are tracked separately.
|
||||||
|
|
||||||
## Available Features
|
## Available Features
|
||||||
|
|||||||
@@ -186,6 +186,7 @@ pub enum Message {
|
|||||||
task_id: TaskId,
|
task_id: TaskId,
|
||||||
result: Result<engine::blogmark::BlogmarkImportResult, String>,
|
result: Result<engine::blogmark::BlogmarkImportResult, String>,
|
||||||
},
|
},
|
||||||
|
WindowCloseRequested,
|
||||||
MainWindowLoaded(Option<window::Id>),
|
MainWindowLoaded(Option<window::Id>),
|
||||||
EmbeddedPreviewReady(Result<(), String>),
|
EmbeddedPreviewReady(Result<(), String>),
|
||||||
EmbeddedStylePreviewReady(Result<(), String>),
|
EmbeddedStylePreviewReady(Result<(), String>),
|
||||||
@@ -1363,6 +1364,11 @@ impl BdsApp {
|
|||||||
|
|
||||||
pub fn update(&mut self, message: Message) -> Task<Message> {
|
pub fn update(&mut self, message: Message) -> Task<Message> {
|
||||||
match message {
|
match message {
|
||||||
|
Message::WindowCloseRequested => {
|
||||||
|
self.persist_project_ui_state();
|
||||||
|
flush_embeddings_and_exit(std::process::exit)
|
||||||
|
}
|
||||||
|
|
||||||
// ── Menu event dispatch ──
|
// ── Menu event dispatch ──
|
||||||
Message::MenuEvent(id) => {
|
Message::MenuEvent(id) => {
|
||||||
if let Some(action) = self.menu_registry.lookup(&id) {
|
if let Some(action) = self.menu_registry.lookup(&id) {
|
||||||
@@ -3583,6 +3589,7 @@ impl BdsApp {
|
|||||||
}
|
}
|
||||||
_ => None,
|
_ => None,
|
||||||
});
|
});
|
||||||
|
let window_close_sub = window::close_requests().map(|_| Message::WindowCloseRequested);
|
||||||
|
|
||||||
// Global mouse tracking for sidebar resize dragging.
|
// Global mouse tracking for sidebar resize dragging.
|
||||||
// The 4px drag handle mouse_area only fires on_press; move/release
|
// The 4px drag handle mouse_area only fires on_press; move/release
|
||||||
@@ -3637,6 +3644,7 @@ impl BdsApp {
|
|||||||
domain_event_tick,
|
domain_event_tick,
|
||||||
toast_tick,
|
toast_tick,
|
||||||
file_drop_sub,
|
file_drop_sub,
|
||||||
|
window_close_sub,
|
||||||
drag_sub,
|
drag_sub,
|
||||||
menu_interaction_sub,
|
menu_interaction_sub,
|
||||||
menu_expand_tick,
|
menu_expand_tick,
|
||||||
@@ -10067,6 +10075,14 @@ impl Drop for BdsApp {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn flush_embeddings_and_exit<F, R>(exit: F) -> R
|
||||||
|
where
|
||||||
|
F: FnOnce(i32) -> R,
|
||||||
|
{
|
||||||
|
let _ = engine::embedding::EmbeddingService::flush_all();
|
||||||
|
exit(0)
|
||||||
|
}
|
||||||
|
|
||||||
fn content_sample(content: &str, max_len: usize) -> String {
|
fn content_sample(content: &str, max_len: usize) -> String {
|
||||||
content.chars().take(max_len).collect()
|
content.chars().take(max_len).collect()
|
||||||
}
|
}
|
||||||
@@ -10125,12 +10141,12 @@ fn remote_error_closes_connection(code: &str) -> bool {
|
|||||||
mod tests {
|
mod tests {
|
||||||
use super::{
|
use super::{
|
||||||
BdsApp, Message, POST_AUTO_SAVE_DELAY_MS, PersistedMediaState, PersistedPostState,
|
BdsApp, Message, POST_AUTO_SAVE_DELAY_MS, PersistedMediaState, PersistedPostState,
|
||||||
PostStatus, SettingsMsg, active_post_tab_id, dropped_image_target, localize_chat_error,
|
PostStatus, SettingsMsg, active_post_tab_id, dropped_image_target,
|
||||||
month_abbreviation, persist_media_editor_state_impl,
|
flush_embeddings_and_exit, localize_chat_error, month_abbreviation,
|
||||||
persist_post_editor_preview_state_impl, persist_post_editor_state_impl,
|
persist_media_editor_state_impl, persist_post_editor_preview_state_impl,
|
||||||
remote_error_closes_connection, save_editor_settings_state_impl,
|
persist_post_editor_state_impl, remote_error_closes_connection,
|
||||||
save_script_editor_state_impl, save_template_editor_state_impl,
|
save_editor_settings_state_impl, save_script_editor_state_impl,
|
||||||
should_start_embedded_preview_creation,
|
save_template_editor_state_impl, should_start_embedded_preview_creation,
|
||||||
};
|
};
|
||||||
use crate::i18n::t;
|
use crate::i18n::t;
|
||||||
use crate::platform::menu::MenuAction;
|
use crate::platform::menu::MenuAction;
|
||||||
@@ -10160,6 +10176,7 @@ mod tests {
|
|||||||
use bds_core::model::{
|
use bds_core::model::{
|
||||||
ChatRole, DomainEntity, DomainEvent, NotificationAction, Project, ScriptKind, TemplateKind,
|
ChatRole, DomainEntity, DomainEvent, NotificationAction, Project, ScriptKind, TemplateKind,
|
||||||
};
|
};
|
||||||
|
|
||||||
use chrono::{Datelike, TimeZone};
|
use chrono::{Datelike, TimeZone};
|
||||||
use std::io::{Read, Write};
|
use std::io::{Read, Write};
|
||||||
use std::net::TcpListener;
|
use std::net::TcpListener;
|
||||||
@@ -10167,6 +10184,13 @@ mod tests {
|
|||||||
use std::thread;
|
use std::thread;
|
||||||
use tempfile::TempDir;
|
use tempfile::TempDir;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn desktop_shutdown_uses_an_immediate_process_exit() {
|
||||||
|
let exit_code = flush_embeddings_and_exit(|code| code);
|
||||||
|
|
||||||
|
assert_eq!(exit_code, 0);
|
||||||
|
}
|
||||||
|
|
||||||
fn make_project() -> Project {
|
fn make_project() -> Project {
|
||||||
Project {
|
Project {
|
||||||
id: "p1".to_string(),
|
id: "p1".to_string(),
|
||||||
|
|||||||
@@ -30,15 +30,20 @@ fn main() -> anyhow::Result<()> {
|
|||||||
iced::application("bDS", BdsApp::update, BdsApp::view)
|
iced::application("bDS", BdsApp::update, BdsApp::view)
|
||||||
.subscription(BdsApp::subscription)
|
.subscription(BdsApp::subscription)
|
||||||
.theme(|_| inputs::app_theme())
|
.theme(|_| inputs::app_theme())
|
||||||
.window(iced::window::Settings {
|
.window(desktop_window_settings(Some(icon)))
|
||||||
size: iced::Size::new(1200.0, 800.0),
|
|
||||||
icon: Some(icon),
|
|
||||||
..Default::default()
|
|
||||||
})
|
|
||||||
.run_with(BdsApp::new)?;
|
.run_with(BdsApp::new)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn desktop_window_settings(icon: Option<iced::window::Icon>) -> 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 {
|
fn current_platform() -> Platform {
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
return Platform::MacOs;
|
return Platform::MacOs;
|
||||||
@@ -49,3 +54,13 @@ fn current_platform() -> Platform {
|
|||||||
#[allow(unreachable_code)]
|
#[allow(unreachable_code)]
|
||||||
Platform::Windows
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ surface LayoutControlSurface {
|
|||||||
TogglePanelRequested()
|
TogglePanelRequested()
|
||||||
ToggleAssistantSidebarRequested()
|
ToggleAssistantSidebarRequested()
|
||||||
ActivityClicked(activity_id)
|
ActivityClicked(activity_id)
|
||||||
|
CloseWindowRequested()
|
||||||
}
|
}
|
||||||
|
|
||||||
surface LayoutRuntimeSurface {
|
surface LayoutRuntimeSurface {
|
||||||
@@ -55,6 +56,13 @@ value AppShell {
|
|||||||
status_bar: StatusBar
|
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 {
|
surface AppShellSurface {
|
||||||
context shell: AppShell
|
context shell: AppShell
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user