diff --git a/crates/ui-egui/src/chrome/control_bar.rs b/crates/ui-egui/src/chrome/control_bar.rs index 018a4e2d9..88cc6dde0 100644 --- a/crates/ui-egui/src/chrome/control_bar.rs +++ b/crates/ui-egui/src/chrome/control_bar.rs @@ -172,13 +172,95 @@ fn inline_label(t: &Tokens, lbl: &str) -> egui::RichText { /// language, the four fields, the W/H link and the item spacing ahead of each of them. With less, /// only the Transform link shows. fn inline_transform_width(ui: &Ui, t: &Tokens) -> f32 { - let measure = |text: egui::WidgetText| text.into_galley(ui, Some(egui::TextWrapMode::Extend), f32::INFINITY, egui::TextStyle::Body).size().x; - let labels: f32 = INLINE_FIELDS.iter().map(|(_, lbl)| measure(inline_label(t, lbl).into())).sum(); + let labels: f32 = INLINE_FIELDS.iter().map(|(_, lbl)| widgets::text_width(ui, inline_label(t, lbl))).sum(); let fields = INLINE_FIELD_WIDTH * INLINE_FIELDS.len() as f32; let items = 2 * INLINE_FIELDS.len() + 1; labels + fields + crate::panels::transform::CONSTRAIN_LINK_SIZE + ui.spacing().item_spacing.x * items as f32 } +/// The size of the Control bar's Align, Distribute and area type vertical alignment buttons. +const BUTTON: f32 = 24.0; + +/// The space ahead of the separator that ends a group of the Control bar's controls. +const GROUP_GAP: f32 = 4.0; + +/// The width of the separators of the Control bar's [`group_end`] and ahead of the Transform link. +const SEPARATOR: f32 = 6.0; + +/// The widths of the Document Setup and Preferences buttons, shown with nothing selected. +const DOCUMENT_SETUP_WIDTH: f32 = 112.0; +const PREFERENCES_WIDTH: f32 = 90.0; + +/// The room `n` of the bar's buttons take, with the spacing after each. +fn buttons_width(ui: &Ui, n: usize) -> f32 { + (BUTTON + ui.spacing().item_spacing.x) * n as f32 +} + +/// The separator that ends a group of the Control bar's controls, as [`group_end_width`] measures it. +fn group_end(ui: &mut Ui) { + ui.add_space(GROUP_GAP); + separator(ui); +} + +fn group_end_width(ui: &Ui) -> f32 { + GROUP_GAP + separator_width(ui) +} + +/// A separator [`SEPARATOR`] wide. +fn separator(ui: &mut Ui) { + ui.add(egui::Separator::default().spacing(SEPARATOR)); +} + +fn separator_width(ui: &Ui) -> f32 { + SEPARATOR + ui.spacing().item_spacing.x +} + +/// The room the controls at the end of the Control bar need: the Transform link, or Document Setup +/// and Preferences with nothing selected. The groups ahead of them (the Paragraph section, area +/// type's vertical alignment, the Align and Distribute buttons) show only while it is left, so +/// the bar trims them from its right end (#5372), after the inline X/Y/W/H fields. +fn end_width(ui: &Ui, nothing_selected: bool) -> f32 { + if nothing_selected { DOCUMENT_SETUP_WIDTH + ui.spacing().item_spacing.x + PREFERENCES_WIDTH } else { crate::panels::transform::link_width(ui) } +} + +/// Draws the Control bar's groups between its Character controls and the Transform link, and +/// returns whether the Transform link and X/Y/W/H follow (false: nothing is selected, and Document +/// Setup and Preferences, drawn here, end the bar). The groups are the Paragraph section and area +/// type's vertical alignment (#3759), then the Align buttons and, for two or more objects, the +/// Distribute buttons (a single object keeps the bar as it was); each shows only while the end of +/// the bar keeps its room. +fn groups_before_transform(app: &mut VectorcraftApp, ui: &mut Ui, objects: usize) -> bool { + let nothing = objects == 0; + if crate::panels::paragraph::control_bar(app, ui, BUTTON, |ui| group_end_width(ui) + end_width(ui, nothing)) { + group_end(ui); + } + if crate::panels::properties::vertical_align::shown(app).is_some() + && ui.available_width() >= buttons_width(ui, 4) + group_end_width(ui) + end_width(ui, nothing) + && crate::panels::properties::vertical_align::buttons(app, ui, BUTTON) + { + group_end(ui); + } + if nothing { + if widgets::flat_button(ui, tl!("Document Setup"), DOCUMENT_SETUP_WIDTH).clicked() { + app.run("file.documentSetup", json!({})).ok(); + } + if widgets::flat_button(ui, tl!("Preferences"), PREFERENCES_WIDTH).clicked() { + app.run("edit.preferences", json!({})).ok(); + } + return false; + } + let end = separator_width(ui) + end_width(ui, false); + if ui.available_width() >= buttons_width(ui, 7) + end { + crate::panels::align::align_buttons(app, ui, BUTTON); + if objects > 1 && ui.available_width() >= GROUP_GAP + buttons_width(ui, 6) + end { + ui.add_space(GROUP_GAP); + crate::panels::align::distribute_buttons(app, ui, BUTTON); + } + separator(ui); + } + true +} + /// The Control bar (Window → Control), context-sensitive like Illustrator's. pub fn control_bar(app: &mut VectorcraftApp, ui: &mut Ui) { let t = Tokens::get(ui.ctx()); @@ -283,28 +365,9 @@ pub fn control_bar(app: &mut VectorcraftApp, ui: &mut Ui) { } ui.separator(); crate::panels::character::control_bar(app, ui); - // Area type: its vertical alignment in the frame (#3759). - if crate::panels::properties::vertical_align::buttons(app, ui, 24.0) { - ui.add_space(4.0); - ui.separator(); - } - if sel.is_empty() { - if widgets::flat_button(ui, tl!("Document Setup"), 112.0).clicked() { - app.run("file.documentSetup", json!({})).ok(); - } - if widgets::flat_button(ui, tl!("Preferences"), 90.0).clicked() { - app.run("edit.preferences", json!({})).ok(); - } + if !groups_before_transform(app, ui, sel.len()) { return; } - crate::panels::align::align_buttons(app, ui, 24.0); - // Two or more objects get the six Distribute Objects buttons too; a single - // object keeps the bar as it was. - if sel.len() > 1 { - ui.add_space(4.0); - crate::panels::align::distribute_buttons(app, ui, 24.0); - } - ui.separator(); // The Transform link opens the whole Transform panel (reference point, rotate, // shear, options) in a popover; X/Y/W/H follow inline while the bar has room. crate::panels::transform::link(app, ui); diff --git a/crates/ui-egui/src/chrome/tests.rs b/crates/ui-egui/src/chrome/tests.rs index 710f3ba0c..86a85699d 100644 --- a/crates/ui-egui/src/chrome/tests.rs +++ b/crates/ui-egui/src/chrome/tests.rs @@ -1939,7 +1939,8 @@ fn the_control_bar_shows_the_global_edit_options() { /// At any width the Control bar shows the inline X, Y, W and H fields whole inside its right margin, /// or leaves all four out and keeps the Transform link; a wider bar never leaves them out once a -/// narrower one showed them. +/// narrower one showed them. The Align buttons, and for two objects the Distribute buttons, are +/// trimmed the same way ahead of the Transform link (#5372). #[test] fn the_control_bar_shows_the_inline_transform_fields_only_when_they_fit() { crate::i18n::set_current(crate::i18n::Lang::EN); @@ -1954,6 +1955,33 @@ fn the_control_bar_shows_the_inline_transform_fields_only_when_they_fit() { ctx.enable_accesskit(); crate::theme::install_fonts(&ctx); let mut seen = false; + // The Align group, then for two objects the Distribute group (#5372): each by its controls. + let mut groups: Vec<(&str, Vec<&str>)> = vec![( + "Align", + vec![ + "Horizontal Align Left", + "Horizontal Align Center", + "Horizontal Align Right", + "Horizontal & Vertical Align Center", + "Vertical Align Top", + "Vertical Align Center", + "Vertical Align Bottom", + ], + )]; + if count == 2 { + groups.push(( + "Distribute", + vec![ + "Vertical Distribute Top", + "Vertical Distribute Center", + "Vertical Distribute Bottom", + "Horizontal Distribute Left", + "Horizontal Distribute Center", + "Horizontal Distribute Right", + ], + )); + } + let mut groups_seen = vec![false; groups.len()]; for width in (600..=2400).step_by(10) { let screen = egui::Rect::from_min_size(egui::Pos2::ZERO, egui::vec2(width as f32, 300.0)); let mut nodes = Vec::new(); @@ -1980,7 +2008,24 @@ fn the_control_bar_shows_the_inline_transform_fields_only_when_they_fit() { } assert!(!seen || fields[3].is_some(), "{count} objects, {width}: left out after a narrower bar showed them"); seen |= fields[3].is_some(); + // The Align and Distribute buttons show whole inside the margin, with the Transform + // link after them, or are left out; once shown, a wider bar keeps them. + let end = |name: &str| nodes.iter().find(|(_, n)| n.label() == Some(name)).and_then(|(_, n)| n.bounds()).map(|b| b.x1); + let inside = |x: f64| x <= f64::from(width) - 10.0; + for ((group, names), was) in groups.iter().zip(groups_seen.iter_mut()) { + let ends: Vec> = names.iter().map(|n| end(n)).collect(); + let shown = ends.iter().all(Option::is_some); + assert!(shown || ends.iter().all(Option::is_none), "{count} objects, {width}: {group} {ends:?}"); + if let Some(Some(last)) = ends.last() { + assert!(inside(*last), "{count} objects, {width}: {group} ends at {last}"); + let transform = end("Transform"); + assert!(transform.is_some_and(inside), "{count} objects, {width}: Transform after {group} ends at {transform:?}"); + } + assert!(!*was || shown, "{count} objects, {width}: {group} left out after a narrower bar showed it"); + *was |= shown; + } } assert!(seen, "{count} objects: a wide bar has room for the fields"); + assert!(groups_seen.iter().all(|s| *s), "{count} objects: a wide bar shows every group: {groups_seen:?}"); } } diff --git a/crates/ui-egui/src/i18n/bg.tsv b/crates/ui-egui/src/i18n/bg.tsv index a40240ba4..28bc3eff5 100644 --- a/crates/ui-egui/src/i18n/bg.tsv +++ b/crates/ui-egui/src/i18n/bg.tsv @@ -3207,7 +3207,9 @@ digits Malayalam Малаялам Patterns Мостри Stroke Options Опции на щриха Character: Знак: + Paragraph: Абзац: Character options Опции на знака + Paragraph options Опции за абзац Stroke options Опции на щриха Transform options Опции за трансформации Stroke Weight Дебелина на щриха diff --git a/crates/ui-egui/src/i18n/bs.tsv b/crates/ui-egui/src/i18n/bs.tsv index a2464b883..eb2b821a3 100644 --- a/crates/ui-egui/src/i18n/bs.tsv +++ b/crates/ui-egui/src/i18n/bs.tsv @@ -1469,7 +1469,9 @@ digits Malayalam Malajalam Standard Vertical Roman Alignment Standardno okomito latinično poravnanje Strikethrough Precrtano Character: Znak: + Paragraph: Pasus: Character options Opcije znakova + Paragraph options Opcije pasusa Subscript Indeks Subscript (on the roadmap) Indeks (u planu) Superscript Eksponent diff --git a/crates/ui-egui/src/i18n/ca.tsv b/crates/ui-egui/src/i18n/ca.tsv index fe905d5ac..847243b71 100644 --- a/crates/ui-egui/src/i18n/ca.tsv +++ b/crates/ui-egui/src/i18n/ca.tsv @@ -615,12 +615,14 @@ Character Alignment Alineació de caràcters Character Direction Direcció de caràcters Character options Opcions de caràcter + Paragraph options Opcions de paràgraf Character Rotation Rotació de caràcters Character Set: Joc de caràcters: Character Style Estil de caràcter Character Style Options Opcions de l’estil de caràcter Character Styles Estils de caràcter Character: Caràcter: + Paragraph: Paràgraf: Charcoal Carbó Charcoal… Carbó… Check an artboard to save. Marca un tauler per desar. diff --git a/crates/ui-egui/src/i18n/cs.tsv b/crates/ui-egui/src/i18n/cs.tsv index 8231ee0f6..2de11a60c 100644 --- a/crates/ui-egui/src/i18n/cs.tsv +++ b/crates/ui-egui/src/i18n/cs.tsv @@ -1493,7 +1493,9 @@ digits Malayalam Malajálamské Standard Vertical Roman Alignment Standardní svislé zarovnání latinky Strikethrough Přeškrtnutí Character: Znak: + Paragraph: Odstavec: Character options Možnosti znaků + Paragraph options Možnosti odstavce Subscript Dolní index Subscript (on the roadmap) Dolní index (v plánu) Superscript Horní index diff --git a/crates/ui-egui/src/i18n/da.tsv b/crates/ui-egui/src/i18n/da.tsv index 1375be430..9db09ea0f 100644 --- a/crates/ui-egui/src/i18n/da.tsv +++ b/crates/ui-egui/src/i18n/da.tsv @@ -1403,7 +1403,9 @@ digits Malayalam Malayalam Standard Vertical Roman Alignment Standard lodret romersk justering Strikethrough Gennemstreget Character: Tegn: + Paragraph: Afsnit: Character options Tegnindstillinger + Paragraph options Afsnitsindstillinger Subscript Sænket skrift Subscript (on the roadmap) Sænket skrift (på vejkortet) Superscript Hævet skrift diff --git a/crates/ui-egui/src/i18n/de.tsv b/crates/ui-egui/src/i18n/de.tsv index acc8fad4f..e17e50261 100644 --- a/crates/ui-egui/src/i18n/de.tsv +++ b/crates/ui-egui/src/i18n/de.tsv @@ -3252,7 +3252,9 @@ effect Points Punkte Patterns Muster Stroke Options Konturoptionen Character: Zeichen: + Paragraph: Absatz: Character options Zeichenoptionen + Paragraph options Absatzoptionen Stroke options Konturoptionen Transform options Transformationsoptionen Stroke Weight Konturstärke diff --git a/crates/ui-egui/src/i18n/el.tsv b/crates/ui-egui/src/i18n/el.tsv index 56467eee2..80a49e899 100644 --- a/crates/ui-egui/src/i18n/el.tsv +++ b/crates/ui-egui/src/i18n/el.tsv @@ -1991,6 +1991,7 @@ font-technology Variable Μεταβλητές Patterns Μοτίβα Stroke Options Επιλογές περιγράμματος Character: Χαρακτήρας: + Paragraph: Παράγραφος: Stroke: Περίγραμμα: Style Στυλ Style: Στυλ: @@ -3655,6 +3656,7 @@ digits Malayalam Μαλαγιάλαμ Stray Points Μεμονωμένα σημεία Stroke (X), double-click for the Color Picker Περίγραμμα (X), διπλό κλικ για επιλογέα χρώματος Character options Επιλογές χαρακτήρων + Paragraph options Επιλογές παραγράφου Stroke options Επιλογές περιγράμματος Transform options Επιλογές μετασχηματισμού Stroke Weight Πάχος περιγράμματος diff --git a/crates/ui-egui/src/i18n/es.tsv b/crates/ui-egui/src/i18n/es.tsv index 7f803f468..bcd4fe12f 100644 --- a/crates/ui-egui/src/i18n/es.tsv +++ b/crates/ui-egui/src/i18n/es.tsv @@ -3291,7 +3291,9 @@ digits Malayalam Malayálam Patterns Motivos Stroke Options Opciones de trazo Character: Carácter: + Paragraph: Párrafo: Character options Opciones de carácter + Paragraph options Opciones de párrafo Stroke options Opciones de trazo Transform options Opciones de transformación Stroke Weight Grosor de trazo diff --git a/crates/ui-egui/src/i18n/fi.tsv b/crates/ui-egui/src/i18n/fi.tsv index 899e4b14a..fde2becd0 100644 --- a/crates/ui-egui/src/i18n/fi.tsv +++ b/crates/ui-egui/src/i18n/fi.tsv @@ -1466,7 +1466,9 @@ digits Malayalam Malajalam Standard Vertical Roman Alignment Tavallinen pystysuuntainen latinalainen tasaus Strikethrough Yliviivaus Character: Merkki: + Paragraph: Kappale: Character options Merkkiasetukset + Paragraph options Kappaleasetukset Subscript Alaindeksi Subscript (on the roadmap) Alaindeksi (suunnitteilla) Superscript Yläindeksi diff --git a/crates/ui-egui/src/i18n/fr.tsv b/crates/ui-egui/src/i18n/fr.tsv index ba0a97227..4a3eb1ce0 100644 --- a/crates/ui-egui/src/i18n/fr.tsv +++ b/crates/ui-egui/src/i18n/fr.tsv @@ -3283,7 +3283,9 @@ digits Malayalam Malayalam Patterns Motifs Stroke Options Options de contour Character: Caractère : + Paragraph: Paragraphe : Character options Options de caractère + Paragraph options Options de paragraphe Stroke options Options de contour Transform options Options de transformation Stroke Weight Épaisseur du contour diff --git a/crates/ui-egui/src/i18n/hu.tsv b/crates/ui-egui/src/i18n/hu.tsv index 9a0724c15..f21ca0d49 100644 --- a/crates/ui-egui/src/i18n/hu.tsv +++ b/crates/ui-egui/src/i18n/hu.tsv @@ -554,12 +554,14 @@ font-technology Variable Változó Character / Paragraph Karakter / bekezdés Character Alignment Karakterigazítás Character options Karakterbeállítások + Paragraph options Bekezdésbeállítások Character Rotation Karakterforgatás Character Set: Karakterkészlet: Character Style Karakterstílus Character Style Options Karakterstílus beállításai Character Styles Karakterstílusok Character: Karakter: + Paragraph: Bekezdés: Check Links Csatolások ellenőrzése # Check Script (File › Scripts) and the report it shows on a script written for the reference app. Check Script Szkript ellenőrzése diff --git a/crates/ui-egui/src/i18n/id.tsv b/crates/ui-egui/src/i18n/id.tsv index 00fc62d15..0a6c910fc 100644 --- a/crates/ui-egui/src/i18n/id.tsv +++ b/crates/ui-egui/src/i18n/id.tsv @@ -1403,7 +1403,9 @@ digits Malayalam Malayalam Standard Vertical Roman Alignment Perataan Romawi vertikal standar Strikethrough Coret Character: Karakter: + Paragraph: Paragraf: Character options Opsi karakter + Paragraph options Opsi paragraf Subscript Subskrip Subscript (on the roadmap) Subskrip (masih dalam roadmap) Superscript Superskrip diff --git a/crates/ui-egui/src/i18n/it.tsv b/crates/ui-egui/src/i18n/it.tsv index ccff8235f..68c1f23fb 100644 --- a/crates/ui-egui/src/i18n/it.tsv +++ b/crates/ui-egui/src/i18n/it.tsv @@ -3327,7 +3327,9 @@ digits Malayalam Malayalam Patterns Motivi Stroke Options Opzioni traccia Character: Carattere: + Paragraph: Paragrafo: Character options Opzioni carattere + Paragraph options Opzioni di paragrafo Stroke options Opzioni traccia Transform options Opzioni di trasformazione Stroke Weight Spessore traccia diff --git a/crates/ui-egui/src/i18n/ja.tsv b/crates/ui-egui/src/i18n/ja.tsv index 294240791..e230d5767 100644 --- a/crates/ui-egui/src/i18n/ja.tsv +++ b/crates/ui-egui/src/i18n/ja.tsv @@ -3090,7 +3090,9 @@ mark-style Japanese 日本式 Strikethrough 打ち消し線 Stroke Options 線オプション Character: 文字: + Paragraph: 段落: Character options 文字オプション + Paragraph options 段落オプション Stroke options 線オプション Transform options 変形オプション Stroke: 線: diff --git a/crates/ui-egui/src/i18n/ka.tsv b/crates/ui-egui/src/i18n/ka.tsv index 9da177b99..32293dd8e 100644 --- a/crates/ui-egui/src/i18n/ka.tsv +++ b/crates/ui-egui/src/i18n/ka.tsv @@ -1329,7 +1329,9 @@ digits Malayalam მალაიალამური Standard Vertical Roman Alignment სტანდარტული ვერტიკალური ლათინური სწორება Strikethrough გადახაზული Character: სიმბოლო: + Paragraph: აბზაცი: Character options სიმბოლოს პარამეტრები + Paragraph options აბზაცის პარამეტრები Subscript ქვედა ინდექსი Subscript (on the roadmap) ქვედა ინდექსი (სამომავლო გეგმაშია) Superscript ზედა ინდექსი diff --git a/crates/ui-egui/src/i18n/ko.tsv b/crates/ui-egui/src/i18n/ko.tsv index 5c215668c..627963555 100644 --- a/crates/ui-egui/src/i18n/ko.tsv +++ b/crates/ui-egui/src/i18n/ko.tsv @@ -3079,7 +3079,9 @@ mark-style Japanese 일본식 Strikethrough 취소선 Stroke Options 선 옵션 Character: 문자: + Paragraph: 단락: Character options 문자 옵션 + Paragraph options 단락 옵션 Stroke options 선 옵션 Transform options 변형 옵션 Stroke: 선: diff --git a/crates/ui-egui/src/i18n/nl.tsv b/crates/ui-egui/src/i18n/nl.tsv index 300fb3de1..7b670288e 100644 --- a/crates/ui-egui/src/i18n/nl.tsv +++ b/crates/ui-egui/src/i18n/nl.tsv @@ -1417,7 +1417,9 @@ digits Malayalam Malayalam Standard Vertical Roman Alignment Standaard verticale Romeinse uitlijning Strikethrough Doorhalen Character: Teken: + Paragraph: Alinea: Character options Tekenopties + Paragraph options Alineaopties Subscript Subscript Subscript (on the roadmap) Subscript (op de roadmap) Superscript Superscript diff --git a/crates/ui-egui/src/i18n/pl.tsv b/crates/ui-egui/src/i18n/pl.tsv index 197f6b9d6..759ad22a2 100644 --- a/crates/ui-egui/src/i18n/pl.tsv +++ b/crates/ui-egui/src/i18n/pl.tsv @@ -1406,7 +1406,9 @@ digits Malayalam Malajalam Standard Vertical Roman Alignment Standardowe pionowe wyrównanie łacińskie Strikethrough Przekreślenie Character: Znak: + Paragraph: Akapit: Character options Opcje znaków + Paragraph options Opcje akapitu Subscript Indeks dolny Subscript (on the roadmap) Indeks dolny (w planach) Superscript Indeks górny diff --git a/crates/ui-egui/src/i18n/pt-br.tsv b/crates/ui-egui/src/i18n/pt-br.tsv index 3a9a0c7f8..da5ac46f9 100644 --- a/crates/ui-egui/src/i18n/pt-br.tsv +++ b/crates/ui-egui/src/i18n/pt-br.tsv @@ -2748,7 +2748,9 @@ digits Malayalam Malaiala Stroke Options Opções de Traçado Stroke Weight Peso do Traçado Character: Caractere: + Paragraph: Parágrafo: Character options Opções de caractere + Paragraph options Opções de parágrafo Stroke options Opções de traçado Transform options Opções de transformação Stroke: Traçado: diff --git a/crates/ui-egui/src/i18n/ru.tsv b/crates/ui-egui/src/i18n/ru.tsv index 3a717f676..ec8506207 100644 --- a/crates/ui-egui/src/i18n/ru.tsv +++ b/crates/ui-egui/src/i18n/ru.tsv @@ -3322,7 +3322,9 @@ digits Malayalam Малаялам Patterns Узоры Stroke Options Параметры обводки Character: Символ: + Paragraph: Абзац: Character options Параметры символа + Paragraph options Параметры абзаца Stroke options Параметры обводки Transform options Параметры трансформирования Stroke Weight Толщина обводки diff --git a/crates/ui-egui/src/i18n/sv.tsv b/crates/ui-egui/src/i18n/sv.tsv index 839596958..2baf46b44 100644 --- a/crates/ui-egui/src/i18n/sv.tsv +++ b/crates/ui-egui/src/i18n/sv.tsv @@ -3213,7 +3213,9 @@ digits Malayalam Malayalam Patterns Mönster Stroke Options Konturalternativ Character: Tecken: + Paragraph: Stycke: Character options Teckenalternativ + Paragraph options Styckealternativ Stroke options Konturalternativ Transform options Transformeringsalternativ Stroke Weight Konturtjocklek diff --git a/crates/ui-egui/src/i18n/tr.tsv b/crates/ui-egui/src/i18n/tr.tsv index f84137f80..312e6163b 100644 --- a/crates/ui-egui/src/i18n/tr.tsv +++ b/crates/ui-egui/src/i18n/tr.tsv @@ -3115,7 +3115,9 @@ digits Malayalam Malayalam Patterns Desenler Stroke Options Kontur Seçenekleri Character: Karakter: + Paragraph: Paragraf: Character options Karakter seçenekleri + Paragraph options Paragraf seçenekleri Stroke options Kontur seçenekleri Transform options Dönüştürme seçenekleri Stroke Weight Kontur Kalınlığı diff --git a/crates/ui-egui/src/i18n/uk.tsv b/crates/ui-egui/src/i18n/uk.tsv index 257723846..47075bd0e 100644 --- a/crates/ui-egui/src/i18n/uk.tsv +++ b/crates/ui-egui/src/i18n/uk.tsv @@ -3320,7 +3320,9 @@ effect Points Точки Patterns Візерунки Stroke Options Параметри обведення Character: Символ: + Paragraph: Абзац: Character options Параметри символу + Paragraph options Параметри абзацу Stroke options Параметри обведення Transform options Параметри трансформування Stroke Weight Товщина обведення diff --git a/crates/ui-egui/src/i18n/vi.tsv b/crates/ui-egui/src/i18n/vi.tsv index cad18a0d6..2e9f19d38 100644 --- a/crates/ui-egui/src/i18n/vi.tsv +++ b/crates/ui-egui/src/i18n/vi.tsv @@ -468,12 +468,14 @@ font-technology Variable Biến thiên Character / Paragraph Ký tự / Đoạn văn Character Alignment Căn chỉnh ký tự Character options Tùy chọn ký tự + Paragraph options Tùy chọn đoạn văn Character Rotation Xoay ký tự Character Set: Bộ ký tự: Character Style Kiểu ký tự Character Style Options Tùy chọn kiểu ký tự Character Styles Các kiểu ký tự Character: Ký tự: + Paragraph: Đoạn văn: Check Links Kiểm tra liên kết # Check Script (File › Scripts) and the report it shows on a script written for the reference app. Check Script Kiểm tra script diff --git a/crates/ui-egui/src/i18n/zh-hans.tsv b/crates/ui-egui/src/i18n/zh-hans.tsv index e0030aa39..65c01ada8 100644 --- a/crates/ui-egui/src/i18n/zh-hans.tsv +++ b/crates/ui-egui/src/i18n/zh-hans.tsv @@ -3158,7 +3158,9 @@ digits Malayalam 马拉雅拉姆数字 Patterns 图案 Stroke Options 描边选项 Character: 字符: + Paragraph: 段落: Character options 字符选项 + Paragraph options 段落选项 Stroke options 描边选项 Transform options 变换选项 Stroke Weight 描边粗细 diff --git a/crates/ui-egui/src/i18n/zh-hant.tsv b/crates/ui-egui/src/i18n/zh-hant.tsv index 36e562b2e..2fe1e1872 100644 --- a/crates/ui-egui/src/i18n/zh-hant.tsv +++ b/crates/ui-egui/src/i18n/zh-hant.tsv @@ -4185,7 +4185,9 @@ digits Malayalam 馬來亞拉姆數字 Patterns 圖樣 Stroke Options 筆畫選項 Character: 字元: + Paragraph: 段落: Character options 字元選項 + Paragraph options 段落選項 Stroke options 筆畫選項 Transform options 變形選項 Stroke Weight 筆畫寬度 diff --git a/crates/ui-egui/src/panels/paragraph.rs b/crates/ui-egui/src/panels/paragraph.rs index 66f907f92..8cb29ef30 100644 --- a/crates/ui-egui/src/panels/paragraph.rs +++ b/crates/ui-egui/src/panels/paragraph.rs @@ -380,14 +380,66 @@ fn selected_alignment(app: &VectorcraftApp, fallback: &ParaStyle) -> Option), and +/// its alignment buttons align text as the Paragraph panel's do +/// (). The bar keeps to the three that +/// don't justify, for its room; all seven are in the Paragraph options the link opens. +const CONTROL_BAR_ALIGNMENTS: usize = 3; + +/// The spacing between the Control bar's alignment buttons, as in the Paragraph panel's row. +const CONTROL_BAR_BUTTON_SPACING: f32 = 3.0; + +/// The width of the Paragraph panel in the Control bar's popover, as the Character panel's. +const CONTROL_BAR_POPOVER_WIDTH: f32 = 260.0; + +/// The Control bar's Paragraph link, as it is drawn and measured. +fn control_bar_link_text(ui: &Ui) -> egui::RichText { + egui::RichText::new(tl!("Paragraph:")).size(12.0).color(Tokens::get(ui.ctx()).text).underline() +} + +/// The Control bar's paragraph controls while type is selected or edited (#5372), after its +/// Character controls: the Paragraph link, which opens the Paragraph panel in a popover under it, +/// and the Align left, Align center and Align right buttons, `size` points square as the bar's others. `reserve` gives the room the controls +/// after them keep, asked only when type is selected: short of room, the buttons are left out, +/// then the link too. Returns whether the link shows. +pub(crate) fn control_bar(app: &mut VectorcraftApp, ui: &mut Ui, size: f32, reserve: impl FnOnce(&Ui) -> f32) -> bool { + let Some((_, para)) = text_style(app) else { return false }; + let text = control_bar_link_text(ui); + let link_width = widgets::text_width(ui, text.clone()); + let room = ui.available_width() - reserve(ui); + let link_room = link_width + ui.spacing().item_spacing.x; + if room < link_room { + return false; + } + let link = ui.link(text).on_hover_text(tl!("Paragraph options")); + widgets::popover(&link, link.clicked(), |ui| { + ui.set_width(CONTROL_BAR_POPOVER_WIDTH); + show(app, ui); + }); + if room >= link_room + (size + CONTROL_BAR_BUTTON_SPACING) * CONTROL_BAR_ALIGNMENTS as f32 { + let spacing = ui.spacing().item_spacing.x; + ui.spacing_mut().item_spacing.x = CONTROL_BAR_BUTTON_SPACING; + first_alignment_buttons(app, ui, ¶, size, CONTROL_BAR_ALIGNMENTS); + ui.spacing_mut().item_spacing.x = spacing; + } + true +} + /// Type › Bullets and Numbering › Apply Bullets (`type.applyBullets {bullet?}`): the paragraphs of /// the selected type, or those the Type tool's selection touches, become a bulleted list with /// `bullet`; without it, a list keeps its bullet and other paragraphs take the dot. diff --git a/crates/ui-egui/src/panels/paragraph/tests.rs b/crates/ui-egui/src/panels/paragraph/tests.rs index 163427da5..0423bc0c9 100644 --- a/crates/ui-egui/src/panels/paragraph/tests.rs +++ b/crates/ui-egui/src/panels/paragraph/tests.rs @@ -543,3 +543,150 @@ fn reset_panel_resets_the_hyphenation_options() { assert_eq!(serde_json::to_value(¶).unwrap().get("justification"), None, "saves no justification options"); } } + +type Nodes = Vec<(egui::accesskit::NodeId, egui::accesskit::Node)>; + +/// An English context for headless Control bar frames, with AccessKit on to find its controls. +fn control_bar_ctx() -> egui::Context { + crate::i18n::set_current(crate::i18n::Lang::EN); + let ctx = egui::Context::default(); + ctx.enable_accesskit(); + crate::theme::install_fonts(&ctx); + ctx +} + +/// A document with the type `texts` (`text.create` params) makes, all of it selected. +fn control_bar_app(texts: &[Value]) -> VectorcraftApp { + let mut app = VectorcraftApp::new(Session::new(), Default::default()); + app.run("file.new", json!({"width": 800, "height": 400})).unwrap(); + let ids: Vec = texts.iter().map(|p| app.run("text.create", p.clone()).unwrap()["id"].clone()).collect(); + app.run("select.set", json!({ "ids": ids })).unwrap(); + app +} + +/// The second of two headless frames of the whole Control bar, `width` wide (the first lays it out). +fn control_bar_nodes(ctx: &egui::Context, app: &mut VectorcraftApp, width: f32, events: Vec) -> Nodes { + let screen = egui::Rect::from_min_size(egui::Pos2::ZERO, vec2(width, 300.0)); + let mut nodes = Vec::new(); + for events in [events, vec![]] { + let mut out = + ctx.run_ui(egui::RawInput { events, screen_rect: Some(screen), ..Default::default() }, |ui| crate::chrome::control_bar(app, ui)); + out.textures_delta.clear(); + nodes = out.platform_output.accesskit_update.map(|u| u.nodes).unwrap_or_default(); + } + nodes +} + +/// The control named `name` (a button's or link's label, a label's text). +fn control_bar_node(nodes: &Nodes, name: &str) -> Option { + nodes.iter().find(|(_, n)| n.label() == Some(name) || n.value() == Some(name)).map(|(_, n)| n.clone()) +} + +/// Where the control named `name` ends, if it shows. +fn control_bar_end(nodes: &Nodes, name: &str) -> Option { + control_bar_node(nodes, name).and_then(|n| n.bounds()).map(|b| b.x1) +} + +/// The Control bar's Paragraph alignment buttons. +const BAR_ALIGNMENTS: [&str; 3] = ["Align left", "Align center", "Align right"]; + +/// #5372: with type selected, the Control bar shows a Paragraph section after the Character one, as +/// the public documentation's Control panel does: a Paragraph: link that opens the Paragraph panel +/// in a popover under it, and the Align left, Align center and Align right buttons. A click aligns +/// the selected type, and the button of its alignment shows selected. The buttons show on a bar of +/// common window widths. +#[test] +fn the_control_bar_shows_the_paragraph_alignment_of_the_selected_type() { + let ctx = control_bar_ctx(); + let mut app = control_bar_app(&[json!({"x": 20, "y": 50, "text": "Point"})]); + // Pinned, so that the room the buttons need can't creep up past common window widths. + for width in [1440.0, 1920.0] { + let nodes = control_bar_nodes(&ctx, &mut app, width, vec![]); + let ends = BAR_ALIGNMENTS.map(|n| control_bar_end(&nodes, n)); + assert!(ends.iter().all(|e| e.is_some_and(|x| x <= f64::from(width) - 10.0)), "pinned: the buttons show on a {width} pt window: {ends:?}"); + } + let width = 1920.0; + let nodes = control_bar_nodes(&ctx, &mut app, width, vec![]); + let center = |n: &egui::accesskit::Node| { + let b = n.bounds().unwrap_or_else(|| panic!("{n:?}")); + egui::pos2(((b.x0 + b.x1) / 2.0) as f32, ((b.y0 + b.y1) / 2.0) as f32) + }; + let click = |app: &mut VectorcraftApp, at: egui::Pos2| { + let press = |pressed| egui::Event::PointerButton { pos: at, button: egui::PointerButton::Primary, pressed, modifiers: Default::default() }; + control_bar_nodes(&ctx, app, width, vec![egui::Event::PointerMoved(at), press(true)]); + control_bar_nodes(&ctx, app, width, vec![press(false)]); + control_bar_nodes(&ctx, app, width, vec![]) + }; + // A selected button shows pressed. + let on = |n: &egui::accesskit::Node| n.toggled() == Some(egui::accesskit::Toggled::True); + let button = control_bar_node(&nodes, "Align center").unwrap_or_else(|| panic!("the Control bar has Align center")); + assert!(!on(&button), "left-aligned type: Align center is not selected"); + assert!(control_bar_node(&nodes, "Align left").is_some_and(|n| on(&n)), "Align left shows selected"); + let undo = app.session.active().unwrap().history.undo.len(); + let nodes = click(&mut app, center(&button)); + assert_eq!(text_style(&app).unwrap().1.justify, Justify::Center, "{}", app.ui.status); + assert_eq!(app.session.active().unwrap().history.undo.len(), undo + 1, "one undo step"); + assert!(control_bar_node(&nodes, "Align center").is_some_and(|n| on(&n)), "Align center shows selected"); + assert!(!control_bar_node(&nodes, "Align left").is_some_and(|n| on(&n)), "Align left no longer selected"); + // The Paragraph: link opens the Paragraph panel in a popover: its justifications show there, + // no panel docks, and a second click closes it. + let link = control_bar_node(&nodes, "Paragraph:").unwrap_or_else(|| panic!("the Paragraph: link")); + assert_eq!(link.description(), Some("Paragraph options")); + assert!(control_bar_node(&nodes, "Justify all lines").is_none(), "closed"); + let nodes = click(&mut app, center(&link)); + assert!(control_bar_node(&nodes, "Justify all lines").is_some(), "the popover shows the Paragraph panel"); + assert_eq!(app.ui.open_panel, None, "no panel docks"); + let nodes = click(&mut app, center(&link)); + assert!(control_bar_node(&nodes, "Justify all lines").is_none(), "a second click closes it"); +} + +/// #5372: at any width the Control bar trims itself from its right end, ending inside its margin. +/// With type selected the inline X/Y/W/H fields go first, then the Distribute and Align buttons, +/// area type's vertical alignment, the Paragraph buttons and last the Paragraph: link, while the +/// Transform link keeps its room; a wider bar never leaves out what a narrower one showed. For one point +/// type object, area type (its vertical alignment) and two objects (the Distribute buttons). +#[test] +fn the_control_bar_trims_its_paragraph_section_after_the_controls_to_its_right() { + let ctx = control_bar_ctx(); + let point = |x: i32| json!({"x": x, "y": 50, "text": "Point"}); + let area = json!({"x": 20, "y": 20, "text": "Area", "area": {"width": 100, "height": 120}}); + const VERTICAL: [&str; 4] = ["Align Top", "Align Center", "Align Bottom", "Justify Vertically"]; + for (case, texts) in [("point type", vec![point(20)]), ("area type", vec![area]), ("two objects", vec![point(20), point(300)])] { + let mut app = control_bar_app(&texts); + // Each group of controls in the bar's order, by its first and last control. + let mut groups: Vec<(&str, Vec<&str>)> = vec![("link", vec!["Paragraph:"]), ("buttons", BAR_ALIGNMENTS.to_vec())]; + if case == "area type" { + groups.push(("vertical alignment", VERTICAL.to_vec())); + } + groups.push(("Align", vec!["Horizontal Align Left", "Vertical Align Bottom"])); + if case == "two objects" { + groups.push(("Distribute", vec!["Vertical Distribute Top", "Horizontal Distribute Right"])); + } + groups.push(("X/Y/W/H", vec!["X:", "H:"])); + let mut seen = vec![false; groups.len()]; + for width in (600..=2400).step_by(10) { + let nodes = control_bar_nodes(&ctx, &mut app, width as f32, vec![]); + let inside = |x: f64| x <= f64::from(width) - 10.0; + let shown: Vec = groups + .iter() + .map(|(group, names)| { + let ends: Vec> = names.iter().map(|n| control_bar_end(&nodes, n)).collect(); + assert!(ends.iter().all(Option::is_some) || ends.iter().all(Option::is_none), "{case}, {width}: {group} {ends:?}"); + if let Some(Some(end)) = ends.last() { + assert!(inside(*end), "{case}, {width}: {group} ends at {end}"); + } + ends.iter().all(Option::is_some) + }) + .collect(); + // Narrower bars are already full before the section (the Character controls). + let transform = control_bar_end(&nodes, "Transform"); + assert!(!shown[0] || transform.is_some_and(inside), "{case}, {width}: Transform ends at {transform:?}"); + for (i, ((group, _), on)) in groups.iter().zip(&shown).enumerate() { + assert!(!seen[i] || *on, "{case}, {width}: {group} left out after a narrower bar showed it"); + assert!(!*on || shown[..i].iter().all(|s| *s), "{case}, {width}: {group} shows while a group before it is left out"); + seen[i] |= on; + } + } + assert!(seen.iter().all(|s| *s), "{case}: a wide bar shows every group: {seen:?}"); + } +} diff --git a/crates/ui-egui/src/panels/transform.rs b/crates/ui-egui/src/panels/transform.rs index 0f1cf3db5..3cd54cc85 100644 --- a/crates/ui-egui/src/panels/transform.rs +++ b/crates/ui-egui/src/panels/transform.rs @@ -461,14 +461,23 @@ pub(crate) fn polygon_rows(app: &mut VectorcraftApp, ui: &mut Ui, n: &Node, live /// The Control bar's underlined "Transform" link: a click toggles the Transform panel in a /// popover under it, as the Stroke link does with the Stroke panel. pub fn link(app: &mut VectorcraftApp, ui: &mut Ui) { - let t = Tokens::get(ui.ctx()); - let resp = ui.link(egui::RichText::new(tl!("Transform")).size(12.0).color(t.text).underline()).on_hover_text(tl!("Transform options")); + let resp = ui.link(link_text(ui)).on_hover_text(tl!("Transform options")); widgets::popover(&resp, resp.clicked(), |ui| { ui.set_width(300.0); show(app, ui); }); } +/// The Transform link's text, as [`link`] draws it and [`link_width`] measures it. +fn link_text(ui: &Ui) -> egui::RichText { + egui::RichText::new(tl!("Transform")).size(12.0).color(Tokens::get(ui.ctx()).text).underline() +} + +/// The width of the Transform [`link`] in `ui`. +pub fn link_width(ui: &Ui) -> f32 { + widgets::text_width(ui, link_text(ui)) +} + /// The width and height of [`constrain_link`]'s button. pub const CONSTRAIN_LINK_SIZE: f32 = 22.0; diff --git a/crates/ui-egui/src/widgets.rs b/crates/ui-egui/src/widgets.rs index b5bc1991b..5c8a701dd 100644 --- a/crates/ui-egui/src/widgets.rs +++ b/crates/ui-egui/src/widgets.rs @@ -49,7 +49,7 @@ pub use dropdowns::{ panel_scroll, popover, set_menu_floor, strip_scroll, }; pub use labels::{dim_label, dim_name, divider, field_label, label_row, link_label, section_header, section_toggle, section_toggle_name, subheader}; -pub(crate) use labels::{label_field, take_previous_label}; +pub(crate) use labels::{label_field, take_previous_label, text_width}; pub(crate) use numeric::{ANY, round_to, set_bare_numbers_are_points, typed_unit}; pub use numeric::{ SpinPick, mixed_field, mixed_field_named, num_field, num_field_live, num_field_named, num_field_within, num_field_within_empty, opt_field, diff --git a/crates/ui-egui/src/widgets/labels.rs b/crates/ui-egui/src/widgets/labels.rs index a71a0ec6a..8c04de54b 100644 --- a/crates/ui-egui/src/widgets/labels.rs +++ b/crates/ui-egui/src/widgets/labels.rs @@ -1,5 +1,11 @@ use super::{Pos2, Rect, Response, Sense, Stroke, Tokens, Ui, icons, pos2, scrub, theme, vec2}; +/// The width `text` takes on one line, as a label or link in `ui` draws it: its galley, which has +/// no padding around it. +pub(crate) fn text_width(ui: &Ui, text: egui::RichText) -> f32 { + egui::WidgetText::from(text).into_galley(ui, Some(egui::TextWrapMode::Extend), f32::INFINITY, egui::TextStyle::Body).size().x +} + /// Bold section header (Properties panel). pub fn section_header(ui: &mut Ui, text: &str) { let t = Tokens::get(ui.ctx()); diff --git a/docs/target-app-parity.md b/docs/target-app-parity.md index a820add61..95c244540 100644 --- a/docs/target-app-parity.md +++ b/docs/target-app-parity.md @@ -1,6 +1,6 @@ # Parity with Adobe Illustrator -> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-11-05 · **Change:** minor (large images in CMYK documents are shrunk before they are separated into ink planes (#4155); scores and estimates unchanged; Libraries panel colours keep their spot ink in every document, #5812; scores and estimates unchanged) · **Target:** Adobe Illustrator 2026 (30.x; public release notes through 30.7, August 2026) +> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-11-06 · **Change:** minor (Control bar Paragraph section with type selected, #5372; scores and estimates unchanged) · **Target:** Adobe Illustrator 2026 (30.x; public release notes through 30.7, August 2026) ## Feature submission evidence @@ -1672,6 +1672,10 @@ While a corner widget is pressed or dragged with the Selection or Direct Selecti A double-click on a Repeat (Radial, Grid or Mirror) with the Selection tool enters isolation mode on it ([isolate objects](https://helpx.adobe.com/illustrator/desktop/manage-objects/select-objects/isolate-objects.html): a double-click with the Selection tool isolates an object; [repeat patterns overview](https://helpx.adobe.com/illustrator/desktop/paint-and-fill/create-and-edit-patterns/repeat-patterns-overview.html): every instance follows edits to the original object); there the source art is selected and edited as usual (a double-click on type in it switches to the Type tool on that text) and every copy follows, with the repeat's settings kept (#5054). Test: `tests::repeat_edit::a_double_click_on_a_repeat_isolates_it_to_edit_its_text`. +## Control bar: Paragraph section while type is selected (2026-11-06) + +With type selected, the Control bar shows a Paragraph: link and the Align left, Align center and Align right buttons after its Character controls, as the public documentation has the Control panel format paragraphs while the Type tool is in use, with a way to the Paragraph panel ([Paragraph panel overview](https://helpx.adobe.com/illustrator/using/formatting-paragraphs.html)), and its alignment buttons align text as the Paragraph panel's do ([Align text](https://helpx.adobe.com/illustrator/using/align-text.html)). A button aligns the selected type (`text.setStyle {justify}`, one undo step) and the shared alignment shows selected; the link opens the Paragraph panel in a popover under it, as Character: opens the Character panel. Keeping to the three alignments that don't justify is our choice, for the bar's room; the popover has all seven. Short of room the bar leaves controls out from its right end: the inline X/Y/W/H fields, the Distribute and Align buttons, area type's vertical alignment, then these buttons and last the link, keeping room for the Transform link; on a 1440 pt bar with one point type object, the buttons show (pinned by a headless test, #5372). + ## Area type: vertical alignment in the Properties and Control panels (2026-11-04) With area type selected, the Properties panel's Paragraph section and the Control panel show Align Top, Align Center, Align Bottom and Justify Vertically buttons, as the public documentation places them ([Align text](https://helpx.adobe.com/illustrator/desktop/design-with-text/edit-format-text/align-text.html)). They set the type's vertical alignment in its frame (`text.areaOptions {verticalAlign}`, as Area Type Options does) on every selected frame in one undo step, each frame keeping its other options; the shared alignment shows selected, none when the frames differ (#3759). As that page limits vertical alignment to area type without rows, columns or threads, the buttons are turned off while a selected frame has rows or columns or is threaded; Area Type Options and `text.areaOptions` still align each row/column cell (a VectorCraft extension, unchanged here). Tests: `panels::properties::tests::vertical_alignment_buttons_align_the_selected_area_type`. @@ -1684,6 +1688,7 @@ Edit › Copy and Cut put the selected ruler guides on the clipboard, with the s | Date | Change | Summary | |---|---|---| +| 2026-11-06 | minor | Control bar: Paragraph: link and Align left/center/right while type is selected, trimmed from the bar's right end after X/Y/W/H and the Align buttons (#5372); evidence added; scores unchanged. | | 2026-11-04 | minor | Contour add-on (beyond Illustrator): drag away from a selected shape's edge to grow a live copy of it, or toward the inside to shrink it; the copy carries a live Offset Path effect, so it stays editable until Bake (Expand Appearance), a second drag changes the same Contour instead of nesting one, and a floating bar sets the distance and the Sharp, Round or Square corners. `addon.contour.set` is the command behind it. It is a built-in add-on: Object › Plug-ins › Contour, and its own heading in All Tools, with no default shortcut or toolbar slot. A VectorCraft design built on Offset Path; no other application was inspected. Scores and estimates unchanged. | | 2026-11-04 | minor | Contour tool (beyond Illustrator): drag away from a selected shape's edge to grow a live copy of it, or toward the inside to shrink it; the copy carries a live Offset Path effect, so it stays editable until Bake (Expand Appearance), a second drag changes the same Contour instead of nesting one, and a floating bar sets the distance and the Sharp, Round or Square corners. `object.contour` is the command behind it. A VectorCraft design built on Offset Path; no other application was inspected. Scores and estimates unchanged. | | 2026-11-05 | minor | View & navigation: document tabs are reordered by dragging them along the tab strip (`document.move`, #5663); evidence added; scores unchanged. | diff --git a/docs/ui-parity.md b/docs/ui-parity.md index c090a1539..7bbc9e58c 100644 --- a/docs/ui-parity.md +++ b/docs/ui-parity.md @@ -1,6 +1,6 @@ # UI and interaction parity -> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-11-05 · **Change:** minor (Effect dialogs: a colour swatch beside a hex colour opens the Color Picker, #5365; scores and estimates unchanged) · **Target:** Adobe Illustrator 2026 (30.x; Units › East Asian Type acts, with Q and Ha units; scores unchanged) +> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-11-06 · **Change:** minor (Control bar: Paragraph: link and Align left/center/right while type is selected, #5372; scores and estimates unchanged) · **Target:** Adobe Illustrator 2026 (30.x; Units › East Asian Type acts, with Q and Ha units; scores unchanged) ## Feature submission evidence @@ -359,6 +359,7 @@ next unit in the order a right-click on a ruler lists them, from Feet back to Po | Select › Start Global Edit / Stop Global Edit; Properties › Quick Actions with the Global Edit Options popover (Match: Appearance, Size; On Artboards: All Artboards or a range, Include Objects On Canvas; Result(s)); Escape stops it; the matched objects are highlighted in their layer colour while it runs; a Shift-click on a highlighted match leaves it out | partial | #3521, `select.globalEdit` / `select.globalEditOptions` / `select.globalEditExclude`, `crates/engine/src/tests/globaledit.rs`; paths, compound paths, symbol instances and groups of them (similar groups, #4939: edits inside one group reach the same object in the others; [public source](https://helpx.adobe.com/illustrator/desktop/manage-objects/edit-objects/edit-groups-with-similar-objects.html)); Shift-click to leave a match out ([public source](https://helpx.adobe.com/ph_fil/illustrator/how-to/global-editing-sensei.html)); the Control bar's Global Edit Options dropdown opens the same options; rotated copies and highlighting matches before the start remain ([public source](https://helpx.adobe.com/illustrator/desktop/manage-objects/edit-objects/edit-similar-objects.html)) | | Contextual task bar | done | | | Control bar with Transform and Stroke popovers | done | | +| Control bar: Paragraph: link and Align left/center/right while type is selected (#5372) | done | After the Character controls, as the public documentation has the Control panel format paragraphs ([Paragraph panel overview](https://helpx.adobe.com/illustrator/using/formatting-paragraphs.html), [Align text](https://helpx.adobe.com/illustrator/using/align-text.html)); the link opens the Paragraph panel in a popover, as Character: does with the Character panel. The bar trims from its right end: X/Y/W/H, Distribute, Align, area type vertical alignment, then these buttons, then the link. The three-button set is our choice for room; all seven alignments are in the popover. `panels/paragraph/tests.rs` headless tests (`the_control_bar_shows_the_paragraph_alignment_of_the_selected_type`, `the_control_bar_trims_its_paragraph_section_after_the_controls_to_its_right`) | | Pathfinder shape results keep their source names and SVG IDs | done | #1293; Minus Front keeps the back name, Minus Back the front name; Unite/Intersect/Exclude use the backmost named input; compound output carries it once; geometry and paint unchanged | | Attributes fill-rule controls with a compound member selected | done | Edits the compound's rule; direction still edits only the member; rendered-hole and Undo/Redo regression tests (#1197) | | Make Compound Path on a group (such as outlined type) keeps its backmost path's paint; compound paths joined into one keep their holes | done | #1537 | @@ -456,6 +457,7 @@ separate. Scores and hours unchanged. [Behavior and command details](development | Date | Change | Summary | |---|---|---| +| 2026-11-06 | minor | Control bar: with type selected, a Paragraph: link (the Paragraph panel in a popover) and the Align left, Align center and Align right buttons follow the Character controls; the bar leaves them out from its right end when short of room, after X/Y/W/H and the Align buttons (#5372); scores and estimates unchanged | | 2026-11-04 | minor | Contour add-on (beyond Illustrator): drag away from a selected shape's edge to grow a live copy of it, or toward the inside to shrink it; the copy carries a live Offset Path effect, so it stays editable until Bake (Expand Appearance), a second drag changes the same Contour instead of nesting one, and a floating bar sets the distance and the Sharp, Round or Square corners. `addon.contour.set` is the command behind it. It is a built-in add-on: Object › Plug-ins › Contour, and its own heading in All Tools, with no default shortcut or toolbar slot. A VectorCraft design built on Offset Path; no other application was inspected. Scores and estimates unchanged. | | 2026-11-04 | minor | Contour tool (beyond Illustrator): drag away from a selected shape's edge to grow a live copy of it, or toward the inside to shrink it; the copy carries a live Offset Path effect, so it stays editable until Bake (Expand Appearance), a second drag changes the same Contour instead of nesting one, and a floating bar sets the distance and the Sharp, Round or Square corners. `object.contour` is the command behind it. A VectorCraft design built on Offset Path; no other application was inspected. Scores and estimates unchanged. | | 2026-11-05 | minor | Document tabs are reordered by dragging them along the tab strip (`document.move`, #5663). |