From 83f5b7eead6856bb2fb768228ba34ddd27fbcfd4 Mon Sep 17 00:00:00 2001 From: dotCooCoo Date: Wed, 4 Nov 2026 07:05:55 -0800 Subject: [PATCH] OpenType: a menu choice at a Type tool caret reaches every character At a caret the panel sends text.setRangeStyle with no range, which styles all of the text, but the check that skips a choice that changes nothing read only the insertion style. When the characters at the caret already had the chosen feature and others did not, the choice did nothing. The check now covers every character of the text at a caret, so the choice applies, and a choice that changes no character still adds no undo step. --- crates/ui-egui/src/panels/opentype.rs | 37 ++++++++++++++++++++++++--- 1 file changed, 34 insertions(+), 3 deletions(-) diff --git a/crates/ui-egui/src/panels/opentype.rs b/crates/ui-egui/src/panels/opentype.rs index b4aea639a..1f98c74cd 100644 --- a/crates/ui-egui/src/panels/opentype.rs +++ b/crates/ui-egui/src/panels/opentype.rs @@ -4,6 +4,7 @@ use egui::Ui; use serde_json::json; +use vectorcraft_doc::{CharStyle, NodeKind}; use vectorcraft_text::{EXTRA_FEATURES, FontDb, OtFeatures, StylisticSet}; use super::character::{shared, text_editing, text_style}; @@ -135,14 +136,22 @@ impl Change { } /// Make `change` to the features of each character the panel acts on, from the character's own -/// features (`featureChanges`), unless it changes none of them. +/// features (`featureChanges`), unless it changes none of them. At a Type tool caret the +/// command styles all the text, so every character of the text is checked. fn edit(app: &mut VectorcraftApp, change: Change) { - let unchanged = shared(app, |s| { + let same = |s: &CharStyle| { let before = OtFeatures::from_tags(s.features.iter().map(String::as_str)); let mut after = before; change.apply(&mut after); after == before - }); + }; + let unchanged = match text_editing(app) { + Some((id, a, b)) if b <= a => match app.session.active().and_then(|st| st.doc.node(id)).map(|n| &n.kind) { + Some(NodeKind::Text(t)) => Some(t.runs.iter().all(|r| same(&r.style))), + _ => None, + }, + _ => shared(app, same), + }; if unchanged != Some(true) { run_features(app, "featureChanges", OtFeatures::changes(|f| change.apply(f))); } @@ -525,4 +534,26 @@ mod tests { ] ); } + + /// At a caret whose insertion style is already a denominator, Denominator still reaches the + /// characters the command styles at a caret (all of them) in one undo step; chosen again once + /// every character has it, it adds no undo step. + #[test] + fn caret_with_the_same_position_still_applies_it() { + let mut app = VectorcraftApp::new(vectorcraft_engine::Session::new(), Default::default()); + app.session.execute("file.new", &json!({"width": 100, "height": 100})).unwrap(); + app.session.select_tool("type", Default::default()).unwrap(); + let id = app.session.execute("text.create", &json!({"x": 10, "y": 40, "text": "1/23"})).unwrap()["id"].clone(); + app.session.execute("text.setRangeStyle", &json!({"id": id, "start": 0, "end": 1, "features": ["dnom"]})).unwrap(); + app.session.apply_actions(vec![vectorcraft_tools::Action::Notify("text.editNew".into())]).unwrap(); + app.session.set_tool_option("select", &json!({"start": 1, "end": 1})); + assert_eq!(text_editing(&app).map(|(_, a, b)| (a, b)), Some((1, 1))); + assert_eq!(shown_position(&app), Some(4)); + let undo = undo_steps(&app); + edit(&mut app, Change::Position(4)); + assert_eq!(undo_steps(&app), undo + 1); + assert_eq!(run_features(&mut app, &id, 4), [("1/23".to_string(), json!(["dnom"]))]); + edit(&mut app, Change::Position(4)); + assert_eq!(undo_steps(&app), undo + 1); + } }