diff --git a/src/app/mod.rs b/src/app/mod.rs index 9127d864c..f4972877f 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -1337,6 +1337,8 @@ pub(super) struct OpenCADStudio { style_rename: Option, /// Edit buffer for the inline rename text input. style_rename_buf: String, + /// The style manager the open rename belongs to. + style_rename_kind: Option, /// Active style-manager transaction. Edits mutate the document live for an /// in-dialog preview but only persist on Apply; closing without Apply /// restores this snapshot. `None` when no style manager is staging. @@ -3078,6 +3080,10 @@ pub enum Message { LayerLineweightSet(LineWeight), LayerTransparencyEdit(String, String), LayerRenameCommit, + /// The widget focused after a click while a layer rename is open. + LayerRenameFocus(Option), + /// The widget focused after a click while a style rename is open. + StyleRenameFocus(Option), // ── Layer State Manager ───────────────────────────────────────────── LayerStateManagerOpen, LayerStateManagerSelect(String), @@ -4880,6 +4886,7 @@ impl OpenCADStudio { // TextStyle font browser style_rename: None, style_rename_buf: String::new(), + style_rename_kind: None, style_stage: None, textstyle_selected: "Standard".to_string(), textstyle_tab: 0, diff --git a/src/app/style_ops.rs b/src/app/style_ops.rs index c20ef77fb..1cea49a71 100644 --- a/src/app/style_ops.rs +++ b/src/app/style_ops.rs @@ -710,7 +710,7 @@ impl OpenCADStudio { // closing the window without Apply calls `style_stage_discard`, which // restores the snapshot taken when the manager opened. - pub(super) fn style_new(&mut self, kind: StyleKind) { + pub(super) fn style_new(&mut self, kind: StyleKind) -> iced::Task { let i = self.active_tab; let name = self.unique_new_name(kind); let h = self.tabs[i].scene.document.allocate_handle(); @@ -720,21 +720,37 @@ impl OpenCADStudio { self.after_style_change(kind); self.command_line .push_output(crate::tf!("Style '{name}' created.").as_ref()); + self.rename_new_style(kind, name) } - pub(super) fn style_copy(&mut self, kind: StyleKind) { + pub(super) fn style_copy(&mut self, kind: StyleKind) -> iced::Task { let i = self.active_tab; let src = self.style_selected(kind); let name = self.unique_suffixed_name(kind, &src); let h = self.tabs[i].scene.document.allocate_handle(); if !self.clone_style_as(kind, &src, &name, h) { - return; + return iced::Task::none(); } self.set_style_selected(kind, name.clone()); self.load_style_bufs(kind); self.after_style_change(kind); self.command_line .push_output(crate::tf!("Style '{name}' created.").as_ref()); + self.rename_new_style(kind, name) + } + + /// A new style opens with its generated name ready to be typed over. + fn rename_new_style( + &mut self, + kind: StyleKind, + name: String, + ) -> iced::Task { + self.style_rename_start(kind, name); + let id = crate::ui::style::style_list::rename_input_id(); + iced::Task::batch([ + iced::widget::operation::focus(id.clone()), + iced::widget::operation::select_all(id), + ]) } pub(super) fn style_delete(&mut self, kind: StyleKind) { @@ -770,6 +786,7 @@ impl OpenCADStudio { self.load_style_bufs(kind); self.style_rename_buf = name.clone(); self.style_rename = Some(name); + self.style_rename_kind = Some(kind); } /// Commit the inline rename. No-op (with feedback) on empty / unchanged / diff --git a/src/app/update/mod.rs b/src/app/update/mod.rs index 23f1743fd..33bc51dac 100644 --- a/src/app/update/mod.rs +++ b/src/app/update/mod.rs @@ -4114,6 +4114,21 @@ impl OpenCADStudio { Message::LayerRenameCommit => self.on_layer_rename_commit(), + // A click moved focus off the name field: keep what was typed, as + // Enter would. An untouched name stays open (the field may not have + // taken focus yet on the click that opened it). + Message::LayerRenameFocus(focused) => { + let layers = &self.tabs[self.active_tab].layers; + let typed = layers + .editing + .as_ref() + .is_some_and(|name| *name != layers.edit_buf); + if typed && focused != Some(crate::ui::window::layers::rename_input_id()) { + return self.on_layer_rename_commit(); + } + Task::none() + } + Message::LayerColorPickerToggle(name) => { let i = self.active_tab; let Some(idx) = self.layer_panel_index(i, &name) else { @@ -7657,12 +7672,23 @@ impl OpenCADStudio { Message::PropAttrCommit(tag) => self.on_prop_attr_commit(tag), Message::PropPointerPressed => { + let mut rename_blur = if self.tabs[self.active_tab].layers.editing.is_some() { + crate::ui::properties::focused_widget_task(Message::LayerRenameFocus) + } else { + Task::none() + }; + if self.style_rename.is_some() { + rename_blur = Task::batch([ + rename_blur, + crate::ui::properties::focused_widget_task(Message::StyleRenameFocus), + ]); + } if !self.dock_panel_visible(crate::ui::dock::PanelId::Properties) && !self.dock_panel_visible(crate::ui::dock::PanelId::GraphicAttributes) { - return Task::none(); + return rename_blur; } - crate::ui::properties::sync_active_field_task() + Task::batch([crate::ui::properties::sync_active_field_task(), rename_blur]) } Message::PropSyncActive(focused) => { @@ -10972,14 +10998,8 @@ impl OpenCADStudio { } Task::none() } - Message::TextStyleDialogNew => { - self.style_new(crate::app::StyleKind::Text); - Task::none() - } - Message::TextStyleDialogCopy => { - self.style_copy(crate::app::StyleKind::Text); - Task::none() - } + Message::TextStyleDialogNew => self.style_new(crate::app::StyleKind::Text), + Message::TextStyleDialogCopy => self.style_copy(crate::app::StyleKind::Text), Message::TextStyleDialogDelete => { self.style_delete(crate::app::StyleKind::Text); Task::none() @@ -10999,6 +11019,18 @@ impl OpenCADStudio { self.style_rename_commit(kind); Task::none() } + Message::StyleRenameFocus(focused) => { + let typed = self + .style_rename + .as_ref() + .is_some_and(|name| *name != self.style_rename_buf); + if typed && focused != Some(crate::ui::style::style_list::rename_input_id()) { + if let Some(kind) = self.style_rename_kind { + self.style_rename_commit(kind); + } + } + Task::none() + } Message::StyleRenameCancel => { self.style_rename_cancel(); Task::none() @@ -11271,14 +11303,8 @@ impl OpenCADStudio { Task::none() } - Message::TableStyleDialogNew => { - self.style_new(crate::app::StyleKind::Table); - Task::none() - } - Message::TableStyleDialogCopy => { - self.style_copy(crate::app::StyleKind::Table); - Task::none() - } + Message::TableStyleDialogNew => self.style_new(crate::app::StyleKind::Table), + Message::TableStyleDialogCopy => self.style_copy(crate::app::StyleKind::Table), Message::TableStyleDialogDelete => { self.style_delete(crate::app::StyleKind::Table); Task::none() @@ -11344,14 +11370,8 @@ impl OpenCADStudio { self.style_stage_commit(); Task::none() } - Message::MlStyleDialogNew => { - self.style_new(crate::app::StyleKind::MLine); - Task::none() - } - Message::MlStyleDialogCopy => { - self.style_copy(crate::app::StyleKind::MLine); - Task::none() - } + Message::MlStyleDialogNew => self.style_new(crate::app::StyleKind::MLine), + Message::MlStyleDialogCopy => self.style_copy(crate::app::StyleKind::MLine), Message::MlStyleDialogDelete => { self.style_delete(crate::app::StyleKind::MLine); Task::none() @@ -11449,14 +11469,8 @@ impl OpenCADStudio { Task::none() } Message::MLeaderStyleDialogSetCurrent => self.on_mleader_style_dialog_set_current(), - Message::MLeaderStyleDialogNew => { - self.style_new(crate::app::StyleKind::MLeader); - Task::none() - } - Message::MLeaderStyleDialogCopy => { - self.style_copy(crate::app::StyleKind::MLeader); - Task::none() - } + Message::MLeaderStyleDialogNew => self.style_new(crate::app::StyleKind::MLeader), + Message::MLeaderStyleDialogCopy => self.style_copy(crate::app::StyleKind::MLeader), Message::MLeaderStyleDialogDelete => { self.style_delete(crate::app::StyleKind::MLeader); Task::none() @@ -11531,14 +11545,8 @@ impl OpenCADStudio { self.dimstyle_compare = name; Task::none() } - Message::DimStyleDialogNew => { - self.style_new(crate::app::StyleKind::Dim); - Task::none() - } - Message::DimStyleDialogCopy => { - self.style_copy(crate::app::StyleKind::Dim); - Task::none() - } + Message::DimStyleDialogNew => self.style_new(crate::app::StyleKind::Dim), + Message::DimStyleDialogCopy => self.style_copy(crate::app::StyleKind::Dim), Message::DimStyleDialogSetCurrent => { // Staged: persists on Apply. let i = self.active_tab; diff --git a/src/ui/properties.rs b/src/ui/properties.rs index 20e0eb9a8..6774f005d 100644 --- a/src/ui/properties.rs +++ b/src/ui/properties.rs @@ -510,6 +510,13 @@ pub fn build_field_key_map(sections: &[PropSection]) -> HashMap iced::Task { + focused_widget_task(Message::PropSyncActive) +} + +/// Ask the widget tree which field holds keyboard focus. +pub fn focused_widget_task( + on_result: fn(Option) -> Message, +) -> iced::Task { use iced::advanced::widget::operation::{Focusable, Outcome}; use iced::advanced::widget::{Id, Operation}; #[derive(Default)] @@ -534,7 +541,7 @@ pub fn sync_active_field_task() -> iced::Task { Outcome::Some(self.focused.clone()) } } - iced::advanced::widget::operate(FocusedId::default()).map(Message::PropSyncActive) + iced::advanced::widget::operate(FocusedId::default()).map(on_result) } /// A translated choice label paired with the unchanged value stored in the diff --git a/src/ui/window/layers.rs b/src/ui/window/layers.rs index 5b993b7e6..747b2a9d9 100644 --- a/src/ui/window/layers.rs +++ b/src/ui/window/layers.rs @@ -737,6 +737,13 @@ fn name_tip<'a>(name: &'a str) -> Element<'a, Message> { .into() } + +/// The layer-name field while a rename is open; a click that moves focus +/// elsewhere commits the typed name. +pub fn rename_input_id() -> iced::widget::Id { + iced::widget::Id::new("layer-rename-input") +} + fn layer_row<'a>( index: usize, layer: &'a Layer, @@ -803,6 +810,7 @@ fn layer_row<'a>( // Name cell let name_cell: Element<'_, Message> = if is_editing { text_input("", edit_buf) + .id(rename_input_id()) .on_input(Message::LayerRenameEdit) .on_submit(Message::LayerRenameCommit) .size(FONT_SZ)