Merge pull request #1566 from marcoarnulfo/fix/new-symbol-options-dialog

Symbols panel: New Symbol asks for Symbol Options before making the symbol
This commit is contained in:
BFlat 2026-10-13 09:31:54 +07:00 committed by GitHub
commit 0810a15f6e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
22 changed files with 134 additions and 12 deletions

View File

@ -1465,6 +1465,14 @@ impl Document {
pub fn new_graphic_style_name(&self) -> String {
(self.graphic_styles.len() + 1..).map(|i| format!("Graphic Style {i}")).find(|n| self.graphic_style(n).is_none()).unwrap_or_default()
}
/// `base`, or the first free "`base` N" (N from 2) when a symbol already has that name.
pub fn unique_symbol_name(&self, base: &str) -> String {
let taken = |n: &str| self.symbols.iter().any(|s| s.name == n);
if !taken(base) {
return base.to_string();
}
(2..).map(|i| format!("{base} {i}")).find(|n| !taken(n)).unwrap_or_else(|| base.to_string())
}
}
fn default_graphic_styles() -> Vec<GraphicStyle> {

View File

@ -526,14 +526,6 @@ fn normalize(mut art: Node) -> Result<(Node, (f64, f64), Point)> {
Ok((art, (w, h), c))
}
fn unique_symbol_name(d: &Document, base: &str) -> String {
let taken = |n: &str| d.symbols.iter().any(|s| s.name == n);
if !taken(base) {
return base.to_string();
}
(2..).map(|i| format!("{base} {i}")).find(|n| !taken(n)).unwrap_or_else(|| base.to_string())
}
fn instances_of(d: &Document, name: Option<&str>) -> Vec<NodeId> {
let mut v = vec![];
d.walk(|n| {
@ -587,7 +579,7 @@ fn symbol_new(s: &mut Session, p: &Value) -> Result<Value> {
let ids = roots(s, p)?;
let art = selection_art(s, p)?.ok_or_else(|| EngineError::Other("nothing selected".into()))?;
let (art, size, c) = normalize(art)?;
let name = unique_symbol_name(&s.doc()?.doc, str_param(p, "name").filter(|n| !n.trim().is_empty()).unwrap_or("New Symbol"));
let name = s.doc()?.doc.unique_symbol_name(str_param(p, "name").filter(|n| !n.trim().is_empty()).unwrap_or("New Symbol"));
let id = s.edit("New Symbol", |d, sel| {
let art = d.reid(&art);
d.symbols.push(Symbol { name: name.clone(), art: Arc::new(art) });
@ -764,7 +756,7 @@ fn symbol_duplicate(s: &mut Session, p: &Value) -> Result<Value> {
let name = name_param(p, C)?.to_string();
let d = &s.doc()?.doc;
let sym = d.symbols.iter().find(|x| x.name == name).cloned().ok_or_else(|| bad(C, format!("no symbol named `{name}`")))?;
let new = unique_symbol_name(d, str_param(p, "newName").unwrap_or(&format!("{name} copy")));
let new = d.unique_symbol_name(str_param(p, "newName").unwrap_or(&format!("{name} copy")));
let size = natural_size(d, &name);
s.edit("Duplicate Symbol", |d, _| {
let art = d.reid(&sym.art);

View File

@ -84,6 +84,7 @@ pub mod spot_colors;
pub(crate) mod svg_options;
pub mod swatch_conflict;
pub mod swatch_options;
pub mod symbol_options;
pub mod symbolism_options;
mod text_export;
pub mod text_import;
@ -290,6 +291,7 @@ registry! {
LiquifyOptions: [liquify::KIND] => liquify::SPEC,
FreehandOptions: [freehand::KIND] => freehand::SPEC,
FlareOptions: [flare_options::KIND] => flare_options::SPEC,
SymbolOptions: [symbol_options::KIND] => symbol_options::SPEC,
SymbolismOptions: [symbolism_options::KIND] => symbolism_options::SPEC,
PerspectiveGridPresets: [perspective_presets::KIND] => perspective_presets::SPEC,
PerspectiveGridOptions: [perspective_options::KIND] => perspective_options::SPEC,

View File

@ -0,0 +1,45 @@
//! Symbol Options: the name of the symbol New Symbol makes from the selection (`symbol.new`), asked
//! before the symbol is made, as the Symbols panel's New Symbol button and menu item do.
//!
//! Fields: `name`.
use serde_json::{Value, json};
use super::swatch_options::{grid, label};
use super::{DialogSpec, form};
use crate::VectorcraftApp;
use crate::state::Dialog;
/// The dialog kind of Symbol Options.
pub const KIND: &str = "symbolOptions";
pub(super) const SPEC: DialogSpec = DialogSpec { heading: |_| tl!("Symbol Options").into(), body, confirm, min_width: 320.0, ..DialogSpec::FORM };
/// Open Symbol Options to name a new symbol made from the selection.
pub fn open(app: &mut VectorcraftApp) -> Result<Value, String> {
let st = app.session.active().ok_or("no document open")?;
if st.selection.is_empty() {
return Err("nothing selected".into());
}
let name = st.doc.unique_symbol_name("New Symbol");
app.ui.dialog = Some(Dialog::new(KIND, json!({ "name": name })));
Ok(Value::Null)
}
fn body(_: &mut VectorcraftApp, ui: &mut egui::Ui, d: &mut Dialog) -> bool {
grid(ui, |ui| {
label(ui, tl!("Name:"));
form::text(ui, d, "name", 190.0);
ui.end_row();
});
false
}
/// OK: make the symbol from the selection with the name typed.
fn confirm(app: &mut VectorcraftApp, d: &Dialog) -> Result<Value, String> {
let r = app.run("symbol.new", json!({ "name": d.str("name") }));
if r.is_ok() {
app.ui.dialog = None;
}
r
}

View File

@ -808,6 +808,8 @@ lighting Light Světlo
File Info Dialog Dialogové okno Informace o souboru
Go to Artboard Přejít na pracovní plochu
Graphic Style Options… Možnosti grafického stylu…
Symbol Options Možnosti symbolu
Symbol Options… Možnosti symbolu…
Home Domů
Import Keyboard Shortcuts… Importovat klávesové zkratky…
Justify all lines Do bloku včetně posledního řádku

Can't render this file because it contains an unexpected character in line 2 and column 73.

View File

@ -1128,6 +1128,8 @@
Graphic Style Library Grafikstil-Bibliothek
Graphic Style Options Grafikstiloptionen
Graphic Style Options… Grafikstiloptionen…
Symbol Options Symboloptionen
Symbol Options… Symboloptionen…
Graphic Styles Grafikstile
Graphic Styles Options Optionen für Grafikstile
Graphics Grafik

Can't render this file because it contains an unexpected character in line 2 and column 73.

View File

@ -1145,6 +1145,8 @@
Graphic Style Library Biblioteca de estilos gráficos
Graphic Style Options Opciones de estilo gráfico
Graphic Style Options… Opciones de estilo gráfico…
Symbol Options Opciones de símbolo
Symbol Options… Opciones de símbolo…
Graphic Styles Estilos gráficos
Graphic Styles Options Opciones de estilos gráficos
Graphics Gráficos

Can't render this file because it contains an unexpected character in line 2 and column 73.

View File

@ -1144,6 +1144,8 @@
Graphic Style Library Bibliothèque de styles graphiques
Graphic Style Options Options de style graphique
Graphic Style Options… Options de style graphique…
Symbol Options Options de symbole
Symbol Options… Options de symbole…
Graphic Styles Styles graphiques
Graphic Styles Options Options des styles graphiques
Graphics Graphiques

Can't render this file because it contains an unexpected character in line 2 and column 73.

View File

@ -1470,6 +1470,8 @@
Graphic Style Library Grafikaistílus-könyvtár
Graphic Style Options Grafikai stílus beállításai
Graphic Style Options… Grafikai stílus beállításai…
Symbol Options Szimbólum beállításai
Symbol Options… Szimbólum beállításai…
Graphic Styles Grafikai stílusok
Graphic Styles (4) Grafikai stílusok (4)
Graphic Styles Options Grafikai stílusok beállításai

Can't render this file because it contains an unexpected character in line 2 and column 73.

View File

@ -1144,6 +1144,8 @@
Graphic Style Library Libreria stili grafici
Graphic Style Options Opzioni stile grafico
Graphic Style Options… Opzioni stile grafico…
Symbol Options Opzioni simbolo
Symbol Options… Opzioni simbolo…
Graphic Styles Stili grafici
Graphic Styles Options Opzioni stili grafici
Graphics Grafica

Can't render this file because it contains an unexpected character in line 2 and column 73.

View File

@ -1422,6 +1422,8 @@
Graphic Style Library グラフィックスタイルライブラリ
Graphic Style Options グラフィックスタイルオプション
Graphic Style Options… グラフィックスタイルオプション…
Symbol Options シンボルオプション
Symbol Options… シンボルオプション…
Graphic Styles Options グラフィックスタイルオプション
Graphics Processor グラフィックプロセッサー
Graphics: {adapter} グラフィックス: {adapter}

Can't render this file because it contains an unexpected character in line 2 and column 73.

View File

@ -1417,6 +1417,8 @@
Graphic Style Library 그래픽 스타일 라이브러리
Graphic Style Options 그래픽 스타일 옵션
Graphic Style Options… 그래픽 스타일 옵션…
Symbol Options 심볼 옵션
Symbol Options… 심볼 옵션…
Graphic Styles Options 그래픽 스타일 옵션
Graphics Processor 그래픽 프로세서
Graphics: {adapter} 그래픽: {adapter}

Can't render this file because it contains an unexpected character in line 2 and column 73.

View File

@ -782,6 +782,8 @@ lighting Light Światło
File Info Dialog Okno informacji o pliku
Go to Artboard Przejdź do obszaru kompozycji
Graphic Style Options… Opcje stylu graficznego…
Symbol Options Opcje symbolu
Symbol Options… Opcje symbolu…
Home Strona startowa
Import Keyboard Shortcuts… Importuj skróty klawiaturowe…
Justify all lines Justuj wszystkie wiersze

Can't render this file because it contains an unexpected character in line 2 and column 73.

View File

@ -983,6 +983,8 @@
Graphic Style Library Biblioteca de Estilos Gráficos
Graphic Style Options Opções de Estilo Gráfico
Graphic Style Options… Opções de Estilo Gráfico…
Symbol Options Opções de Símbolo
Symbol Options… Opções de Símbolo…
Graphic Styles Estilos Gráficos
Graphic Styles Options Opções de Estilos Gráficos
Graphics Processor Processador Gráfico

Can't render this file because it contains an unexpected character in line 6 and column 47.

View File

@ -1142,6 +1142,8 @@
Graphic Style Library Библиотека графических стилей
Graphic Style Options Параметры графического стиля
Graphic Style Options… Параметры графического стиля…
Symbol Options Параметры символа
Symbol Options… Параметры символа…
Graphic Styles Графические стили
Graphic Styles Options Параметры графических стилей
Graphics Графика

Can't render this file because it contains an unexpected character in line 2 and column 73.

View File

@ -1138,6 +1138,8 @@
Graphic Style Library Бібліотека графічних стилів
Graphic Style Options Параметри графічного стилю
Graphic Style Options… Параметри графічного стилю…
Symbol Options Параметри символу
Symbol Options… Параметри символу…
Graphic Styles Графічні стилі
Graphic Styles Options Параметри графічних стилів
Graphics Графіка

Can't render this file because it has a wrong number of fields in line 13.

View File

@ -1323,6 +1323,8 @@
Graphic Style Library Thư viện kiểu đồ họa
Graphic Style Options Tùy chọn kiểu đồ họa
Graphic Style Options… Tùy chọn kiểu đồ họa…
Symbol Options Tùy chọn ký hiệu
Symbol Options… Tùy chọn ký hiệu…
Graphic Styles Kiểu đồ họa
Graphic Styles Options Tùy chọn kiểu đồ họa
Graphics Đồ họa

Can't render this file because it has a wrong number of fields in line 11.

View File

@ -1095,6 +1095,8 @@
Graphic Style Library 图形样式库
Graphic Style Options 图形样式选项
Graphic Style Options… 图形样式选项…
Symbol Options 符号选项
Symbol Options… 符号选项…
Graphic Styles 图形样式
Graphic Styles Options 图形样式选项
Graphics 图形

Can't render this file because it contains an unexpected character in line 2 and column 73.

View File

@ -1119,6 +1119,8 @@
Graphic Style Library 繪圖樣式資料庫
Graphic Style Options 繪圖樣式選項
Graphic Style Options… 繪圖樣式選項…
Symbol Options 符號選項
Symbol Options… 符號選項…
Graphic Styles 繪圖樣式
Graphic Styles Options 繪圖樣式選項
Graphics 圖形

Can't render this file because it contains an unexpected character in line 6 and column 47.

View File

@ -403,6 +403,12 @@ pub const UI_COMMANDS: &[(&str, &str, &str, &str)] = &[
"",
"{name?} open Graphic Style Options (dialog `graphicStyleOptions`, field `name`): for style `name` OK renames it (graphicStyle.rename); without, OK makes a new style of that name from the selection (graphicStyle.new)",
),
(
"ui.symbolOptions",
"Symbol Options…",
"",
"{} open Symbol Options for a new symbol made from the selection, what the Symbols panel's New Symbol opens (dialog `symbolOptions`, field `name`, default the first free \"New Symbol\"/\"New Symbol N\"); OK runs symbol.new {name}",
),
(
"tool.options",
"Tool Options…",
@ -1290,6 +1296,7 @@ pub fn run_ui_command(app: &mut VectorcraftApp, id: &str, p: &Value) -> Option<R
}
"ui.colorPicker" => crate::dialogs::open_color_picker(app, p),
"ui.graphicStyleOptions" => crate::dialogs::graphic_style_options::open(app, s("name").as_deref()),
"ui.symbolOptions" => crate::dialogs::symbol_options::open(app),
"ui.brushOptions" => crate::dialogs::brush_options::open(app, s("name").as_deref()),
"tool.options" => crate::toolbar::open_options(app, &s("tool").unwrap_or_default()),
"ui.colorGuideOptions" => {

View File

@ -155,7 +155,9 @@ pub fn show(app: &mut VectorcraftApp, ui: &mut Ui) {
}
ui.add_space((ui.available_width() - 2.0 * 28.0).max(0.0));
if widgets::icon_button_enabled(ui, "dc-new-item", tl!("New Symbol"), false, has_sel, 24.0).clicked() {
app.run("symbol.new", json!({})).ok();
// Alt skips Symbol Options: the symbol is made under the default name.
let cmd = if ui.input(|i| i.modifiers.alt) { "symbol.new" } else { "ui.symbolOptions" };
app.run(cmd, json!({})).ok();
}
if widgets::icon_button_enabled(ui, "trash-2", tl!("Delete Symbol"), false, sel.is_some(), 24.0).clicked()
&& let Some(n) = &sel
@ -171,7 +173,7 @@ pub fn menu(app: &mut VectorcraftApp, ui: &mut Ui) {
let inst = has_instances_selected(app);
let name = json!({"name": sel});
let items: [(&str, bool, &str, Value); 9] = [
(tl!("New Symbol…"), has_sel, "symbol.new", json!({})),
(tl!("New Symbol…"), has_sel, "ui.symbolOptions", json!({})),
(tl!("Redefine Symbol"), has_sel && sel.is_some(), "symbol.update", name.clone()),
(tl!("Duplicate Symbol"), sel.is_some(), "symbol.duplicate", name.clone()),
(tl!("Delete Symbol"), sel.is_some(), "symbol.delete", name.clone()),
@ -245,4 +247,37 @@ mod tests {
frame(&ctx, &mut app, vec![egui::Event::PointerMoved(tile + vec2(80.0, 80.0))]);
assert_eq!(egui::DragAndDrop::payload::<PanelDrag>(&ctx).as_deref(), Some(&PanelDrag::Symbol(names[0].clone())));
}
/// One headless frame of the panel menu; returns its painted labels.
fn menu_frame(ctx: &egui::Context, app: &mut VectorcraftApp, events: Vec<egui::Event>) -> Vec<(String, Rect)> {
let raw = egui::RawInput { screen_rect: Some(Rect::from_min_size(Pos2::ZERO, vec2(236.0, 400.0))), events, ..Default::default() };
let mut out = ctx.run_ui(raw, |ui| menu(app, ui));
out.textures_delta.clear();
crate::tests_removeanchors::shapes_text(&out.shapes.iter().map(|c| c.shape.clone()).collect::<Vec<_>>())
}
#[test]
fn new_symbol_asks_for_symbol_options_before_making_the_symbol() {
let mut app = VectorcraftApp::new(Session::new(), Default::default());
app.run("file.new", json!({"width": 200, "height": 100})).unwrap();
app.run("shape.rectangle", json!({"x": 10, "y": 10, "width": 30, "height": 20})).unwrap();
let ctx = egui::Context::default();
crate::theme::install_fonts(&ctx);
let labels = menu_frame(&ctx, &mut app, vec![]);
let item = labels.iter().find(|(t, _)| t.trim() == "New Symbol…").map(|(_, r)| r.center()).expect("New Symbol… in the menu");
menu_frame(&ctx, &mut app, vec![egui::Event::PointerMoved(item), button(item, true)]);
menu_frame(&ctx, &mut app, vec![button(item, false)]);
assert_eq!(app.ui.dialog.as_ref().map(|d| d.kind.as_str()), Some("symbolOptions"));
assert!(symbols(&mut app).is_empty(), "no symbol before OK");
assert_eq!(app.ui.dialog.as_ref().unwrap().str("name"), "New Symbol");
crate::dialogs::confirm(&mut app).unwrap();
assert!(app.ui.dialog.is_none());
// The next one is offered a name no symbol has, and OK keeps the name typed.
app.run("shape.rectangle", json!({"x": 60, "y": 10, "width": 30, "height": 20})).unwrap();
app.run("ui.symbolOptions", json!({})).unwrap();
assert_eq!(app.ui.dialog.as_ref().unwrap().str("name"), "New Symbol 2");
app.ui.dialog.as_mut().unwrap().fields.insert("name".into(), json!("Badge"));
crate::dialogs::confirm(&mut app).unwrap();
assert_eq!(symbols(&mut app), ["New Symbol", "Badge"]);
}
}

View File

@ -114,6 +114,9 @@ dialog open); without `name` it names a new style made from the selection (`grap
`ui.mergeGraphicStyles {names}` opens the same dialog to name the style `ui.dialog.confirm` merges from them
(`graphicStyle.merge`). Deleting styles from the panel asks first with a `confirm` dialog (`graphicStyle.delete`).
Symbols: `ui.symbolOptions` opens the `symbolOptions` dialog (field `name`) that the Symbols panel's New Symbol asks
first; `ui.dialog.confirm` makes the symbol from the selection with that name (`symbol.new`).
Gradient panel: double-clicking a stop on the panel's slider opens the same `gradientStop` popover, with a `screen`
field (`[x, y]`, screen points) in place of `x`/`y`. The panel's stop eyedropper selects the Eyedropper tool with the
tool option `stop` (the tool to return to; `tool.setOption {key: "stop", value: "gradient"}`): its next click on art