feat(ui): migrate the last dialogs to modals — fully single-window (Plan B)
Move the unsaved-changes prompt, the Save As browser and the file-association prompt onto `active_modal` / `modal_content`, reusing their existing open/close helpers (now just set/clear active_modal). The daemon `view` no longer dispatches per window — every dialog is an in-canvas modal, so the app is single-window on native and web alike. `close_active_modal` handles their ✕: the unsaved prompt cancels the pending close, the association prompt records that it was answered. Native + web build; 75 tests pass. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
c24c6885d0
commit
94b906d95b
3 changed files with 56 additions and 81 deletions
|
|
@ -606,6 +606,9 @@ pub enum ModalKind {
|
||||||
MlStyle,
|
MlStyle,
|
||||||
MLeaderStyle,
|
MLeaderStyle,
|
||||||
DimStyle,
|
DimStyle,
|
||||||
|
Unsaved,
|
||||||
|
SaveDialog,
|
||||||
|
AssocPrompt,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Identifies a DimStyle field that can be edited in the dialog.
|
/// Identifies a DimStyle field that can be edited in the dialog.
|
||||||
|
|
@ -1687,18 +1690,10 @@ impl OpenCADStudio {
|
||||||
// One-time prompt offering to make Open CAD Studio the default app for
|
// One-time prompt offering to make Open CAD Studio the default app for
|
||||||
// .dwg / .dxf. Shown only on the first launch that hasn't answered it
|
// .dwg / .dxf. Shown only on the first launch that hasn't answered it
|
||||||
// yet; the flag is persisted so we never ask twice.
|
// yet; the flag is persisted so we never ask twice.
|
||||||
let assoc_prompt: Task<Message> = if s.default_assoc_prompted {
|
let assoc_prompt: Task<Message> = Task::none();
|
||||||
Task::none()
|
if !s.default_assoc_prompted {
|
||||||
} else {
|
s.active_modal = Some(ModalKind::AssocPrompt);
|
||||||
let (id, open) = window::open(window::Settings {
|
}
|
||||||
size: iced::Size::new(440.0, 210.0),
|
|
||||||
resizable: false,
|
|
||||||
level: window::Level::AlwaysOnTop,
|
|
||||||
..Default::default()
|
|
||||||
});
|
|
||||||
s.assoc_prompt_window = Some(id);
|
|
||||||
open.map(|_| Message::Noop)
|
|
||||||
};
|
|
||||||
(
|
(
|
||||||
s,
|
s,
|
||||||
Task::batch([open_main, check_update, focus_cmd, cli_open, assoc_prompt]),
|
Task::batch([open_main, check_update, focus_cmd, cli_open, assoc_prompt]),
|
||||||
|
|
|
||||||
|
|
@ -117,6 +117,9 @@ impl OpenCADStudio {
|
||||||
self.ribbon.deactivate_tool_if("KEYBOARD");
|
self.ribbon.deactivate_tool_if("KEYBOARD");
|
||||||
}
|
}
|
||||||
Some(About) => self.ribbon.deactivate_tool_if("ABOUT"),
|
Some(About) => self.ribbon.deactivate_tool_if("ABOUT"),
|
||||||
|
// Dismissing these via ✕ is the cancel/decline path.
|
||||||
|
Some(Unsaved) => self.pending_close = None,
|
||||||
|
Some(AssocPrompt) => self.mark_assoc_prompted(),
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
self.active_modal = None;
|
self.active_modal = None;
|
||||||
|
|
@ -5784,24 +5787,16 @@ impl OpenCADStudio {
|
||||||
}
|
}
|
||||||
Message::AssocPromptYes => {
|
Message::AssocPromptYes => {
|
||||||
self.mark_assoc_prompted();
|
self.mark_assoc_prompted();
|
||||||
let close = if let Some(id) = self.assoc_prompt_window.take() {
|
self.active_modal = None;
|
||||||
window::close(id)
|
Task::perform(
|
||||||
} else {
|
|
||||||
Task::none()
|
|
||||||
};
|
|
||||||
let run = Task::perform(
|
|
||||||
crate::io::file_association::set_default_app(),
|
crate::io::file_association::set_default_app(),
|
||||||
Message::AssocResult,
|
Message::AssocResult,
|
||||||
);
|
)
|
||||||
Task::batch([close, run])
|
|
||||||
}
|
}
|
||||||
Message::AssocPromptNo => {
|
Message::AssocPromptNo => {
|
||||||
self.mark_assoc_prompted();
|
self.mark_assoc_prompted();
|
||||||
if let Some(id) = self.assoc_prompt_window.take() {
|
self.active_modal = None;
|
||||||
window::close(id)
|
Task::none()
|
||||||
} else {
|
|
||||||
Task::none()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Message::AssocResult(result) => {
|
Message::AssocResult(result) => {
|
||||||
match result {
|
match result {
|
||||||
|
|
@ -8534,44 +8529,27 @@ impl OpenCADStudio {
|
||||||
self.save_dialog_filename = format!("{}.{ext}", self.tabs[tab_idx].tab_display_name());
|
self.save_dialog_filename = format!("{}.{ext}", self.tabs[tab_idx].tab_display_name());
|
||||||
}
|
}
|
||||||
self.save_dialog_entries = crate::io::read_dir_entries(&self.save_dialog_folder.clone());
|
self.save_dialog_entries = crate::io::read_dir_entries(&self.save_dialog_folder.clone());
|
||||||
let (id, task) = window::open(window::Settings {
|
self.active_modal = Some(super::ModalKind::SaveDialog);
|
||||||
size: iced::Size::new(560.0, 480.0),
|
Task::none()
|
||||||
resizable: true,
|
|
||||||
level: window::Level::AlwaysOnTop,
|
|
||||||
..Default::default()
|
|
||||||
});
|
|
||||||
self.save_dialog_window = Some(id);
|
|
||||||
task.map(|_| Message::Noop)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn close_save_dialog_window(&mut self) -> Task<Message> {
|
fn close_save_dialog_window(&mut self) -> Task<Message> {
|
||||||
if let Some(id) = self.save_dialog_window.take() {
|
if self.active_modal == Some(super::ModalKind::SaveDialog) {
|
||||||
window::close(id)
|
self.active_modal = None;
|
||||||
} else {
|
|
||||||
Task::none()
|
|
||||||
}
|
}
|
||||||
|
Task::none()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn open_unsaved_dialog_window(&mut self) -> Task<Message> {
|
fn open_unsaved_dialog_window(&mut self) -> Task<Message> {
|
||||||
if let Some(id) = self.unsaved_dialog_window {
|
self.active_modal = Some(super::ModalKind::Unsaved);
|
||||||
return window::gain_focus(id);
|
Task::none()
|
||||||
}
|
|
||||||
let (id, task) = window::open(window::Settings {
|
|
||||||
size: iced::Size::new(420.0, 155.0),
|
|
||||||
resizable: false,
|
|
||||||
level: window::Level::AlwaysOnTop,
|
|
||||||
..Default::default()
|
|
||||||
});
|
|
||||||
self.unsaved_dialog_window = Some(id);
|
|
||||||
task.map(|_| Message::Noop)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn close_unsaved_dialog_window(&mut self) -> Task<Message> {
|
fn close_unsaved_dialog_window(&mut self) -> Task<Message> {
|
||||||
if let Some(id) = self.unsaved_dialog_window.take() {
|
if self.active_modal == Some(super::ModalKind::Unsaved) {
|
||||||
window::close(id)
|
self.active_modal = None;
|
||||||
} else {
|
|
||||||
Task::none()
|
|
||||||
}
|
}
|
||||||
|
Task::none()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn load_textstyle_bufs(&mut self, tab: usize) {
|
pub(super) fn load_textstyle_bufs(&mut self, tab: usize) {
|
||||||
|
|
|
||||||
|
|
@ -40,35 +40,9 @@ impl std::fmt::Display for RenderModeChoice {
|
||||||
impl OpenCADStudio {
|
impl OpenCADStudio {
|
||||||
pub fn view(&self, window_id: window::Id) -> Element<'_, Message> {
|
pub fn view(&self, window_id: window::Id) -> Element<'_, Message> {
|
||||||
// ── Floating panel windows ─────────────────────────────────────────
|
// ── Floating panel windows ─────────────────────────────────────────
|
||||||
if Some(window_id) == self.assoc_prompt_window {
|
// All dialogs are in-canvas modals now (Plan B); view_main stacks the
|
||||||
return default_assoc_dialog_window();
|
// active one. `window_id` is unused — there is only the main window.
|
||||||
}
|
let _ = window_id;
|
||||||
if Some(window_id) == self.unsaved_dialog_window {
|
|
||||||
let tab_name = match &self.pending_close {
|
|
||||||
Some(super::PendingClose::Tab(idx)) => self
|
|
||||||
.tabs
|
|
||||||
.get(*idx)
|
|
||||||
.map(|t| t.tab_display_name())
|
|
||||||
.unwrap_or_default(),
|
|
||||||
Some(super::PendingClose::Quit) => self
|
|
||||||
.tabs
|
|
||||||
.iter()
|
|
||||||
.find(|t| t.dirty)
|
|
||||||
.map(|t| t.tab_display_name())
|
|
||||||
.unwrap_or_default(),
|
|
||||||
None => String::new(),
|
|
||||||
};
|
|
||||||
return unsaved_changes_dialog_window(&tab_name);
|
|
||||||
}
|
|
||||||
if Some(window_id) == self.save_dialog_window {
|
|
||||||
return save_as_dialog_window(
|
|
||||||
&self.save_dialog_filename,
|
|
||||||
&self.save_dialog_folder,
|
|
||||||
&self.save_dialog_entries,
|
|
||||||
&self.save_dialog_format,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
self.view_main()
|
self.view_main()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1543,6 +1517,34 @@ impl OpenCADStudio {
|
||||||
&self.style_rename_buf,
|
&self.style_rename_buf,
|
||||||
), 720, 560)
|
), 720, 560)
|
||||||
}
|
}
|
||||||
|
super::ModalKind::AssocPrompt => sized(default_assoc_dialog_window(), 440, 210),
|
||||||
|
super::ModalKind::Unsaved => {
|
||||||
|
let tab_name = match &self.pending_close {
|
||||||
|
Some(super::PendingClose::Tab(idx)) => self
|
||||||
|
.tabs
|
||||||
|
.get(*idx)
|
||||||
|
.map(|t| t.tab_display_name())
|
||||||
|
.unwrap_or_default(),
|
||||||
|
Some(super::PendingClose::Quit) => self
|
||||||
|
.tabs
|
||||||
|
.iter()
|
||||||
|
.find(|t| t.dirty)
|
||||||
|
.map(|t| t.tab_display_name())
|
||||||
|
.unwrap_or_default(),
|
||||||
|
None => String::new(),
|
||||||
|
};
|
||||||
|
sized(unsaved_changes_dialog_window(&tab_name), 420, 160)
|
||||||
|
}
|
||||||
|
super::ModalKind::SaveDialog => sized(
|
||||||
|
save_as_dialog_window(
|
||||||
|
&self.save_dialog_filename,
|
||||||
|
&self.save_dialog_folder,
|
||||||
|
&self.save_dialog_entries,
|
||||||
|
&self.save_dialog_format,
|
||||||
|
),
|
||||||
|
560,
|
||||||
|
480,
|
||||||
|
),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue