variables: Window › Variables opens the panel

As Window's other panel items do, and as the reference's Window menu has
it, Window › Variables is the panel itself instead of a submenu of
eighteen commands. The panel menu gains Make Text Dynamic, Make
Visibility Dynamic, Unbind Variable and Delete Variable beside the
bottom bar's buttons, which share one reading of what the selection
allows; every command stays in the command palette. The labels only
the submenu used leave the catalogs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
bflatastic 2026-10-10 22:34:24 +07:00
parent 2e07f887bf
commit d0015a496b
13 changed files with 37 additions and 93 deletions

View File

@ -535,16 +535,13 @@
Tabs Tabulátory
Variables Proměnné
New Variable… Nová proměnná…
Bind Variable… Přiřadit proměnnou…
Unbind Variable Zrušit přiřazení proměnné
Delete Variable Odstranit proměnnou
New Data Set… Nová sada dat…
Edit Data Set… Upravit sadu dat…
Delete Data Set Odstranit sadu dat
Select Data Set… Vybrat sadu dat…
Next Data Set Další sada dat
Previous Data Set Předchozí sada dat
Variables Panel Panel proměnných
Make Text Dynamic Nastavit text jako dynamický
Make Visibility Dynamic Nastavit viditelnost jako dynamickou
Capture Data Set Pořídit sadu dat

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

View File

@ -3968,11 +3968,8 @@ theme Appearance Erscheinungsbild
Previous Data Set Vorheriger Datensatz
Data Sets Datensätze
New Variable… Neue Variable…
Bind Variable… Variable binden…
New Data Set… Neuer Datensatz…
Edit Data Set… Datensatz bearbeiten…
Select Data Set… Datensatz auswählen…
Variables Panel Bedienfeld „Variablen“
Data Set Datensatz
Kind Art
Variable Variable

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

View File

@ -3069,11 +3069,8 @@
Previous Data Set Conjunto de datos anterior
Data Sets Conjuntos de datos
New Variable… Nueva variable…
Bind Variable… Vincular variable…
New Data Set… Nuevo conjunto de datos…
Edit Data Set… Editar conjunto de datos…
Select Data Set… Seleccionar conjunto de datos…
Variables Panel Panel de variables
Data Set Conjunto de datos
Kind Tipo
Variable Variable

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

View File

@ -3066,11 +3066,8 @@
Previous Data Set Jeu de données précédent
Data Sets Jeux de données
New Variable… Nouvelle variable…
Bind Variable… Lier la variable…
New Data Set… Nouveau jeu de données…
Edit Data Set… Modifier le jeu de données…
Select Data Set… Sélectionner le jeu de données…
Variables Panel Panneau des variables
Data Set Jeu de données
Kind Type
Variable Variable

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

View File

@ -3069,11 +3069,8 @@
Previous Data Set Set di dati precedente
Data Sets Set di dati
New Variable… Nuova variabile…
Bind Variable… Associa variabile…
New Data Set… Nuovo set di dati…
Edit Data Set… Modifica set di dati…
Select Data Set… Seleziona set di dati…
Variables Panel Pannello variabili
Data Set Set di dati
Kind Tipo
Variable Variabile

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

View File

@ -582,11 +582,8 @@
Previous Data Set 前のデータセット
Data Sets データセット
New Variable… 新規変数…
Bind Variable… 変数をバインド…
New Data Set… 新規データセット…
Edit Data Set… データセットを編集…
Select Data Set… データセットを選択…
Variables Panel 変数パネル
Data Set データセット
Kind 種類
Variable 変数

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

View File

@ -2531,16 +2531,13 @@
Variable Width Profile Perfil de Largura Variável
Variables Variáveis
New Variable… Nova variável…
Bind Variable… Associar variável…
Unbind Variable Desassociar variável
Delete Variable Excluir variável
New Data Set… Novo conjunto de dados…
Edit Data Set… Editar conjunto de dados…
Delete Data Set Excluir conjunto de dados
Select Data Set… Selecionar conjunto de dados…
Next Data Set Próximo conjunto de dados
Previous Data Set Conjunto de dados anterior
Variables Panel Painel de variáveis
Make Text Dynamic Tornar texto dinâmico
Make Visibility Dynamic Tornar visibilidade dinâmica
Capture Data Set Capturar conjunto de dados

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

View File

@ -3065,11 +3065,8 @@
Previous Data Set Предыдущий набор данных
Data Sets Наборы данных
New Variable… Новая переменная…
Bind Variable… Привязать переменную…
New Data Set… Новый набор данных…
Edit Data Set… Изменить набор данных…
Select Data Set… Выбрать набор данных…
Variables Panel Панель переменных
Data Set Набор данных
Kind Тип
Variable Переменная

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

View File

@ -3061,11 +3061,8 @@
Previous Data Set Попередній набір даних
Data Sets Набори даних
New Variable… Нова змінна…
Bind Variable… Прив’язати змінну…
New Data Set… Новий набір даних…
Edit Data Set… Змінити набір даних…
Select Data Set… Вибрати набір даних…
Variables Panel Панель змінних
Data Set Набір даних
Kind Тип
Variable Змінна

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

View File

@ -2960,11 +2960,8 @@
Previous Data Set 上一个数据集
Data Sets 数据集
New Variable… 新建变量…
Bind Variable… 绑定变量…
New Data Set… 新建数据集…
Edit Data Set… 编辑数据集…
Select Data Set… 选择数据集…
Variables Panel 变量面板
Data Set 数据集
Kind 类型
Variable 变量

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

View File

@ -3052,11 +3052,8 @@
Previous Data Set 上一個資料集
Data Sets 資料集
New Variable… 新增變數…
Bind Variable… 繫結變數…
New Data Set… 新增資料集…
Edit Data Set… 編輯資料集…
Select Data Set… 選取資料集…
Variables Panel 變數面板
Data Set 資料集
Kind 類型
Variable 變數

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

View File

@ -48,34 +48,6 @@ fn library_placeholders() -> Vec<Item> {
vec![todo("Built-in Libraries"), todo("User Defined"), Sep, todo("Other Library…")]
}
/// Window › Variables: the panel, then the commands that define and apply what it shows, in
/// the order the panel does them. The "…" commands open a Variables dialog
/// (`dialogs::variables`), so the menu and the command palette both reach them.
fn variables_window_menu(panel: impl Fn(&'static str, &'static str) -> Item) -> Vec<Item> {
vec![
panel("Variables Panel", crate::panels::variables::ID),
Sep,
c("Make Text Dynamic", "variable.makeTextDynamic"),
c("Make Visibility Dynamic", "variable.makeVisibilityDynamic"),
Sep,
c("New Variable…", "variable.define"),
c("Variable Options…", "variable.rename"),
c("Bind Variable…", "variable.bind"),
c("Unbind Variable", "variable.unbind"),
c("Delete Variable", "variable.delete"),
Sep,
c("Capture Data Set", "dataset.capture"),
c("New Data Set…", "dataset.new"),
c("Edit Data Set…", "dataset.set"),
c("Update Data Set", "dataset.update"),
c("Rename Data Set…", "dataset.rename"),
c("Delete Data Set", "dataset.delete"),
Sep,
c("Select Data Set…", "dataset.select"),
c("Next Data Set", "dataset.next"),
c("Previous Data Set", "dataset.prev"),
]
}
use Item::Sep;
/// UI-level commands: (id, label, shortcut, params doc).
@ -2577,7 +2549,7 @@ pub fn menu_tree_named(english_names: bool) -> Vec<(&'static str, Vec<Item>)> {
panel("Tabs", "tabs"),
],
),
sub("Variables", variables_window_menu(panel)),
panel("Variables", crate::panels::variables::ID),
Sep,
sub("Brush Libraries", library_placeholders()),
sub("Graphic Style Libraries", crate::panels::graphic_styles::window_menu()),

View File

@ -64,6 +64,23 @@ pub(crate) fn select_bound_object(app: &mut VectorcraftApp) {
}
}
/// What the selection allows the bottom bar's and the panel menu's commands to do.
struct Picked {
selected: Vec<u64>,
has_selection: bool,
/// Unbind needs a bound object under the selection, not just any selection.
selection_bound: bool,
/// Make Text Dynamic needs exactly one type object: a text variable only drives type.
single_type: bool,
}
fn picked(st: &vectorcraft_engine::DocState) -> Picked {
let selected: Vec<u64> = st.selection.objects.iter().map(|i| i.0).collect();
let selection_bound = selected.iter().any(|id| st.doc.variables.bindings.contains_key(&NodeId(*id)));
let single_type = matches!(selected.as_slice(), [one] if st.doc.node(NodeId(*one)).is_some_and(|n| matches!(n.kind, NodeKind::Text(_))));
Picked { has_selection: !selected.is_empty(), selected, selection_bound, single_type }
}
/// Delete Variable: the highlighted variable only (the command's default); the rest stay.
pub(crate) fn delete_highlighted(app: &mut VectorcraftApp) {
app.run("variable.delete", json!({})).ok();
@ -110,13 +127,8 @@ pub fn show(app: &mut VectorcraftApp, ui: &mut Ui) {
})
.collect();
let active = sets.iter().find(|s| s.active);
let selected: Vec<u64> = st.selection.objects.iter().map(|i| i.0).collect();
let has_selection = !selected.is_empty();
let Picked { selected, has_selection, selection_bound, single_type } = picked(st);
let highlighted = highlighted_row(st);
// Unbind needs a bound object under the selection, not just any selection.
let selection_bound = selected.iter().any(|id| st.doc.variables.bindings.contains_key(&NodeId(*id)));
// Make Text Dynamic needs exactly one type object: a text variable only drives type.
let single_type = matches!(selected.as_slice(), [one] if st.doc.node(NodeId(*one)).is_some_and(|n| matches!(n.kind, NodeKind::Text(_))));
// The current data set across the top: its name, and the arrows that step through them.
ui.horizontal(|ui| {
@ -226,6 +238,7 @@ pub fn menu(app: &mut VectorcraftApp, ui: &mut Ui) {
let bound: Vec<u64> = st.doc.variables.bindings.keys().map(|i| i.0).collect();
let highlighted = highlighted_row(st);
let highlighted_bound: Vec<u64> = highlighted.as_ref().map(|h| bound_objects(st, h)).unwrap_or_default();
let Picked { selected, has_selection, selection_bound, single_type } = picked(st);
if widgets::menu_item(ui, tl!("New Variable…"), true, false) {
crate::menus::invoke(app, "variable.define", json!({}));
@ -235,6 +248,18 @@ pub fn menu(app: &mut VectorcraftApp, ui: &mut Ui) {
let name = highlighted.clone().or_else(|| vars.first().cloned()).unwrap_or_default();
crate::dialogs::variables::open(app, "variable.rename", Some(&name));
}
if widgets::menu_item(ui, tl!("Make Text Dynamic"), single_type, false) {
app.run("variable.makeTextDynamic", json!({})).ok();
}
if widgets::menu_item(ui, tl!("Make Visibility Dynamic"), has_selection, false) {
app.run("variable.makeVisibilityDynamic", json!({})).ok();
}
if widgets::menu_item(ui, tl!("Unbind Variable"), selection_bound, false) {
app.run("variable.unbind", json!({"ids": selected})).ok();
}
if widgets::menu_item(ui, tl!("Delete Variable"), highlighted.is_some(), false) {
delete_highlighted(app);
}
ui.separator();
if widgets::menu_item(ui, tl!("Capture Data Set"), true, false) {
app.run("dataset.capture", json!({})).ok();
@ -405,38 +430,18 @@ mod tests {
}
}
/// Every Variables command is in the Window menu, so the feature is reachable without an
/// agent: the palette runs these with empty params, which the dialogs turn into a form.
/// Window › Variables opens the panel, as Window's other panel items do; its commands are in
/// the panel, its menu and the command palette.
#[test]
fn every_variables_command_is_in_the_window_menu() {
fn window_variables_opens_the_panel() {
let app = VectorcraftApp::new(vectorcraft_engine::Session::new(), Default::default());
let entries = crate::menus::menu_entries(&app);
let in_menu = |id: &str| entries.iter().any(|e| e.command.as_deref() == Some(id));
for id in [
"variable.define",
"variable.rename",
"variable.delete",
"variable.bind",
"variable.unbind",
"variable.makeTextDynamic",
"variable.makeVisibilityDynamic",
"dataset.new",
"dataset.set",
"dataset.capture",
"dataset.update",
"dataset.rename",
"dataset.delete",
"dataset.select",
"dataset.next",
"dataset.prev",
] {
assert!(in_menu(id), "{id} is not in the Window menu");
}
let panel = entries
.iter()
.find(|e| e.command.as_deref() == Some("window.panel") && e.params.get("panel").and_then(serde_json::Value::as_str) == Some(ID))
.expect("the Variables panel is listed");
assert_eq!(panel.path, vec!["Window".to_string(), "Variables".to_string()]);
assert_eq!((panel.path.clone(), panel.label.as_str()), (vec!["Window".to_string()], "Variables"));
assert!(!entries.iter().any(|e| e.path == ["Window", "Variables"]), "no Variables submenu");
}
/// A menu item that needs a name opens the dialog rather than running the command with