Type: Cmd/Ctrl+Alt+Q resets tracking to 0 and kerning to Auto

The reference app resets tracking and kerning to 0 with Cmd/Ctrl+Alt+Q.
VectorCraft had no command for it.

type.resetTracking (Reset Tracking and Kerning, default Cmd+Alt+Q) sets
tracking to 0 and kerning to Auto, which the Character panel shows as 0,
in the text the Type tool has selected, or else in all the text of
every selected type object. At a caret it resets only the kerning of
the character before it, where Alt+Left/Right kerns. It is one undo
step. The range logic of type.step moves into a shared type_ranges.

When a call gives ids, type.step and type.resetTracking change all the
text of those type objects even while the Type tool edits text, as
paint.setFill does. Before, type.step changed the Type tool's selected
text instead.

Docs: docs/mcp.md, docs/target-app-parity.md.
This commit is contained in:
dotCooCoo
2026-10-16 02:56:35 +07:00
committed by bflatastic
parent d726f6596c
commit cb9522f7cd
21 changed files with 206 additions and 30 deletions
+81 -26
View File
@@ -63,7 +63,7 @@ pub fn specs() -> Vec<CommandSpec> {
"Step Type",
[],
None,
"{attribute: \"size\"|\"leading\"|\"tracking\"|\"kerning\"|\"baselineShift\", by?: steps (1; negative steps down), id?, start?: byte, end?: byte, ids?: [id…] (instead of id)} step type by the Preferences › Type increments (size and leading: typeSizeIncrement, pt; tracking and kerning: trackingIncrement, 1/1000 em; baseline shift: baselineShiftIncrement, pt), what the Type tool's Alt+arrows do (Cmd/Ctrl too: five steps; ←/→ kerning at the caret or tracking of the selection, ↑/↓ leading, Shift+↑/↓ baseline shift). The range of `id`, else the text the Type tool has selected (at a caret: kerning steps the character before it, leading its paragraph), else every selected type object → {ids}",
"{attribute: \"size\"|\"leading\"|\"tracking\"|\"kerning\"|\"baselineShift\", by?: steps (1; negative steps down), id?, start?: byte, end?: byte, ids?: [id…] (instead of id)} step type by the Preferences › Type increments (size and leading: typeSizeIncrement, pt; tracking and kerning: trackingIncrement, 1/1000 em; baseline shift: baselineShiftIncrement, pt), what the Type tool's Alt+arrows do (Cmd/Ctrl too: five steps; ←/→ kerning at the caret or tracking of the selection, ↑/↓ leading, Shift+↑/↓ baseline shift). The range of `id`, else the type objects in `ids`, else the text the Type tool has selected (at a caret: kerning steps the character before it, leading its paragraph), else every selected type object → {ids}",
has_doc,
type_step
),
@@ -85,6 +85,15 @@ pub fn specs() -> Vec<CommandSpec> {
has_selection,
|s, p| type_step(s, &json!({"attribute": "size", "by": -f64_or(p, "by", 1.0)}))
),
cmd!(
"type.resetTracking",
"Reset Tracking and Kerning",
[],
Some("Cmd+Alt+Q"),
"{ids?: [id…]} set tracking to 0 and kerning to Auto (the font's own kerning, which the Character panel shows as 0) in all the text of the type objects in `ids`, else in the text the Type tool has selected (at a caret: only the kerning of the character before it), else in every selected type object, as one undo step → {ids}",
has_selection,
reset_tracking
),
cmd!(
"text.discardEmpty",
"Discard Empty Type",
@@ -598,6 +607,46 @@ pub(crate) fn editing_range(s: &mut Session) -> Result<Option<(NodeId, usize, us
Ok(Some((id, at("start"), at("end"))))
}
/// The text ranges a type shortcut changes: the range of `id`, else all the text of the type
/// objects in `ids`, else the text the Type tool has selected, else all the text of every selected
/// type object. At a caret, `at_caret` gives the range from the plain text and the caret's byte
/// (None: nothing changes there). Each range comes with whether it came from a caret.
fn type_ranges(
s: &mut Session,
p: &Value,
c: &str,
at_caret: impl Fn(&str, usize) -> Option<(usize, usize)>,
) -> Result<Vec<(NodeId, usize, usize, bool)>> {
// (text, its range; None: all of it).
let targets: Vec<(NodeId, Option<(usize, usize)>)> = match id_param(p, "id") {
Some(id) => vec![(id, Some(range_of(p, edit::runs_len(&text_ref(s, id)?.runs))))],
None => {
let editing = editing_range(s)?.filter(|_| p.get("ids").filter(|v| !v.is_null()).is_none());
match editing {
Some((id, a, b)) => vec![(id, Some((a.min(b), a.max(b))))],
None => super::typecmd::text_targets(s, p, c)?.into_iter().map(|id| (id, None)).collect(),
}
}
};
let mut ranges = vec![];
for (id, range) in targets {
let text = text_ref(s, id)?.plain_text();
let (a, b) = range.map_or((0, text.len()), |(a, b)| (a.min(text.len()), b.min(text.len())));
let (a, b, caret) = if a < b {
(a, b, false)
} else {
match at_caret(&text, a) {
Some((a, b)) => (a, b, true),
None => continue,
}
};
if a < b {
ranges.push((id, a, b, caret));
}
}
Ok(ranges)
}
fn type_step(s: &mut Session, p: &Value) -> Result<Value> {
const C: &str = "type.step";
let step = match str_param(p, "attribute") {
@@ -619,36 +668,19 @@ fn type_step(s: &mut Session, p: &Value) -> Result<Value> {
Step::Tracking | Step::Kerning => prefs.tracking_increment,
Step::BaselineShift => prefs.baseline_shift_increment,
};
// (text, its range; None: all of it).
let targets: Vec<(NodeId, Option<(usize, usize)>)> = match id_param(p, "id") {
Some(id) => vec![(id, Some(range_of(p, edit::runs_len(&text_ref(s, id)?.runs))))],
None => match editing_range(s)? {
Some((id, a, b)) => vec![(id, Some((a.min(b), a.max(b))))],
None => super::typecmd::text_targets(s, p, C)?.into_iter().map(|id| (id, None)).collect(),
},
};
// The ranges stepped. At a caret: kerning is the space after the character before it, leading
// the paragraph's; the rest wait for a selection.
let mut ranges = vec![];
for (id, range) in targets {
let text = text_ref(s, id)?.plain_text();
let (a, b) = range.map_or((0, text.len()), |(a, b)| (a.min(text.len()), b.min(text.len())));
let (a, b) = match step {
_ if a < b => (a, b),
Step::Kerning => (edit::prev_char(&text, a), a),
Step::Leading => {
let para = edit::paragraph_at(&text, a);
(para.start, para.end)
}
_ => continue,
};
if a < b {
ranges.push((id, a, b));
let ranges = type_ranges(s, p, C, |text, a| match step {
Step::Kerning => Some((edit::prev_char(text, a), a)),
Step::Leading => {
let para = edit::paragraph_at(text, a);
Some((para.start, para.end))
}
}
_ => None,
})?;
if !ranges.is_empty() {
s.edit("Character", |d, _| {
for &(id, a, b) in &ranges {
for &(id, a, b, _) in &ranges {
let t = text_mut(d, id).ok_or(EngineError::NoNode(id))?;
let points = if matches!(step, Step::Size | Step::Leading | Step::BaselineShift) {
t.style_scale().ok_or_else(|| bad(C, "text transform is collapsed or unrepresentable"))?.points
@@ -679,6 +711,29 @@ fn type_step(s: &mut Session, p: &Value) -> Result<Value> {
Ok(json!({ "ids": ranges.iter().map(|(id, ..)| id.0).collect::<Vec<_>>() }))
}
/// Cmd/Ctrl+Alt+Q: tracking 0 and kerning Auto (0 in the Character panel) in the type objects in
/// `ids`, else in the Type tool's selected text, else in every selected type object. At a caret,
/// only the kerning of the character before it changes, as Alt+←/→ kerns there.
fn reset_tracking(s: &mut Session, p: &Value) -> Result<Value> {
let ranges = type_ranges(s, p, "type.resetTracking", |text, a| Some((edit::prev_char(text, a), a)))?;
if !ranges.is_empty() {
s.edit("Reset Tracking and Kerning", |d, _| {
for &(id, a, b, caret) in &ranges {
let t = text_mut(d, id).ok_or(EngineError::NoNode(id))?;
edit::style_range(&mut t.runs, a, b, |st| {
st.kerning = None;
if !caret {
st.tracking = 0.0;
}
});
refresh_bounds(t);
}
Ok(())
})?;
}
Ok(json!({ "ids": ranges.iter().map(|(id, ..)| id.0).collect::<Vec<_>>() }))
}
/// Tracking (1/1000 em) that makes the first paragraph of `t` exactly `target` wide, if it fits
/// on one line at all.
fn headline_tracking(t: &TextObject, target: f64) -> Option<f64> {
+48
View File
@@ -1019,6 +1019,54 @@ fn type_increments_step_the_type() {
// The font size shortcut steps the selected text while the tool edits.
s.execute("type.size.increase", &json!({})).unwrap();
assert_eq!((style_at(&s, id, 0).size, style_at(&s, id, 6).size), (19.0, 22.0));
// The type objects given in `ids` step in full while the tool edits.
s.execute("type.step", &json!({"attribute": "size", "ids": [id.0]})).unwrap();
assert_eq!((style_at(&s, id, 0).size, style_at(&s, id, 6).size), (22.0, 25.0));
}
/// Cmd/Ctrl+Alt+Q resets tracking to 0 and kerning to Auto (0 in the Character panel) in every
/// selected type object and in the Type tool's selected text. At a caret it resets only the
/// kerning of the character before it. It is one undo step. Type objects given in `ids` are reset
/// in full while the Type tool edits, and the typing in progress ends first.
#[test]
fn reset_tracking_sets_tracking_and_kerning_to_zero() {
use vectorcraft_tools::PointerKind::{Down, Up};
let mut s = new_doc();
let id = NodeId(s.execute("text.create", &json!({"x": 100, "y": 100, "text": "Hello world", "size": 20})).unwrap()["id"].as_u64().unwrap());
let spaced = |s: &mut Session| s.execute("text.setRangeStyle", &json!({"id": id.0, "tracking": 200, "kerning": -40})).unwrap();
let spacing = |s: &Session, byte| {
let st = style_at(s, id, byte);
(st.tracking, st.kerning)
};
spaced(&mut s);
let other = NodeId(s.execute("text.create", &json!({"x": 100, "y": 300, "text": "Other", "size": 20})).unwrap()["id"].as_u64().unwrap());
s.execute("text.setRangeStyle", &json!({"id": other.0, "tracking": 200})).unwrap();
s.execute("select.set", &json!({"ids": [id.0]})).unwrap();
assert_eq!(find_command("type.resetTracking").unwrap().shortcut, Some("Cmd+Alt+Q"));
assert_eq!(s.execute("type.resetTracking", &json!({})).unwrap()["ids"], json!([id.0]));
assert_eq!((spacing(&s, 0), spacing(&s, 10)), ((0.0, None), (0.0, None)));
s.execute("edit.undo", &json!({})).unwrap();
assert_eq!(spacing(&s, 0), (200.0, Some(-40.0)), "one undo step");
// With the Type tool, it resets only the selected text.
let v = ViewInfo::default();
s.select_tool("type", v).unwrap();
gesture(&mut s, v, &[(Down, 102.0, 95.0), (Up, 102.0, 95.0)]);
assert_eq!(s.tool_options()["editing"], json!(id.0), "editing the text");
s.set_tool_option("select", &json!({"start": 0, "end": 5}));
s.execute("type.resetTracking", &json!({})).unwrap();
assert_eq!((spacing(&s, 4), spacing(&s, 6)), ((0.0, None), (200.0, Some(-40.0))));
// At a caret it resets only the kerning of the character before it, and the tracking does not change.
s.set_tool_option("select", &json!({"start": 8, "end": 8}));
s.execute("type.resetTracking", &json!({})).unwrap();
assert_eq!((spacing(&s, 6), spacing(&s, 7), spacing(&s, 8)), ((200.0, Some(-40.0)), (200.0, None), (200.0, Some(-40.0))));
// While the Type tool edits, the type objects in `ids` are reset in full.
s.execute("type.resetTracking", &json!({"ids": [id.0]})).unwrap();
assert_eq!((spacing(&s, 6), spacing(&s, 10)), ((0.0, None), (0.0, None)));
// Typing in progress ends first, so the reset of another object stays when typing goes on.
s.tool_text("!", v).unwrap();
s.execute("type.resetTracking", &json!({"ids": [other.0]})).unwrap();
s.tool_text("?", v).unwrap();
assert_eq!(style_at(&s, other, 0).tracking, 0.0, "the reset stays");
}
/// Type › Fill New Type Objects With Placeholder Text (#394, on by default): type the Type tool
+20
View File
@@ -1038,6 +1038,26 @@ fn type_preferences_apply_to_agents() {
assert_eq!(style(&mut s, 6), (size + 4.0, tracking + 50.0));
}
/// Cmd+Alt+Q reaches agents: the placeholder text the Type tool selects goes back to tracking 0
/// and Auto kerning.
#[test]
fn cmd_alt_q_resets_tracking_and_kerning_for_agents() {
let mut s = server();
let events = json!([{"kind": "down", "x": 100, "y": 100}, {"kind": "up", "x": 100, "y": 100}]);
let r = call(&mut s, 1, "pointer_gesture", json!({"tool": "type", "events": events}));
assert_eq!(r["isError"], false, "{r}");
let id = serde_json::from_str::<Value>(&text_of(&r)).unwrap()["selection"][0].as_u64().unwrap();
let r = call(&mut s, 2, "run_command", json!({"command": "text.setRangeStyle", "params": {"id": id, "tracking": 200, "kerning": -40}}));
assert_eq!(r["isError"], false, "{r}");
let r = call(&mut s, 3, "press_key", json!({"key": "Q", "mods": {"cmd": true, "alt": true}}));
assert_eq!(r["isError"], false, "{r}");
assert_eq!(serde_json::from_str::<Value>(&text_of(&r)).unwrap()["command"], "type.resetTracking", "{r}");
let r = call(&mut s, 4, "run_command", json!({"command": "text.getRange", "params": {"id": id}}));
let runs = serde_json::from_str::<Value>(&text_of(&r)).unwrap()["runs"].clone();
assert_eq!(runs.as_array().unwrap().len(), 1, "{runs}");
assert_eq!((&runs[0]["style"]["tracking"], &runs[0]["style"]["kerning"]), (&json!(0.0), &Value::Null), "{runs}");
}
/// The drawing tools snap to Smart Guides over MCP as with the mouse (#506): a Rectangle drawn
/// from near another object's corner starts on it, and a Pen anchor placed beside that object's
/// centre lines up with it.
+1
View File
@@ -3456,6 +3456,7 @@ axis Both Obě
Area / Path Type Text v oblasti / na cestě
Decrease Font Size Zmenšit velikost písma
Increase Font Size Zvětšit velikost písma
Reset Tracking and Kerning Obnovit prostrkání a vyrovnání párů
Step Type Krokovat text
Get Text Range Získat rozsah textu
Punctuation Interpunkce
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -1049,6 +1049,7 @@
Fit Headline Überschrift einpassen
Decrease Font Size Schriftgrad verringern
Increase Font Size Schriftgrad erhöhen
Reset Tracking and Kerning Laufweite und Kerning zurücksetzen
Spacing Abstand
Step Type Text schrittweise ändern
Fit in View In Ansicht einpassen
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -1066,6 +1066,7 @@
Fit Headline Ajustar titular
Decrease Font Size Reducir tamaño de fuente
Increase Font Size Aumentar tamaño de fuente
Reset Tracking and Kerning Restablecer espaciado e interletraje
Spacing Espaciado
Step Type Ajustar texto por incrementos
Fit in View Encajar en la vista
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -1065,6 +1065,7 @@
Fit Headline Ajuster le titre
Decrease Font Size Réduire la taille de police
Increase Font Size Augmenter la taille de police
Reset Tracking and Kerning Réinitialiser l’approche et le crénage
Spacing Espacement
Step Type Texte par incréments
Fit in View Ajuster à la vue
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -2915,6 +2915,7 @@
Reset to Defaults Alapértékek visszaállítása
Reset to White, Black Visszaállítás fehér–fekete átmenetre
Reset Tracing Tracing visszaállítása
Reset Tracking and Kerning Betűköz és alávágás visszaállítása
Reset Typography Typography visszaállítása
Reset Web Web visszaállítása
Reset with Mesh Visszaállítás hálóval
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -1065,6 +1065,7 @@
Fit Headline Adatta titolo
Decrease Font Size Riduci dimensione font
Increase Font Size Aumenta dimensione font
Reset Tracking and Kerning Reimposta spaziatura caratteri e crenatura
Spacing Spaziatura
Step Type Dimensione testo a incrementi
Fit in View Adatta alla vista
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -329,6 +329,7 @@
Fit Headline ヘッドラインを合わせる
Decrease Font Size フォントサイズを小さく
Increase Font Size フォントサイズを大きく
Reset Tracking and Kerning トラッキングとカーニングをリセット
Step Type 文字を段階的に調整
Find Font… フォントの検索と置換…
Change Case 大文字と小文字の変更
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -325,6 +325,7 @@
Fit Headline 제목에 맞추기
Decrease Font Size 글꼴 크기 줄이기
Increase Font Size 글꼴 크기 키우기
Reset Tracking and Kerning 자간 및 커닝 재설정
Step Type 단계별 문자
Find Font… 글꼴 찾기…
Change Case 대소문자 변경
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -3288,6 +3288,7 @@ axis Both Obie
Area / Path Type Tekst akapitowy / na ścieżce
Decrease Font Size Zmniejsz rozmiar czcionki
Increase Font Size Zwiększ rozmiar czcionki
Reset Tracking and Kerning Resetuj światło i kerning
Step Type Krok tekstu
Get Text Range Pobierz zakres tekstu
Punctuation Interpunkcja
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -949,6 +949,7 @@
Fit Headline Ajustar Manchete
Decrease Font Size Diminuir Tamanho da Fonte
Increase Font Size Aumentar Tamanho da Fonte
Reset Tracking and Kerning Redefinir Rastreamento e Kerning
Step Type Ajustar Texto em Incrementos
Fit dashes to corners and ends Ajustar tracejados aos cantos e extremidades
Fit in Window Ajustar na Janela
Can't render this file because it contains an unexpected character in line 6 and column 47.
+1
View File
@@ -1063,6 +1063,7 @@
Fit Headline Подогнать заголовок
Decrease Font Size Уменьшить размер шрифта
Increase Font Size Увеличить размер шрифта
Reset Tracking and Kerning Сбросить трекинг и кернинг
Spacing Интервал
Step Type Подгонять текст по шагам
Fit in View Вписать в вид
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -1059,6 +1059,7 @@
Fit Headline Підігнати заголовок
Decrease Font Size Зменшити розмір шрифту
Increase Font Size Збільшити розмір шрифту
Reset Tracking and Kerning Скинути трекінг і кернінг
Spacing Інтервал
Step Type Підганяти текст кроками
Fit in View Вписати у вигляд
Can't render this file because it contains an unexpected character in line 715 and column 40.
+1
View File
@@ -2660,6 +2660,7 @@
Reset to Default Đặt lại về mặc định
Reset to default Đặt lại về mặc định
Reset to White, Black Đặt lại về Trắng, Đen
Reset Tracking and Kerning Đặt lại giãn chữ và kerning
Reset with Mesh Đặt lại với lưới
Reset with Mesh… Đặt lại với lưới…
Reset with Warp Đặt lại với biến dạng
1 # Vietnamese (vi) catalog. Same format as zh-hant.tsv: context <TAB> source <TAB> translation
2660
2661
2662
2663
2664
2665
2666
+1
View File
@@ -1018,6 +1018,7 @@
Fit Headline 适合标题
Decrease Font Size 减小字体大小
Increase Font Size 增大字体大小
Reset Tracking and Kerning 重置字距和字偶距
Spacing 间距
Step Type 按增量调整文字
Fit in View 适合视图
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -1041,6 +1041,7 @@
Fit Headline 符合標題
Decrease Font Size 縮小字體大小
Increase Font Size 放大字體大小
Reset Tracking and Kerning 重設字距與字距微調
Spacing 間距
Step Type 依增量調整文字
Fit in View 符合檢視
Can't render this file because it contains an unexpected character in line 6 and column 47.
+34
View File
@@ -662,6 +662,40 @@ mod tests {
assert!(!crate::menus::enabled(&app, "type.alignCenter"));
}
/// Cmd+Alt+Q resets the tracking and kerning of the selected type (tracking 0, kerning Auto),
/// and of the Type tool's selected text while it edits.
#[test]
fn cmd_alt_q_resets_tracking_and_kerning() {
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"})).unwrap()["id"].as_u64().unwrap();
let spaced =
|app: &mut VectorcraftApp| app.session.execute("text.setRangeStyle", &json!({"id": id, "tracking": 200, "kerning": -40})).unwrap();
let spacing = |app: &mut VectorcraftApp| {
let r = app.session.execute("text.getRange", &json!({"id": id})).unwrap();
r["runs"]
.as_array()
.unwrap()
.iter()
.map(|run| (run["text"].clone(), run["style"]["tracking"].clone(), run["style"]["kerning"].clone()))
.collect::<Vec<_>>()
};
let chord =
egui::Event::Key { key: Key::Q, physical_key: None, pressed: true, repeat: false, modifiers: Modifiers::COMMAND | Modifiers::ALT };
spaced(&mut app);
app.session.execute("select.set", &json!({"ids": [id]})).unwrap();
frame(&mut app, vec![chord.clone()]);
assert_eq!(spacing(&mut app), [(json!("One Two"), json!(0.0), json!(null))]);
// While the Type tool edits: only its selection.
spaced(&mut app);
app.select_tool("type");
app.session.apply_actions(vec![vectorcraft_tools::Action::Notify("text.editNew".into())]).unwrap();
app.session.set_tool_option("select", &json!({"start": 4, "end": 7}));
assert!(app.session.tool_wants_text());
frame(&mut app, vec![chord]);
assert_eq!(spacing(&mut app), [(json!("One "), json!(200.0), json!(-40.0)), (json!("Two"), json!(0.0), json!(null))]);
}
/// Tab shows and hides the panels, and in the Type tool it types a tab, without moving the
/// keyboard focus on to a field: the keys after it (a tool letter, the next letter typed) still
/// work.
+7 -4
View File
@@ -1917,10 +1917,13 @@ Pen's second anchor at (150, 431), in line with the square's centre.
- `placeholderText` (on by default): type the Type tools place (`pointer_gesture` with `type`, `areaType`…) starts with
placeholder text, selected, so `type_text` replaces it; `text.create` and `text.createInPath` take `placeholder: true`.
- `typeSizeIncrement`, `trackingIncrement`, `baselineShiftIncrement`: what `type.step {attribute: "size" | "leading" |
"tracking" | "kerning" | "baselineShift", by?}` steps by, on the text range given, the Type tool's selected text or the
selected type objects. `type.size.increase` / `type.size.decrease` (Cmd+Shift+. and Cmd+Shift+,) step the size; the
Type tool's Alt+arrows (`press_key {key: "Right", mods: {alt: true}}`) step kerning at a caret or tracking of the
selection (←/→), leading (↑/↓) and baseline shift (Shift+↑/↓), five steps with Cmd/Ctrl too.
"tracking" | "kerning" | "baselineShift", by?}` steps by, on the text range given, the type objects in `ids`, the
Type tool's selected text or the selected type objects. `type.size.increase` / `type.size.decrease` (Cmd+Shift+. and
Cmd+Shift+,) step the size; the Type tool's Alt+arrows (`press_key {key: "Right", mods: {alt: true}}`) step kerning at
a caret or tracking of the selection (←/→), leading (↑/↓) and baseline shift (Shift+↑/↓), five steps with Cmd/Ctrl
too. `type.resetTracking` (Cmd+Alt+Q, `press_key {key: "Q", mods: {cmd: true, alt: true}}`) sets tracking to 0 and
kerning to Auto in the type objects in `ids`, the Type tool's selected text or the selected type objects; at a caret
it resets only the kerning of the character before it.
- `missingGlyphProtection` (on by default): `text.setStyle` and `text.setRangeStyle` changing the font leave the
characters the new font has no glyph for in the font that had one.
- `typeSelectionByPathOnly` (off by default): on, the Selection tools (`pointer_gesture` with `selection`, and the
+1
View File
@@ -438,6 +438,7 @@ Moved from the ROADMAP's "Shipped so far" on 2026-10-10. Update it when a featur
- Tab shows and hides the panels (in the Type tool it types a tab) without moving the keyboard focus onto a field, so the next shortcut or letter still works; in dialogs and panel fields it moves between fields.
- Shift+Tab (`window.panels`) shows and hides the dock and the floating panels and leaves the toolbar and the Control bar as they are; hiding the panels with Tab or Shift+Tab also closes a panel popped out of the icon column.
- Cmd/Ctrl+A follows the keyboard: in a panel, dialog or search field it selects the field's text, while the Type tool edits it selects all of that text (also `select.all` over MCP), and only otherwise every object. On macOS the menu bar's Select All, Cut, Copy and Paste (keys or clicks) act on a focused field too, and elsewhere the in-window menus' do: the field that had the keyboard when the menu opened gets them, with its selection, and the keyboard back (#516). Not yet: Select All across threaded frames (one frame's text is selected).
- Cmd/Ctrl+Alt+Q (`type.resetTracking`) sets the tracking of the selected type, or of the text the Type tool has selected, to 0 and its kerning to Auto (0 in the Character panel), in one undo step; at a caret it resets the kerning of the character before it.
- Shortcut sets other apps save as text (`.kys`) open in Import Shortcut Set from File › Open, from Edit › Keyboard Shortcuts › Import… in the desktop app, or from a drop on the window. It matches each entry to a tool, command or panel by name and decodes the file's key and modifier codes where VectorCraft's default shortcuts agree on them; the user answers the rest, and Apply fills the Keyboard Shortcuts dialog's working set. The answers are kept with the preferences for the next import (`shortcutSet.read`, `importMatches.*`).
- On Windows and Linux the app bar is the window's title bar (its own minimize, maximize and close buttons, drag to move, edges to resize), with the dragon app icon as the brand mark; dialogs and menus size to their content. As on a native menu bar, once one menu is open, moving the pointer onto another title opens that one.
- **Tools:**