fix(ui): keep a typed layer or style name when the field loses focus

Renaming a layer or a style was only applied on Enter; clicking anywhere
else dropped the typed name. A click that moves focus off a field whose
text changed now commits it. New and Copy in the style managers open the
generated name for renaming, so a new style can be named right away.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Hakan SevenandClaude Opus 5.5 committed 2026-10-13 11:19:12 +03:00
1 parent 4639546472
commit f6503ad4d3
5 files changed
+93 -46

No files matched your search

+7
View File
@@ -1337,6 +1337,8 @@ pub(super) struct OpenCADStudio {
style_rename: Option<String>,
/// Edit buffer for the inline rename text input.
style_rename_buf: String,
/// The style manager the open rename belongs to.
style_rename_kind: Option<style_ops::StyleKind>,
/// 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<iced::advanced::widget::Id>),
/// The widget focused after a click while a style rename is open.
StyleRenameFocus(Option<iced::advanced::widget::Id>),
// ── 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,
+20 -3
View File
@@ -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<crate::app::Message> {
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<crate::app::Message> {
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<crate::app::Message> {
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 /
+50 -42
View File
@@ -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;
+8 -1
View File
@@ -510,6 +510,13 @@ pub fn build_field_key_map(sections: &[PropSection]) -> HashMap<iced::widget::Id
/// the `WebFieldCopy` operation idiom: `focusable` fires for every focusable
/// widget, and we record the one that `is_focused()`.
pub fn sync_active_field_task() -> iced::Task<Message> {
focused_widget_task(Message::PropSyncActive)
}
/// Ask the widget tree which field holds keyboard focus.
pub fn focused_widget_task(
on_result: fn(Option<iced::advanced::widget::Id>) -> Message,
) -> iced::Task<Message> {
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<Message> {
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
+8
View File
@@ -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)