From 33a105a619d2142fb2cf41d1956b90db00000462 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 7 Oct 2026 19:01:25 +0000 Subject: [PATCH] Graphic Attributes: quieter swatches, Enter in linetype scale, undo names - Colour swatches carry no *VARIES* or ByBlock text; the L / B / C / V button already shows that. - A separator sits between "Create Layer with active settings" and the "Create fills on the current layer" checkbox. - The linetype scale field is a plain text field like the Properties panel: the first click selects its value, Enter applies it (decimal comma and expressions accepted) instead of repeating the last command. This drops the iced_aw number_input feature again. - Undo/redo name fill changes "Fill change" and linetype changes "Linetype", in all 21 locales. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01VgkJN8DEojuPTumAXLHotu --- Cargo.lock | 12 --- Cargo.toml | 2 +- locales/ar-SA/opencadstudio.ftl | 2 +- locales/bg-BG/opencadstudio.ftl | 2 +- locales/cs-CZ/opencadstudio.ftl | 2 +- locales/de-DE/opencadstudio.ftl | 2 +- locales/el-GR/opencadstudio.ftl | 2 +- locales/en-US/opencadstudio.ftl | 2 +- locales/es-ES/opencadstudio.ftl | 2 +- locales/fi-FI/opencadstudio.ftl | 2 +- locales/fr-FR/opencadstudio.ftl | 2 +- locales/hi-IN/opencadstudio.ftl | 2 +- locales/hu-HU/opencadstudio.ftl | 2 +- locales/it-IT/opencadstudio.ftl | 2 +- locales/ja-JP/opencadstudio.ftl | 2 +- locales/ko-KR/opencadstudio.ftl | 2 +- locales/nl-NL/opencadstudio.ftl | 2 +- locales/pl-PL/opencadstudio.ftl | 2 +- locales/pt-BR/opencadstudio.ftl | 2 +- locales/ru-RU/opencadstudio.ftl | 2 +- locales/tr-TR/opencadstudio.ftl | 2 +- locales/zh-CN/opencadstudio.ftl | 2 +- locales/zh-TW/opencadstudio.ftl | 2 +- src/app/update/graphic_attributes.rs | 88 ++++++++++++++++++++- src/app/update/mod.rs | 17 +++- src/locale_catalog.rs | 2 +- src/ui/window/graphic_attributes.rs | 113 +++++++++++---------------- 27 files changed, 168 insertions(+), 108 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 19b6a71c0..2c94eff96 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2571,8 +2571,6 @@ dependencies = [ "iced_core", "iced_fonts_macros", "iced_widget", - "num-format", - "num-traits", "web-time", ] @@ -3770,16 +3768,6 @@ dependencies = [ "syn 2.0.119", ] -[[package]] -name = "num-format" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a652d9771a63711fd3c3deb670acfbe5c30a4072e664d7a3bf5a9e1056ac72c3" -dependencies = [ - "arrayvec", - "itoa", -] - [[package]] name = "num-integer" version = "0.1.46" diff --git a/Cargo.toml b/Cargo.toml index 1805b6b8a..8d5dbfc21 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,7 +26,7 @@ url = "2" iced = { git = "https://github.com/iced-rs/iced.git", rev = "23604ff22ab0aad9e00b9327cb7b8546ed84db39", features = ["image", "svg", "advanced", "canvas", "smol", "markdown"] } iced_runtime = { git = "https://github.com/iced-rs/iced.git", rev = "23604ff22ab0aad9e00b9327cb7b8546ed84db39" } iced_core = { git = "https://github.com/iced-rs/iced.git", rev = "23604ff22ab0aad9e00b9327cb7b8546ed84db39" } -iced_aw = { git = "https://github.com/HakanSeven12/iced_aw.git", branch = "agent/fix-iced-fonts", features = ["menu", "context_menu", "color_picker", "drop_down", "number_input"] } +iced_aw = { git = "https://github.com/HakanSeven12/iced_aw.git", branch = "agent/fix-iced-fonts", features = ["menu", "context_menu", "color_picker", "drop_down"] } bytemuck = { version = "1.25", features = ["derive"] } glam = { version = "0.33", features = ["bytemuck"] } rfd = "0.17" diff --git a/locales/ar-SA/opencadstudio.ftl b/locales/ar-SA/opencadstudio.ftl index 0766b2552..69fc56032 100644 --- a/locales/ar-SA/opencadstudio.ftl +++ b/locales/ar-SA/opencadstudio.ftl @@ -5652,7 +5652,7 @@ graphic-attributes = .one-unsupported = سمات الرسم: لم يتم تغيير كائن واحد غير مدعوم. .unsupported-skipped = سمات الرسم: لم يتم تغيير __ocs_fmt_0__ من الكائنات غير المدعومة. .fill-transparency = شفافية التعبئة - .line-linetype = نوع خط الخطوط + .fill-change = تغيير التعبئة .line-transparency = شفافية الخطوط .cylindrical-short = أسطواني .spherical-short = كروي diff --git a/locales/bg-BG/opencadstudio.ftl b/locales/bg-BG/opencadstudio.ftl index bc8e77fe8..ee13f803b 100644 --- a/locales/bg-BG/opencadstudio.ftl +++ b/locales/bg-BG/opencadstudio.ftl @@ -5640,7 +5640,7 @@ graphic-attributes = .one-unsupported = Графични атрибути: 1 неподдържан обект не беше променен. .unsupported-skipped = Графични атрибути: __ocs_fmt_0__ неподдържани обекта не бяха променени. .fill-transparency = Прозрачност на запълването - .line-linetype = Тип линия на линиите + .fill-change = Промяна на запълването .line-transparency = Прозрачност на линиите .cylindrical-short = Цил. .spherical-short = Сфер. diff --git a/locales/cs-CZ/opencadstudio.ftl b/locales/cs-CZ/opencadstudio.ftl index bfbd5cec9..3011095ae 100644 --- a/locales/cs-CZ/opencadstudio.ftl +++ b/locales/cs-CZ/opencadstudio.ftl @@ -5637,7 +5637,7 @@ graphic-attributes = .one-unsupported = Grafické atributy: 1 nepodporovaný objekt nebyl změněn. .unsupported-skipped = Grafické atributy: nepodporované objekty (__ocs_fmt_0__) nebyly změněny. .fill-transparency = Průhlednost výplně - .line-linetype = Typ čáry + .fill-change = Změna výplně .line-transparency = Průhlednost čar .cylindrical-short = Válc. .spherical-short = Kul. diff --git a/locales/de-DE/opencadstudio.ftl b/locales/de-DE/opencadstudio.ftl index 9708790cf..49a9892c6 100644 --- a/locales/de-DE/opencadstudio.ftl +++ b/locales/de-DE/opencadstudio.ftl @@ -5638,7 +5638,7 @@ graphic-attributes = .one-unsupported = Grafikattribute: 1 nicht unterstütztes Objekt wurde nicht geändert. .unsupported-skipped = Grafikattribute: __ocs_fmt_0__ nicht unterstützte Objekte wurden nicht geändert. .fill-transparency = Füllungstransparenz - .line-linetype = Linientyp der Linien + .fill-change = Füllung ändern .line-transparency = Linientransparenz .cylindrical-short = Zyl. .spherical-short = Kugel diff --git a/locales/el-GR/opencadstudio.ftl b/locales/el-GR/opencadstudio.ftl index e9aac1bb9..3b6bf7812 100644 --- a/locales/el-GR/opencadstudio.ftl +++ b/locales/el-GR/opencadstudio.ftl @@ -5640,7 +5640,7 @@ graphic-attributes = .one-unsupported = Γραφικά χαρακτηριστικά: 1 μη υποστηριζόμενο αντικείμενο δεν άλλαξε. .unsupported-skipped = Γραφικά χαρακτηριστικά: __ocs_fmt_0__ μη υποστηριζόμενα αντικείμενα δεν άλλαξαν. .fill-transparency = Διαφάνεια γεμίσματος - .line-linetype = Τύπος γραμμής + .fill-change = Αλλαγή γεμίσματος .line-transparency = Διαφάνεια γραμμών .cylindrical-short = Κυλ. .spherical-short = Σφαιρ. diff --git a/locales/en-US/opencadstudio.ftl b/locales/en-US/opencadstudio.ftl index 456f168f4..6d263460d 100644 --- a/locales/en-US/opencadstudio.ftl +++ b/locales/en-US/opencadstudio.ftl @@ -5641,7 +5641,7 @@ graphic-attributes = .one-unsupported = Graphic Attributes: 1 unsupported object was not changed. .unsupported-skipped = Graphic Attributes: __ocs_fmt_0__ unsupported objects were not changed. .fill-transparency = Fill transparency - .line-linetype = Line linetype + .fill-change = Fill change .line-transparency = Line transparency .cylindrical-short = Cyl. .spherical-short = Spher. diff --git a/locales/es-ES/opencadstudio.ftl b/locales/es-ES/opencadstudio.ftl index 8005d34de..835fe5b55 100644 --- a/locales/es-ES/opencadstudio.ftl +++ b/locales/es-ES/opencadstudio.ftl @@ -5640,7 +5640,7 @@ graphic-attributes = .one-unsupported = Atributos gráficos: no se modificó 1 objeto no admitido. .unsupported-skipped = Atributos gráficos: no se modificaron __ocs_fmt_0__ objetos no admitidos. .fill-transparency = Transparencia del relleno - .line-linetype = Tipo de línea + .fill-change = Cambio de relleno .line-transparency = Transparencia de línea .cylindrical-short = Cil. .spherical-short = Esfér. diff --git a/locales/fi-FI/opencadstudio.ftl b/locales/fi-FI/opencadstudio.ftl index 2cbd7647e..3fbc0d13f 100644 --- a/locales/fi-FI/opencadstudio.ftl +++ b/locales/fi-FI/opencadstudio.ftl @@ -5637,7 +5637,7 @@ graphic-attributes = .one-unsupported = Grafiikkamääritteet: 1 tukematonta objektia ei muutettu. .unsupported-skipped = Grafiikkamääritteet: __ocs_fmt_0__ tukematonta objektia jätettiin muuttamatta. .fill-transparency = Täytön läpinäkyvyys - .line-linetype = Viivojen viivatyyppi + .fill-change = Täytön muutos .line-transparency = Viivojen läpinäkyvyys .cylindrical-short = Lieriö .spherical-short = Pallo diff --git a/locales/fr-FR/opencadstudio.ftl b/locales/fr-FR/opencadstudio.ftl index 2d0f667ca..503f011fc 100644 --- a/locales/fr-FR/opencadstudio.ftl +++ b/locales/fr-FR/opencadstudio.ftl @@ -5639,7 +5639,7 @@ graphic-attributes = .one-unsupported = Attributs graphiques : 1 objet non pris en charge n'a pas été modifié. .unsupported-skipped = Attributs graphiques : __ocs_fmt_0__ objets non pris en charge n'ont pas été modifiés. .fill-transparency = Transparence du remplissage - .line-linetype = Type de ligne + .fill-change = Modification du remplissage .line-transparency = Transparence des lignes .cylindrical-short = Cyl. .spherical-short = Sphér. diff --git a/locales/hi-IN/opencadstudio.ftl b/locales/hi-IN/opencadstudio.ftl index 27fa7766e..076582fd6 100644 --- a/locales/hi-IN/opencadstudio.ftl +++ b/locales/hi-IN/opencadstudio.ftl @@ -5628,7 +5628,7 @@ graphic-attributes = .one-unsupported = ग्राफ़िक गुण: 1 असमर्थित ऑब्जेक्ट नहीं बदला गया। .unsupported-skipped = ग्राफ़िक गुण: __ocs_fmt_0__ असमर्थित ऑब्जेक्ट नहीं बदले गए। .fill-transparency = भराव पारदर्शिता - .line-linetype = रेखा का लाइनटाइप + .fill-change = भराव परिवर्तन .line-transparency = रेखा पारदर्शिता .cylindrical-short = बेलन. .spherical-short = गोल. diff --git a/locales/hu-HU/opencadstudio.ftl b/locales/hu-HU/opencadstudio.ftl index ce71c489d..9c2555673 100644 --- a/locales/hu-HU/opencadstudio.ftl +++ b/locales/hu-HU/opencadstudio.ftl @@ -5639,7 +5639,7 @@ graphic-attributes = .one-unsupported = Grafikus jellemzők: 1 nem támogatott objektum nem változott. .unsupported-skipped = Grafikus jellemzők: __ocs_fmt_0__ nem támogatott objektum nem változott. .fill-transparency = Kitöltés átlátszósága - .line-linetype = Vonaltípus + .fill-change = Kitöltés módosítása .line-transparency = Vonalak átlátszósága .cylindrical-short = Henger .spherical-short = Gömb diff --git a/locales/it-IT/opencadstudio.ftl b/locales/it-IT/opencadstudio.ftl index bac57efe9..89b0a7179 100644 --- a/locales/it-IT/opencadstudio.ftl +++ b/locales/it-IT/opencadstudio.ftl @@ -5639,7 +5639,7 @@ graphic-attributes = .one-unsupported = Attributi grafici: 1 oggetto non supportato non è stato modificato. .unsupported-skipped = Attributi grafici: __ocs_fmt_0__ oggetti non supportati non sono stati modificati. .fill-transparency = Trasparenza riempimento - .line-linetype = Tipo di linea + .fill-change = Modifica riempimento .line-transparency = Trasparenza linee .cylindrical-short = Cil. .spherical-short = Sfer. diff --git a/locales/ja-JP/opencadstudio.ftl b/locales/ja-JP/opencadstudio.ftl index fec230084..1e5cfd68e 100644 --- a/locales/ja-JP/opencadstudio.ftl +++ b/locales/ja-JP/opencadstudio.ftl @@ -5628,7 +5628,7 @@ graphic-attributes = .one-unsupported = グラフィック属性: サポートされていないオブジェクト 1 個は変更されませんでした。 .unsupported-skipped = グラフィック属性: サポートされていないオブジェクト __ocs_fmt_0__ 個は変更されませんでした。 .fill-transparency = 塗り潰しの透過性 - .line-linetype = 線の線種 + .fill-change = 塗り潰しの変更 .line-transparency = 線の透過性 .cylindrical-short = 円柱 .spherical-short = 球 diff --git a/locales/ko-KR/opencadstudio.ftl b/locales/ko-KR/opencadstudio.ftl index c1fcbc464..90172b57e 100644 --- a/locales/ko-KR/opencadstudio.ftl +++ b/locales/ko-KR/opencadstudio.ftl @@ -5639,7 +5639,7 @@ graphic-attributes = .one-unsupported = 그래픽 속성: 지원되지 않는 객체 1개가 변경되지 않았습니다. .unsupported-skipped = 그래픽 속성: 지원되지 않는 객체 __ocs_fmt_0__개가 변경되지 않았습니다. .fill-transparency = 채우기 투명도 - .line-linetype = 선의 선종류 + .fill-change = 채우기 변경 .line-transparency = 선 투명도 .cylindrical-short = 원통 .spherical-short = 구 diff --git a/locales/nl-NL/opencadstudio.ftl b/locales/nl-NL/opencadstudio.ftl index 2c9de8d1a..fdf7c69ea 100644 --- a/locales/nl-NL/opencadstudio.ftl +++ b/locales/nl-NL/opencadstudio.ftl @@ -5638,7 +5638,7 @@ graphic-attributes = .one-unsupported = Grafische attributen: 1 niet-ondersteund object is niet gewijzigd. .unsupported-skipped = Grafische attributen: __ocs_fmt_0__ niet-ondersteunde objecten zijn niet gewijzigd. .fill-transparency = Transparantie vulling - .line-linetype = Lijntype lijnen + .fill-change = Vulling wijzigen .line-transparency = Transparantie lijnen .cylindrical-short = Cil. .spherical-short = Bol diff --git a/locales/pl-PL/opencadstudio.ftl b/locales/pl-PL/opencadstudio.ftl index 70af7f1fa..f2c1e5c23 100644 --- a/locales/pl-PL/opencadstudio.ftl +++ b/locales/pl-PL/opencadstudio.ftl @@ -5639,7 +5639,7 @@ graphic-attributes = .one-unsupported = Atrybuty graficzne: 1 nieobsługiwany obiekt nie został zmieniony. .unsupported-skipped = Atrybuty graficzne: nieobsługiwane obiekty (__ocs_fmt_0__) nie zostały zmienione. .fill-transparency = Przezroczystość wypełnienia - .line-linetype = Rodzaj linii + .fill-change = Zmiana wypełnienia .line-transparency = Przezroczystość linii .cylindrical-short = Walc. .spherical-short = Sfer. diff --git a/locales/pt-BR/opencadstudio.ftl b/locales/pt-BR/opencadstudio.ftl index c007c8458..312cc29f8 100644 --- a/locales/pt-BR/opencadstudio.ftl +++ b/locales/pt-BR/opencadstudio.ftl @@ -5640,7 +5640,7 @@ graphic-attributes = .one-unsupported = Atributos gráficos: 1 objeto não suportado não foi alterado. .unsupported-skipped = Atributos gráficos: __ocs_fmt_0__ objetos não suportados não foram alterados. .fill-transparency = Transparência do preenchimento - .line-linetype = Tipo de linha + .fill-change = Alteração de preenchimento .line-transparency = Transparência da linha .cylindrical-short = Cil. .spherical-short = Esfér. diff --git a/locales/ru-RU/opencadstudio.ftl b/locales/ru-RU/opencadstudio.ftl index 3b4d84356..c708c089f 100644 --- a/locales/ru-RU/opencadstudio.ftl +++ b/locales/ru-RU/opencadstudio.ftl @@ -5641,7 +5641,7 @@ graphic-attributes = .one-unsupported = Графические атрибуты: 1 неподдерживаемый объект не изменён. .unsupported-skipped = Графические атрибуты: неподдерживаемые объекты (__ocs_fmt_0__) не изменены. .fill-transparency = Прозрачность заливки - .line-linetype = Тип линий + .fill-change = Изменение заливки .line-transparency = Прозрачность линий .cylindrical-short = Цил. .spherical-short = Сфер. diff --git a/locales/tr-TR/opencadstudio.ftl b/locales/tr-TR/opencadstudio.ftl index f94360dbd..81e5f8d1d 100644 --- a/locales/tr-TR/opencadstudio.ftl +++ b/locales/tr-TR/opencadstudio.ftl @@ -5563,7 +5563,7 @@ graphic-attributes = .one-unsupported = Grafik öznitelikleri: desteklenmeyen 1 nesne değiştirilmedi. .unsupported-skipped = Grafik öznitelikleri: desteklenmeyen __ocs_fmt_0__ nesne değiştirilmedi. .fill-transparency = Dolgu saydamlığı - .line-linetype = Çizgi tipi + .fill-change = Dolgu değişikliği .line-transparency = Çizgi saydamlığı .cylindrical-short = Sil. .spherical-short = Küre diff --git a/locales/zh-CN/opencadstudio.ftl b/locales/zh-CN/opencadstudio.ftl index bf728d3a4..28fccc873 100644 --- a/locales/zh-CN/opencadstudio.ftl +++ b/locales/zh-CN/opencadstudio.ftl @@ -5623,7 +5623,7 @@ graphic-attributes = .one-unsupported = 图形特性: 1 个不支持的对象未更改。 .unsupported-skipped = 图形特性: __ocs_fmt_0__ 个不支持的对象未更改。 .fill-transparency = 填充透明度 - .line-linetype = 线的线型 + .fill-change = 更改填充 .line-transparency = 线的透明度 .cylindrical-short = 圆柱 .spherical-short = 球形 diff --git a/locales/zh-TW/opencadstudio.ftl b/locales/zh-TW/opencadstudio.ftl index e8953c1a2..0ed8cf646 100644 --- a/locales/zh-TW/opencadstudio.ftl +++ b/locales/zh-TW/opencadstudio.ftl @@ -5635,7 +5635,7 @@ graphic-attributes = .one-unsupported = 圖形特性: 1 個不支援的物件未變更。 .unsupported-skipped = 圖形特性: __ocs_fmt_0__ 個不支援的物件未變更。 .fill-transparency = 填充透明度 - .line-linetype = 線的線型 + .fill-change = 變更填充 .line-transparency = 線的透明度 .cylindrical-short = 圓柱 .spherical-short = 球形 diff --git a/src/app/update/graphic_attributes.rs b/src/app/update/graphic_attributes.rs index f41e7f147..3e6a9b3e4 100644 --- a/src/app/update/graphic_attributes.rs +++ b/src/app/update/graphic_attributes.rs @@ -219,7 +219,11 @@ impl OpenCADStudio { use GraphicAttributesMsg as M; if !matches!( message, - M::ToggleMenu(_) | M::HatchPatternSearch(_) | M::HatchPatternFocus(_) | M::Gradient(_) + M::ToggleMenu(_) + | M::HatchPatternSearch(_) + | M::HatchPatternFocus(_) + | M::LineLinetypeScaleInput(_) + | M::Gradient(_) ) { self.graphic_attributes.open_menu = None; } @@ -253,6 +257,19 @@ impl OpenCADStudio { M::LineLinetype(linetype) => self.on_line_linetype(linetype), M::LineLineweight(lineweight) => self.on_line_lineweight(lineweight), M::LineLinetypeScale(scale) => self.on_line_linetype_scale(scale), + M::LineLinetypeScaleInput(value) => { + self.graphic_attributes.linetype_scale_input = Some(value); + Task::none() + } + M::LineLinetypeScaleSubmit => { + match self.graphic_attributes.linetype_scale_input.take() { + Some(typed) => { + let value = crate::app::expr_eval::eval_number(&typed.replace(',', ".")); + value.map_or_else(Task::none, |scale| self.on_line_linetype_scale(scale)) + } + None => Task::none(), + } + } M::LineTransparency(transparency) => self.on_line_transparency(transparency), M::FillColor(color) => self.on_solid_fill_color(color), M::FillTransparency(transparency) => self.on_fill_transparency(transparency), @@ -731,7 +748,7 @@ impl OpenCADStudio { ); } let selected = selected.as_slice(); - let pending = self.begin_undo(i, crate::t!("Graphic Attributes"), selected.len(), true); + let pending = self.begin_undo(i, crate::t!("Fill change"), selected.len(), true); let mut changed = false; for handle in to_close { let Some(mut entity) = self.tabs[i].scene.document.get_entity(*handle).cloned() else { @@ -920,7 +937,7 @@ impl OpenCADStudio { if !self.has_selection() { return self.on_ribbon_linetype_changed(linetype); } - self.edit_graphic_part(Part::Line, &crate::t!("Line linetype"), |entity| { + self.edit_graphic_part(Part::Line, &crate::t!("Linetype"), |entity| { crate::scene::view::dispatch::apply_common_prop(entity, "linetype", &linetype); }); Task::none() @@ -1604,4 +1621,69 @@ mod tests { Some((DockSide::Right, 0)) ); } + + fn output_since(app: &OpenCADStudio, start: usize) -> String { + app.command_line.history[start..] + .iter() + .map(|entry| entry.text.as_str()) + .collect::>() + .join("\n") + } + + #[test] + fn undo_names_fill_and_linetype_changes() { + let mut app = fresh(); + let boundary = add_closed_square(&mut app, 0.0); + select(&mut app, &[boundary]); + + let _ = app.update(ga(GraphicAttributesMsg::Fill(GraphicAttribute::Solid))); + let start = app.command_line.history.len(); + let _ = app.update(Message::Undo); + assert!(output_since(&app, start).contains("Undo: Fill change")); + + let _ = app.update(ga(GraphicAttributesMsg::LineLinetype("Continuous".into()))); + let start = app.command_line.history.len(); + let _ = app.update(Message::Undo); + assert!(output_since(&app, start).contains("Undo: Linetype")); + } + + #[test] + fn typed_linetype_scale_is_applied_on_enter() { + let mut app = fresh(); + let i = app.active_tab; + let line = app.tabs[i].scene.add_entity(EntityType::Line(Line::new())); + select(&mut app, &[line]); + + // Enter without typing changes nothing. + let _ = app.update(ga(GraphicAttributesMsg::LineLinetypeScaleSubmit)); + assert_eq!(common(&app, line).linetype_scale, 1.0); + + // Typing alone does not apply; Enter does, with a decimal comma too. + let _ = app.update(ga(GraphicAttributesMsg::LineLinetypeScaleInput("0,35".into()))); + assert_eq!(common(&app, line).linetype_scale, 1.0); + let _ = app.update(ga(GraphicAttributesMsg::LineLinetypeScaleSubmit)); + assert_eq!(common(&app, line).linetype_scale, 0.35); + assert!(app.graphic_attributes.linetype_scale_input.is_none()); + + // Invalid or non-positive input is dropped. + for typed in ["abc", "0", "-2"] { + let _ = app.update(ga(GraphicAttributesMsg::LineLinetypeScaleInput(typed.into()))); + let _ = app.update(ga(GraphicAttributesMsg::LineLinetypeScaleSubmit)); + assert_eq!(common(&app, line).linetype_scale, 0.35); + } + } + + #[test] + fn clicking_the_linetype_scale_field_selects_it_once() { + let mut app = fresh(); + let field = iced::widget::Id::new(palette::LINETYPE_SCALE_FIELD); + let _ = app.update(Message::PropSyncActive(Some(field.clone()))); + assert!(app.graphic_attributes.linetype_scale_focused); + + // Focus elsewhere drops unsubmitted text. + app.graphic_attributes.linetype_scale_input = Some("3".into()); + let _ = app.update(Message::PropSyncActive(None)); + assert!(!app.graphic_attributes.linetype_scale_focused); + assert!(app.graphic_attributes.linetype_scale_input.is_none()); + } } diff --git a/src/app/update/mod.rs b/src/app/update/mod.rs index 36097bbff..8ab8f8603 100644 --- a/src/app/update/mod.rs +++ b/src/app/update/mod.rs @@ -6887,13 +6887,28 @@ impl OpenCADStudio { Message::PropAttrCommit(tag) => self.on_prop_attr_commit(tag), Message::PropPointerPressed => { - if !self.dock_panel_visible(crate::ui::dock::PanelId::Properties) { + if !self.dock_panel_visible(crate::ui::dock::PanelId::Properties) + && !self.dock_panel_visible(crate::ui::dock::PanelId::GraphicAttributes) + { return Task::none(); } crate::ui::properties::sync_active_field_task() } Message::PropSyncActive(focused) => { + // The Graphic Attributes linetype scale field behaves like a + // Properties field: the first click selects its value. + let scale_field = iced::widget::Id::new( + crate::ui::window::graphic_attributes::LINETYPE_SCALE_FIELD, + ); + if focused.as_ref() == Some(&scale_field) { + if !std::mem::replace(&mut self.graphic_attributes.linetype_scale_focused, true) { + return iced::widget::operation::select_all(scale_field); + } + return Task::none(); + } + self.graphic_attributes.linetype_scale_focused = false; + self.graphic_attributes.linetype_scale_input = None; let panel = &mut self.tabs[self.active_tab].properties; if let Some(id) = focused.as_ref() { if let Some(key) = panel.prop_field_key_for_id(id) { diff --git a/src/locale_catalog.rs b/src/locale_catalog.rs index f9e70e4b4..f634e9e4d 100644 --- a/src/locale_catalog.rs +++ b/src/locale_catalog.rs @@ -5274,7 +5274,7 @@ pub(super) fn message_attribute(source: &str) -> Option<(&'static str, &'static "Graphic Attributes: 1 unsupported object was not changed." => Some(("graphic-attributes", "one-unsupported")), "Graphic Attributes: {skipped} unsupported objects were not changed." => Some(("graphic-attributes", "unsupported-skipped")), "Fill transparency" => Some(("graphic-attributes", "fill-transparency")), - "Line linetype" => Some(("graphic-attributes", "line-linetype")), + "Fill change" => Some(("graphic-attributes", "fill-change")), "Line transparency" => Some(("graphic-attributes", "line-transparency")), "Cyl." => Some(("graphic-attributes", "cylindrical-short")), "Spher." => Some(("graphic-attributes", "spherical-short")), diff --git a/src/ui/window/graphic_attributes.rs b/src/ui/window/graphic_attributes.rs index 9a435c725..1531dd072 100644 --- a/src/ui/window/graphic_attributes.rs +++ b/src/ui/window/graphic_attributes.rs @@ -21,15 +21,14 @@ type DrawDepth = FxHashMap; const PALETTE_CONTROL_ICON_SIZE: f32 = 20.0; const COMPACT_BUTTON_HEIGHT: f32 = 22.0; -const LINETYPE_SCALE_STEP: f64 = 0.1; const TRANSPARENCY_ICON: &[u8] = include_bytes!("../../../assets/icons/attributes/transparency.svg"); const LINEWEIGHT_ICON: &[u8] = include_bytes!("../../../assets/icons/attributes/lineweight.svg"); -/// A usable object linetype scale (CELTSCALE): any positive finite value. -/// Spinner steps are rounded to six decimals so repeated `±0.1` steps do not -/// accumulate floating-point noise; typed values such as the ISO pen widths -/// 0.13, 0.18, 0.25 and 0.35 are kept as entered. +/// A usable object linetype scale (CELTSCALE): any positive finite value, +/// rounded to six decimals so arithmetic noise does not reach the drawing. +/// Typed values such as the ISO pen widths 0.13, 0.18, 0.25 and 0.35 are +/// kept as entered. pub(crate) fn normalized_linetype_scale(value: f64) -> Option { let value = (value * 1.0e6).round() / 1.0e6; (value.is_finite() && value > 0.0).then_some(value) @@ -109,6 +108,9 @@ pub enum GraphicAttributesMsg { LineLinetype(String), LineLineweight(LineWeight), LineLinetypeScale(f64), + /// Text typed in the linetype scale field, applied on Enter. + LineLinetypeScaleInput(String), + LineLinetypeScaleSubmit, LineTransparency(Transparency), FillColor(AcadColor), FillTransparency(Transparency), @@ -150,6 +152,10 @@ pub struct GraphicAttributesState { pub pending_fill_close: Option, /// New fills go to the current layer instead of their object's layer. pub fill_on_current_layer: bool, + /// Text typed in the linetype scale field, until Enter applies it. + pub linetype_scale_input: Option, + /// Whether the linetype scale field has focus (its value was selected). + pub linetype_scale_focused: bool, /// View-side caches; `view` runs after every message, so neither the /// fill lookup nor the gradient preview is rebuilt unless its input changed. fill_index: RefCell>, @@ -694,35 +700,16 @@ fn color_control<'a>( target: ColorPickTarget, changed: fn(AcadColor) -> GraphicAttributesMsg, ) -> Element<'a, Message> { - let label = if info.varies { - Some("*VARIES*") - } else if info.color == AcadColor::ByBlock { - Some("ByBlock") - } else { - None - }; - let label: Element<'a, Message> = match label { - Some(label) => container(text(label).size(10)) - .width(Fill) - .align_x(iced::Left) - .align_y(iced::Center) - .padding([0, 6]) - .into(), - None => container(Space::new()).width(Fill).height(Fill).into(), - }; + // The L / B / C / V button already shows ByLayer, ByBlock and *VARIES*, + // so the swatch carries no text. let display = info.display; - let swatch = button(label) + let swatch = button(Space::new().width(Fill).height(Fill)) .on_press(Message::OpenColorWindow(target.clone(), info.color)) .width(Fill) .height(COMPACT_BUTTON_HEIGHT) .padding(0) .style(move |theme: &Theme, status| button::Style { background: Some(Background::Color(display)), - text_color: if display.r * 0.299 + display.g * 0.587 + display.b * 0.114 > 0.55 { - iced::Color::BLACK - } else { - iced::Color::WHITE - }, border: Border { color: if matches!(status, button::Status::Hovered) { theme.palette().primary.base.color @@ -818,6 +805,7 @@ fn header_menu(side: DockSide, open: bool, fill_on_current_layer: bool) -> Eleme item("Set all attributes ByBlock", GraphicAttributesMsg::SetAllByBlock), item("Remove ByLayer / ByBlock", GraphicAttributesMsg::RemoveReferences), item("Create Layer with active settings", GraphicAttributesMsg::CreateLayer), + container(iced::widget::rule::horizontal(1)).padding([2, 4]), container( iced::widget::checkbox(fill_on_current_layer) .label(crate::t!("Create fills on the current layer")) @@ -1067,28 +1055,26 @@ fn linetype_row<'a>( .into() } -fn linetype_scale_field<'a>(scale: f64, varies: bool, continuous: bool) -> Element<'a, Message> { - let field: Element<'a, Message> = if varies { - text_input("*VARIES*", "") - .size(crate::ui::ROW_H * 0.42) - .padding([5, 5]) - .style(crate::ui::properties::combo_input_style) - .width(Fill) - .into() - } else { - let value = normalized_linetype_scale(scale).unwrap_or(1.0); - let mut input = iced_aw::number_input( - &value, - (std::ops::Bound::Excluded(0.0), std::ops::Bound::Unbounded), - |value: f64| match normalized_linetype_scale(value) { - Some(value) => msg(GraphicAttributesMsg::LineLinetypeScale(value)), - None => Message::Noop, - }, - ) - .step(LINETYPE_SCALE_STEP) - .set_size(crate::ui::ROW_H * 0.42) +/// Widget id of the linetype scale field, for select-all on focus. +pub(crate) const LINETYPE_SCALE_FIELD: &str = "graphic-linetype-scale"; + +/// Like a Properties field: clicking selects the value, Enter applies it. +fn linetype_scale_field<'a>( + scale: f64, + varies: bool, + continuous: bool, + typed: Option<&str>, +) -> Element<'a, Message> { + let shown = match typed { + Some(typed) => typed.to_string(), + None if varies => String::new(), + None => normalized_linetype_scale(scale).unwrap_or(1.0).to_string(), + }; + let mut field = text_input(if varies { "*VARIES*" } else { "" }, &shown) + .id(iced::widget::Id::new(LINETYPE_SCALE_FIELD)) + .size(crate::ui::ROW_H * 0.42) .padding([5, 5]) - .input_style(move |theme: &Theme, status| { + .style(move |theme: &Theme, status| { let mut style = crate::ui::properties::combo_input_style(theme, status); if continuous { let disabled = theme.palette().background.base.text.scale_alpha(0.42); @@ -1097,25 +1083,12 @@ fn linetype_scale_field<'a>(scale: f64, varies: bool, continuous: bool) -> Eleme } style }) - .style(move |theme: &Theme, status| { - let text = theme.palette().background.base.text; - iced_aw::number_input::number_input::Style { - button_background: None, - icon_color: if continuous || status == iced_aw::style::Status::Disabled { - text.scale_alpha(0.42) - } else { - text - }, - } - }) .width(Fill); - if continuous { - input = input.on_input_maybe(None:: Message>); - } else { - input = input.on_submit(Message::Noop); - } - input.into() - }; + if !continuous { + field = field + .on_input(|value| msg(GraphicAttributesMsg::LineLinetypeScaleInput(value))) + .on_submit(msg(GraphicAttributesMsg::LineLinetypeScaleSubmit)); + } let icon = GraphicAttribute::Solid.icon(); let icon = if continuous { crate::ui::icons::themed_disabled(icon, PALETTE_CONTROL_ICON_SIZE) @@ -1193,8 +1166,10 @@ fn line_section<'a>( no_selection: bool, draw_depth: &DrawDepth, side: DockSide, - open_menu: Option, + state: &GraphicAttributesState, ) -> Vec> { + let open_menu = state.open_menu; + let typed_scale = state.linetype_scale_input.as_deref(); let mut rows = Vec::new(); if handles.is_empty() && !no_selection { return rows; @@ -1246,7 +1221,7 @@ fn line_section<'a>( let effective = effective_lineweight(document, handles, draw_depth, lineweight); rows.push( row![ - linetype_scale_field(scale, scale_varies, continuous), + linetype_scale_field(scale, scale_varies, continuous, typed_scale), lineweight_field( properties, lineweight, @@ -1296,7 +1271,7 @@ pub fn view<'a>( selected.is_empty(), draw_depth, side, - open_menu, + state, )); body = body