Merge pull request #3823 from MiQ71/fix-3786-text-spacing-shortcuts

Fix text spacing shortcuts for selected type
This commit is contained in:
BFlat
2026-10-24 02:28:11 +07:00
committed by GitHub
2 changed files with 62 additions and 1 deletions
+1 -1
View File
@@ -427,7 +427,7 @@ pub fn overflow_markers(marks: &mut OverflowMarks, cx: &ToolContext) -> Vec<Over
/// `None` when `r` runs past the end of `s` or backwards.
/// The `type.step` an Alt+arrow asks for, by one step: ←/→ kerning at a `caret`, else tracking;
/// ↑/↓ leading (down opens it up); Shift+↑/↓ baseline shift.
fn step_key(key: ToolKey, shift: bool, caret: bool) -> Option<(&'static str, f64)> {
pub fn step_key(key: ToolKey, shift: bool, caret: bool) -> Option<(&'static str, f64)> {
let sign = |up: bool| if up { 1.0 } else { -1.0 };
Some(match (key, shift) {
(ToolKey::Left | ToolKey::Right, false) => (if caret { "kerning" } else { "tracking" }, sign(key == ToolKey::Right)),
+61
View File
@@ -628,6 +628,27 @@ pub fn handle(app: &mut VectorcraftApp, ctx: &egui::Context) {
}
return;
}
// Alt+arrows on selected type objects step their spacing as the Type tool does (Cmd/Ctrl:
// five steps); Alt+arrow copies and nudges every other selection.
let m = ctx.input(|i| i.modifiers);
if m.alt
&& app.session.active().is_some_and(|st| {
st.selection.guides.is_empty()
&& !st.selection.objects.is_empty()
&& st.selection.objects.iter().all(|id| matches!(st.doc.node(*id).map(|n| &n.kind), Some(vectorcraft_doc::NodeKind::Text(_))))
})
{
let keys = [(Key::ArrowLeft, ToolKey::Left), (Key::ArrowRight, ToolKey::Right), (Key::ArrowUp, ToolKey::Up), (Key::ArrowDown, ToolKey::Down)];
for (key, tool_key) in keys {
if ctx.input_mut(|i| i.consume_key(m, key)) {
if let Some((attribute, by)) = vectorcraft_tools::text::step_key(tool_key, m.shift, false) {
let by = if m.command { by * 5.0 } else { by };
let _ = app.run("type.step", json!({"attribute": attribute, "by": by}));
}
return;
}
}
}
// Arrow nudges (Shift = ×10, Alt = copy).
let arrows = [(Key::ArrowLeft, -1.0, 0.0), (Key::ArrowRight, 1.0, 0.0), (Key::ArrowUp, 0.0, -1.0), (Key::ArrowDown, 0.0, 1.0)];
for (k, dx, dy) in arrows {
@@ -1624,6 +1645,46 @@ mod tests {
assert_eq!(sizes(&mut app), [(json!("One "), json!(14.0)), (json!("Two"), json!(24.0))]);
}
/// #3786: Alt+arrows step a selected text object's spacing instead of copying and nudging it.
#[test]
fn alt_arrows_step_selected_type_object() {
let mut app = VectorcraftApp::new(vectorcraft_engine::Session::new(), Default::default());
app.session.execute("file.new", &json!({"width": 200, "height": 200})).unwrap();
let id = app.session.execute("text.create", &json!({"x": 20, "y": 50, "text": "One Two", "size": 20})).unwrap()["id"].as_u64().unwrap();
app.select_tool("selection");
app.session.execute("select.set", &json!({"ids": [id]})).unwrap();
let press = |key, modifiers| egui::Event::Key { key, physical_key: None, pressed: true, repeat: false, modifiers };
frame(&mut app, vec![egui::Event::ModifiersChanged(Modifiers::ALT), press(Key::ArrowRight, Modifiers::ALT)]);
let r = app.session.execute("text.getRange", &json!({"id": id})).unwrap();
assert_eq!(r["runs"][0]["style"]["tracking"], json!(20.0));
assert_eq!(app.session.active().unwrap().selection.objects, [vectorcraft_doc::NodeId(id)]);
frame(&mut app, vec![egui::Event::ModifiersChanged(Modifiers::ALT), press(Key::ArrowDown, Modifiers::ALT)]);
let r = app.session.execute("text.getRange", &json!({"id": id})).unwrap();
assert_eq!(r["runs"][0]["style"]["leading"], json!(26.0));
let alt_shift = Modifiers::ALT | Modifiers::SHIFT;
frame(&mut app, vec![egui::Event::ModifiersChanged(alt_shift), press(Key::ArrowUp, alt_shift)]);
let r = app.session.execute("text.getRange", &json!({"id": id})).unwrap();
assert_eq!(r["runs"][0]["style"]["baseline_shift"], json!(2.0));
// Shift+Alt+Left/Right have no text-spacing action; they must still be consumed instead of
// falling through to Alt+arrow's copy-and-nudge behaviour.
for key in [Key::ArrowLeft, Key::ArrowRight] {
frame(&mut app, vec![egui::Event::ModifiersChanged(alt_shift), press(key, alt_shift)]);
assert_eq!(app.session.active().unwrap().selection.objects, [vectorcraft_doc::NodeId(id)]);
}
// Other objects keep Alt+arrow's copy-and-nudge shortcut.
let rect = app.session.execute("shape.rectangle", &json!({"x": 0, "y": 0, "width": 10, "height": 10})).unwrap()["id"].as_u64().unwrap();
app.session.execute("select.set", &json!({"ids": [rect]})).unwrap();
frame(&mut app, vec![egui::Event::ModifiersChanged(Modifiers::ALT), press(Key::ArrowRight, Modifiers::ALT)]);
let selected = &app.session.active().unwrap().selection.objects;
assert_eq!(selected.len(), 1);
assert_ne!(selected[0], vectorcraft_doc::NodeId(rect), "a non-type object is still copied by Alt+arrow");
}
/// #793: with a layout without Latin letters the one-key shortcuts go by the key's position:
/// the P key typing Persian `ح` chooses the Pen, the V key typing Russian `м` the Selection
/// tool. A Latin layout keeps the letter typed: AZERTY's A (the Q key's place) is A.