mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-31 13:17:19 -06:00
Merge pull request #4674 from storytold/feat/control-bar-symbol-replace
Control bar: Replace menu for selected symbol instances (#4121)
This commit is contained in:
@@ -190,6 +190,7 @@ pub fn control_bar(app: &mut VectorcraftApp, ui: &mut Ui) {
|
||||
let image = crate::place::control_bar_details(app, ui) || crate::panels::image_trace::control_bar(app, ui);
|
||||
crate::toolbar::control_bar_options(app, ui);
|
||||
crate::dialogs::envelope::control_bar(app, ui);
|
||||
crate::panels::symbols::control_bar(app, ui);
|
||||
let opacity = if first.is_some() { crate::panels::current_transparency(app).map_or(1.0, |t| t.0) } else { 1.0 };
|
||||
if !image {
|
||||
let shown_stroke = crate::panels::current_stroke(app);
|
||||
|
||||
@@ -2583,6 +2583,7 @@ font-technology Variable Променливи
|
||||
Repeat Tab Повтаряне на табулатора
|
||||
Repeat {name} Повтаряне на {name}
|
||||
Replace Замяна
|
||||
Replace: Замяна:
|
||||
Replace Font Замяна на шрифт
|
||||
Replace it with another file, or ignore it: its images keep showing the preview saved with the document. Заменете го с друг файл или го игнорирайте: неговите изображения ще продължат да показват записания с документа преглед.
|
||||
Replace Spine Замяна на оста
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -3285,6 +3285,7 @@
|
||||
Repeat {name} Repeteix {name}
|
||||
Repeating Repetició
|
||||
Replace Reemplaça
|
||||
Replace: Reemplaça:
|
||||
Replace Font Reemplaça el tipus de lletra
|
||||
Replace it with another file, or ignore it: its images keep showing the preview saved with the document. Reemplaça’l per un altre fitxer o ignora’l: les seves imatges continuaran mostrant la previsualització desada amb el document.
|
||||
Replace Spine Reemplaça l’espina
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -3800,6 +3800,7 @@ axis Both Obě
|
||||
Set Text Nastavit text
|
||||
Insert Character Vložit znak
|
||||
Replace Nahradit
|
||||
Replace: Nahradit:
|
||||
Type on a Path Options Možnosti textu na cestě
|
||||
Highlight Variable Zvýraznit proměnnou
|
||||
New Variable Nová proměnná
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -3537,6 +3537,7 @@ axis Both Begge
|
||||
Set Text Angiv tekst
|
||||
Insert Character Indsæt tegn
|
||||
Replace Erstat
|
||||
Replace: Erstat:
|
||||
Type on a Path Options Indstillinger for tekst på kurve
|
||||
Highlight Variable Fremhæv variabel
|
||||
New Variable Ny variabel
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -2603,6 +2603,7 @@ mark-style Japanese Japanisch
|
||||
Repeat Tab Tabulator wiederholen
|
||||
Repeat {name} {name} wiederholen
|
||||
Replace Ersetzen
|
||||
Replace: Ersetzen:
|
||||
Replace Font Schrift ersetzen
|
||||
Replace it with another file, or ignore it: its images keep showing the preview saved with the document. Ersetzen Sie sie durch eine andere Datei oder ignorieren Sie sie: Ihre Bilder zeigen weiterhin die mit dem Dokument gespeicherte Vorschau.
|
||||
Replace Spine Achse ersetzen
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -1798,6 +1798,7 @@ font-technology Variable Μεταβλητές
|
||||
Rename Μετονομασία
|
||||
Repeat Επανάληψη
|
||||
Replace Αντικατάσταση
|
||||
Replace: Αντικατάσταση:
|
||||
Replace… Αντικατάσταση…
|
||||
Reselect Επαναφορά προηγούμενης επιλογής
|
||||
Reset Επαναφορά
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 164 and column 5.
|
@@ -2641,6 +2641,7 @@ font-technology Variable Variables
|
||||
Repeat Tab Repetir tabulación
|
||||
Repeat {name} Repetir {name}
|
||||
Replace Reemplazar
|
||||
Replace: Reemplazar:
|
||||
Replace Font Reemplazar fuente
|
||||
Replace it with another file, or ignore it: its images keep showing the preview saved with the document. Reemplázalo con otro archivo o ignóralo: sus imágenes seguirán mostrando la vista previa guardada con el documento.
|
||||
Replace Spine Reemplazar eje
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -3720,6 +3720,7 @@ axis Both Molemmat
|
||||
Set Text Aseta teksti
|
||||
Insert Character Lisää merkki
|
||||
Replace Korvaa
|
||||
Replace: Korvaa:
|
||||
Type on a Path Options Polulla olevan tekstin asetukset
|
||||
Highlight Variable Korosta muuttuja
|
||||
New Variable Uusi muuttuja
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -2638,6 +2638,7 @@ font-technology Variable Variables
|
||||
Repeat Tab Répéter la tabulation
|
||||
Repeat {name} Répéter {name}
|
||||
Replace Remplacer
|
||||
Replace: Remplacer :
|
||||
Replace Font Remplacer la police
|
||||
Replace it with another file, or ignore it: its images keep showing the preview saved with the document. Remplacez-le par un autre fichier ou ignorez-le : ses images continueront d’afficher l’aperçu enregistré avec le document.
|
||||
Replace Spine Remplacer la dorsale
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -3169,6 +3169,7 @@ mark-style Japanese Japán
|
||||
Repeat Tab Tabulátor ismétlése
|
||||
Repeat {name} Ismétlés: {name}
|
||||
Replace Csere
|
||||
Replace: Csere:
|
||||
Replace Font Betűtípus cseréje
|
||||
Replace it with another file, or ignore it: its images keep showing the preview saved with the document. Cseréld le egy másik fájlra, vagy hagyd ki: a képei a dokumentummal mentett előnézetet mutatják tovább.
|
||||
Replace Spine Gerinc cseréje
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -3537,6 +3537,7 @@ axis Both Keduanya
|
||||
Set Text Atur teks
|
||||
Insert Character Sisipkan karakter
|
||||
Replace Ganti
|
||||
Replace: Ganti:
|
||||
Type on a Path Options Opsi teks pada jalur
|
||||
Highlight Variable Sorot variabel
|
||||
New Variable Variabel baru
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2169 and column 5.
|
@@ -2672,6 +2672,7 @@ mark-style Japanese Giapponese
|
||||
Repeat Tab Ripeti tabulazione
|
||||
Repeat {name} Ripeti {name}
|
||||
Replace Sostituisci
|
||||
Replace: Sostituisci:
|
||||
Replace Font Sostituisci font
|
||||
Replace it with another file, or ignore it: its images keep showing the preview saved with the document. Sostituiscilo con un altro file oppure ignoralo: le sue immagini continueranno a mostrare l'anteprima salvata con il documento.
|
||||
Replace Spine Sostituisci dorsale
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -2652,6 +2652,7 @@ mark-style Japanese 日本式
|
||||
Repeat Tab タブを繰り返し
|
||||
Repeat {name} {name}を繰り返す
|
||||
Replace 置換
|
||||
Replace: 置換:
|
||||
Replace Font フォントを置換
|
||||
Replace Symbol シンボルを置き換え
|
||||
Replace With Font 置換するフォント
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -3322,6 +3322,7 @@ axis Both ორივე
|
||||
Set Text ტექსტის დაყენება
|
||||
Insert Character სიმბოლოს ჩასმა
|
||||
Replace ჩანაცვლება
|
||||
Replace: ჩანაცვლება:
|
||||
Type on a Path Options ტრაექტორიაზე ტექსტის პარამეტრები
|
||||
Highlight Variable ცვლადის გამოკვეთა
|
||||
New Variable ახალი ცვლადი
|
||||
|
||||
|
Can't render this file because it is too large.
|
@@ -2639,6 +2639,7 @@ mark-style Japanese 일본식
|
||||
Repeat Tab 탭 반복
|
||||
Repeat {name} {name} 반복
|
||||
Replace 바꾸기
|
||||
Replace: 바꾸기:
|
||||
Replace Font 글꼴 바꾸기
|
||||
Replace Symbol 심볼 바꾸기
|
||||
Replace With Font 바꿀 글꼴
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -3551,6 +3551,7 @@ axis Both Beide
|
||||
Set Text Tekst instellen
|
||||
Insert Character Teken invoegen
|
||||
Replace Vervangen
|
||||
Replace: Vervangen:
|
||||
Type on a Path Options Opties voor tekst op pad
|
||||
Highlight Variable Variabele markeren
|
||||
New Variable Nieuwe variabele
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -3642,6 +3642,7 @@ axis Both Obie
|
||||
Set Text Ustaw tekst
|
||||
Insert Character Wstaw znak
|
||||
Replace Zastąp
|
||||
Replace: Zastąp:
|
||||
Type on a Path Options Opcje tekstu na ścieżce
|
||||
Highlight Variable Wyróżnij zmienną
|
||||
New Variable Nowa zmienna
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -2179,6 +2179,7 @@
|
||||
Repeat Tab Repetir Parada de Tabulação
|
||||
Repeat {name} Repetir {name}
|
||||
Replace Substituir
|
||||
Replace: Substituir:
|
||||
Replace Font Substituir Fonte
|
||||
Replace Spine Substituir Coluna
|
||||
Replace Symbol Substituir Símbolo
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
@@ -2668,6 +2668,7 @@ mark-style Japanese Японский
|
||||
Repeat Tab Повторить табуляцию
|
||||
Repeat {name} Повторить {name}
|
||||
Replace Заменить
|
||||
Replace: Заменить:
|
||||
Replace Font Заменить шрифт
|
||||
Replace it with another file, or ignore it: its images keep showing the preview saved with the document. Замените его другим файлом или игнорируйте: его изображения продолжат показывать предварительный просмотр, сохранённый с документом.
|
||||
Replace Spine Заменить остов
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -2583,6 +2583,7 @@ font-technology Variable Variabla
|
||||
Repeat Tab Upprepa tabb
|
||||
Repeat {name} Upprepa {name}
|
||||
Replace Ersätt
|
||||
Replace: Ersätt:
|
||||
Replace Font Ersätt typsnitt
|
||||
Replace it with another file, or ignore it: its images keep showing the preview saved with the document. Ersätt den med en annan fil eller ignorera den: dess bilder fortsätter att visa förhandsgranskningen som sparats med dokumentet.
|
||||
Replace Spine Ersätt ryggrad
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 5 and column 25.
|
@@ -2460,6 +2460,7 @@ shape Open Açık
|
||||
Repeat Tab Sekmeyi Tekrarla
|
||||
Repeat {name} {name}'yi tekrarla
|
||||
Replace Değiştir
|
||||
Replace: Değiştir:
|
||||
Replace Font Yazı Tipini Değiştir
|
||||
Replace it with another file, or ignore it: its images keep showing the preview saved with the document. Onu başka bir dosyayla değiştirin veya yok sayın: resimleri, belgeyle birlikte kaydedilen önizlemeyi göstermeye devam eder.
|
||||
Replace Spine Omurgayı Değiştir
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 5 and column 25.
|
@@ -2664,6 +2664,7 @@ mark-style Japanese Японський
|
||||
Repeat Tab Повторити табуляцію
|
||||
Repeat {name} Повторити {name}
|
||||
Replace Замінити
|
||||
Replace: Замінити:
|
||||
Replace Font Замінити шрифт
|
||||
Replace it with another file, or ignore it: its images keep showing the preview saved with the document. Замініть його іншим файлом або ігноруйте: зображення продовжать показувати попередній перегляд, збережений з документом.
|
||||
Replace Spine Замінити вісь
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 178 and column 5.
|
@@ -2935,6 +2935,7 @@ mark-style Japanese Kiểu Nhật
|
||||
Repeat Tab Tab lặp lại
|
||||
Repeat {name} Lặp lại {name}
|
||||
Replace Thay thế
|
||||
Replace: Thay thế:
|
||||
Replace Font Thay thế phông chữ
|
||||
Replace it with another file, or ignore it: its images keep showing the preview saved with the document. Thay thế bằng tệp khác hoặc bỏ qua: ảnh của nó vẫn hiển thị bản xem trước đã lưu cùng tài liệu.
|
||||
Replace Spine Thay thế trục
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 200 and column 5.
|
@@ -2541,6 +2541,7 @@ font-script Japanese 汉字
|
||||
Repeat Tab 重复制表位
|
||||
Repeat {name} 重复 {name}
|
||||
Replace 替换
|
||||
Replace: 替换:
|
||||
Replace Font 替换字体
|
||||
Replace it with another file, or ignore it: its images keep showing the preview saved with the document. 将其替换为其他文件,或忽略:其图像继续显示随文档保存的预览。
|
||||
Replace Spine 替换脊线
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -3478,6 +3478,7 @@ font-script Japanese 漢字
|
||||
Repeat Tab 重複定位點
|
||||
Repeat {name} 重複 {name}
|
||||
Replace 取代
|
||||
Replace: 取代:
|
||||
Replace Font 取代字體
|
||||
Replace it with another file, or ignore it: its images keep showing the preview saved with the document. 以其他檔案取代,或忽略:其影像會繼續顯示隨文件儲存的預覽。
|
||||
Replace Spine 取代脊線
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
@@ -149,6 +149,41 @@ fn has_instances_selected(app: &VectorcraftApp) -> bool {
|
||||
})
|
||||
}
|
||||
|
||||
/// The symbols of the selected objects when every one is a symbol instance; `None` otherwise.
|
||||
fn selected_instance_symbols(app: &VectorcraftApp) -> Option<Vec<String>> {
|
||||
let st = app.session.active()?;
|
||||
let symbols: Option<Vec<String>> = st
|
||||
.selection
|
||||
.objects
|
||||
.iter()
|
||||
.map(|id| match st.doc.node(*id).map(|n| &n.kind) {
|
||||
Some(NodeKind::SymbolInstance { symbol, .. }) => Some(symbol.clone()),
|
||||
_ => None,
|
||||
})
|
||||
.collect();
|
||||
symbols.filter(|s| !s.is_empty())
|
||||
}
|
||||
|
||||
/// The Control bar's Replace menu while symbol instances alone are selected: the document's
|
||||
/// symbols, showing the one the instances share (blank when they differ); picking another
|
||||
/// replaces them all, as "Select a new symbol from the Replace dropdown in the Control panel"
|
||||
/// (<https://helpx.adobe.com/illustrator/desktop/manage-objects/traces-mockups-symbols/edit-symbols.html>).
|
||||
pub(crate) fn control_bar(app: &mut VectorcraftApp, ui: &mut Ui) {
|
||||
let Some(symbols) = selected_instance_symbols(app) else { return };
|
||||
let Some(st) = app.session.active() else { return };
|
||||
let names: Vec<String> = st.doc.symbols.iter().map(|s| s.name.clone()).collect();
|
||||
let shared = symbols.first().filter(|first| symbols.iter().all(|s| s == *first)).cloned().unwrap_or_default();
|
||||
ui.label(egui::RichText::new(tl!("Replace:")).size(12.0).color(Tokens::get(ui.ctx()).text));
|
||||
let options: Vec<&str> = names.iter().map(String::as_str).collect();
|
||||
if let Some(name) = widgets::dropdown_names(ui, "cb-symbol-replace", &shared, &options, 140.0).and_then(|i| names.get(i))
|
||||
&& *name != shared
|
||||
&& let Err(e) = app.run("symbol.replace", json!({ "name": name }))
|
||||
{
|
||||
app.status(e);
|
||||
}
|
||||
ui.add_space(4.0);
|
||||
}
|
||||
|
||||
pub fn show(app: &mut VectorcraftApp, ui: &mut Ui) {
|
||||
let t = Tokens::get(ui.ctx());
|
||||
let Some((doc, keys)) = app.session.active().map(|d| (d.doc.clone(), document_symbols(d))) else { return };
|
||||
@@ -702,4 +737,65 @@ mod tests {
|
||||
};
|
||||
assert!(orange(3, 3) && orange(36, 36), "{:?} {:?}", thumb.pixels[3 * 40 + 3], thumb.pixels[36 * 40 + 36]);
|
||||
}
|
||||
|
||||
/// #4121: with symbol instances selected, the Control bar's Replace menu lists the document's
|
||||
/// symbols and shows the instance's own; picking another replaces it.
|
||||
#[test]
|
||||
fn the_control_bar_replace_menu_swaps_the_selected_instances_symbol() {
|
||||
fn bar(app: &mut VectorcraftApp, ctx: &egui::Context, events: Vec<egui::Event>) -> Vec<egui::Shape> {
|
||||
let raw = egui::RawInput { screen_rect: Some(Rect::from_min_size(Pos2::ZERO, vec2(800.0, 400.0))), events, ..Default::default() };
|
||||
let mut out = ctx.run_ui(raw, |ui| {
|
||||
ui.horizontal(|ui| control_bar(app, ui));
|
||||
});
|
||||
out.textures_delta.clear();
|
||||
out.shapes.into_iter().map(|c| c.shape).collect()
|
||||
}
|
||||
fn text_at(shapes: &[egui::Shape], s: &str) -> Option<Pos2> {
|
||||
shapes.iter().find_map(|sh| match sh {
|
||||
egui::Shape::Text(t) if t.galley.text().trim() == s => Some(t.visual_bounding_rect().center()),
|
||||
egui::Shape::Vec(v) => text_at(v, s),
|
||||
_ => None,
|
||||
})
|
||||
}
|
||||
fn click(app: &mut VectorcraftApp, ctx: &egui::Context, at: Pos2) {
|
||||
bar(app, ctx, vec![egui::Event::PointerMoved(at), button(at, true)]);
|
||||
bar(app, ctx, vec![button(at, false)]);
|
||||
}
|
||||
let mut app = VectorcraftApp::new(Session::new(), Default::default());
|
||||
app.run("file.new", json!({"width": 300, "height": 200})).unwrap();
|
||||
for (name, x) in [("Leaf", 10), ("Star", 150)] {
|
||||
app.run("shape.rectangle", json!({"x": x, "y": 10, "width": 30, "height": 30})).unwrap();
|
||||
app.run("symbol.new", json!({"name": name})).unwrap();
|
||||
}
|
||||
let instance_of = |app: &VectorcraftApp, name: &str| {
|
||||
let mut found = None;
|
||||
for l in &app.session.active().unwrap().doc.layers {
|
||||
l.walk(&mut |n| {
|
||||
if let NodeKind::SymbolInstance { symbol, .. } = &n.kind
|
||||
&& symbol == name
|
||||
{
|
||||
found = Some(n.id);
|
||||
}
|
||||
});
|
||||
}
|
||||
found
|
||||
};
|
||||
let leaf = instance_of(&app, "Leaf").expect("a Leaf instance");
|
||||
let ctx = egui::Context::default();
|
||||
crate::theme::install_fonts(&ctx);
|
||||
// Art, not an instance: no Replace menu.
|
||||
let rect = app.run("shape.rectangle", json!({"x": 10, "y": 100, "width": 30, "height": 30})).unwrap()["id"].as_u64().unwrap();
|
||||
app.run("select.set", json!({"ids": [rect]})).unwrap();
|
||||
assert!(text_at(&bar(&mut app, &ctx, vec![]), "Replace:").is_none());
|
||||
app.run("select.set", json!({"ids": [leaf.0]})).unwrap();
|
||||
bar(&mut app, &ctx, vec![]);
|
||||
let shapes = bar(&mut app, &ctx, vec![]);
|
||||
assert!(text_at(&shapes, "Replace:").is_some());
|
||||
let shown = text_at(&shapes, "Leaf").expect("the menu shows the instance's symbol");
|
||||
click(&mut app, &ctx, shown);
|
||||
let star = text_at(&bar(&mut app, &ctx, vec![]), "Star").expect("the menu lists the other symbol");
|
||||
click(&mut app, &ctx, star);
|
||||
let doc = &app.session.active().unwrap().doc;
|
||||
assert!(matches!(&doc.node(leaf).unwrap().kind, NodeKind::SymbolInstance { symbol, .. } if symbol == "Star"), "the instance is replaced");
|
||||
}
|
||||
}
|
||||
|
||||
+2
-1
@@ -1,6 +1,6 @@
|
||||
# UI and interaction parity
|
||||
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-30 · **Change:** minor (Color Mixer popover: the Color panel's menu with its color modes (#4249); scores unchanged) · **Target:** Adobe Illustrator 2026 (30.x)
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-30 · **Change:** minor (Control bar: a Replace menu swaps the selected symbol instances' symbol (#4121); scores unchanged) · **Target:** Adobe Illustrator 2026 (30.x)
|
||||
|
||||
## Feature submission evidence
|
||||
|
||||
@@ -361,6 +361,7 @@ separate. Scores and hours unchanged. [Behavior and command details](development
|
||||
|
||||
| Date | Change | Summary |
|
||||
|---|---|---|
|
||||
| 2026-10-30 | minor | Control bar: a Replace menu swaps the selected symbol instances' symbol (#4121); scores and estimates unchanged |
|
||||
| 2026-10-30 | minor | Color Mixer popover: the Color panel's menu with its color modes (#4249); scores and estimates unchanged |
|
||||
| 2026-10-30 | minor | Object › Path › Average & Join… (`path.averageJoin`, Cmd+Alt+Shift+J) moves two open path ends to their midpoint and joins them there, the joined anchor a corner or a smooth point as its dialog's Points choice asks, in one undo step; one row done; scores and estimates unchanged. |
|
||||
| 2026-10-30 | minor | G12: Preferences audit (#394): 5 options are stored but not acted on (Open Documents As Tabs, Units › East Asian Type, Hyphenation › Default Language, Scratch Disks, Legacy vector clipboard), each waiting on a subsystem; Highlight Alternate Glyphs, Real-time Drawing and the Devices options act; scores and estimates unchanged |
|
||||
|
||||
Reference in New Issue
Block a user