variables: review fixes for the panel, dialogs and translations

- Dataset and variable names are the user's: the panel's Data Set dropdown and
  the dialogs' name dropdowns show them as they are instead of translating
  them; the dialog's Kind dropdown shows translated kind names.
- Choosing the empty option in a dialog dropdown picked the first name.
- The panel reads the art once per frame for every row's italics, the dialog
  no longer clones the document's variables every frame, and slices are
  matched instead of indexed.
- "No document" and "Define a variable first" go through tl!, and the dialog
  headings Variable Options / Edit Data Set, Text and Visibility have rows in
  every complete catalog; the unused "{name} · {kind} · {count}" row is gone.
- German rows for every Variables string (the catalog predates this branch).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
bflatastic 2026-10-10 22:15:42 +07:00
parent b3fa281282
commit 500e19680d
11 changed files with 124 additions and 37 deletions

View File

@ -21,6 +21,11 @@ pub const KIND: &str = "variables";
/// The kinds a variable can have, as `variable.define` names them.
const KINDS: [&str; 2] = ["text", "visibility"];
/// What the dialog shows for each of [`KINDS`].
fn kind_labels() -> [&'static str; 2] {
[tl!("Text"), tl!("Visibility")]
}
pub(super) const SPEC: DialogSpec =
DialogSpec { heading: |d| tl!(&d.str("__label")).to_string(), body, confirm, min_width: 380.0, ..DialogSpec::FORM };
@ -96,7 +101,7 @@ pub fn open(app: &mut VectorcraftApp, id: &str, preselect: Option<&str>) -> bool
app.status(if key == "variable" { tl!("no variable to rename").to_string() } else { tl!("no data set to rename").to_string() });
return true;
}
fields.insert(key.into(), json!(preselect.filter(|p| names.contains(p)).unwrap_or(names[0])));
fields.insert(key.into(), json!(preselect.filter(|p| names.contains(p)).or(names.first().copied()).unwrap_or_default()));
fields.insert("newName".into(), json!(""));
}
}
@ -136,10 +141,10 @@ fn value_key(name: &str) -> String {
fn body(app: &mut VectorcraftApp, ui: &mut egui::Ui, d: &mut Dialog) -> bool {
let t = Tokens::get(ui.ctx());
let Some(st) = app.session.active() else {
widgets::dim_label(ui, "No document");
widgets::dim_label(ui, tl!("No document"));
return false;
};
let vars = st.doc.variables.clone();
let vars = &st.doc.variables;
let variable_names: Vec<&str> = vars.variables.iter().map(|v| v.name.as_str()).collect();
let dataset_names: Vec<&str> = vars.datasets.iter().map(|x| x.name.as_str()).collect();
egui::Grid::new("variables-grid").num_columns(2).spacing([12.0, 8.0]).show(ui, |ui| {
@ -150,7 +155,12 @@ fn body(app: &mut VectorcraftApp, ui: &mut egui::Ui, d: &mut Dialog) -> bool {
text(ui, d, "name", 220.0);
ui.end_row();
label(ui, tl!("Kind"));
dropdown(ui, d, "kind", &KINDS, 220.0);
// The kinds by their translated names; the field keeps the command's own word.
let labels = kind_labels();
let shown = KINDS.iter().position(|k| *k == d.str("kind")).and_then(|i| labels.get(i)).copied().unwrap_or_default();
if let Some(kind) = widgets::dropdown_names(ui, ("variables-dd", "kind"), shown, &labels, 220.0).and_then(|i| KINDS.get(i)) {
d.fields.insert("kind".into(), json!(kind));
}
ui.end_row();
}
"variable.bind" => {
@ -182,7 +192,7 @@ fn body(app: &mut VectorcraftApp, ui: &mut egui::Ui, d: &mut Dialog) -> bool {
text(ui, d, "name", 220.0);
ui.end_row();
if vars.variables.is_empty() {
widgets::dim_label(ui, "Define a variable first");
widgets::dim_label(ui, tl!("Define a variable first"));
ui.end_row();
return;
}
@ -223,16 +233,18 @@ fn checkbox(ui: &mut egui::Ui, d: &mut Dialog, key: &str) -> egui::Response {
r
}
/// A dropdown over `options` for `key`, written back as it changes. The empty option is first
/// so a fresh dialog can still be filled in; a name that is no longer offered reads as empty
/// rather than being silently swapped for another one.
/// A dropdown over the document's names `options` (shown as they are, never translated) for
/// `key`, written back as it changes. The empty option is first so a fresh dialog can still be
/// filled in; a name that is no longer offered reads as empty rather than being silently
/// swapped for another one.
fn dropdown(ui: &mut egui::Ui, d: &mut Dialog, key: &str, options: &[&str], width: f32) {
let cur = d.str(key);
let mut labels: Vec<&str> = vec![""];
labels.extend_from_slice(options);
let shown = if cur.is_empty() || options.contains(&cur.as_str()) { cur.as_str() } else { "" };
if let Some(i) = widgets::dropdown(ui, ("variables-dd", key), shown, &labels, width) {
d.fields.insert(key.into(), json!(options.get(i.saturating_sub(1)).copied().unwrap_or_default()));
if let Some(i) = widgets::dropdown_names(ui, ("variables-dd", key), shown, &labels, width) {
// The empty option (0) empties the field; the rest are `options`, one along.
d.fields.insert(key.into(), json!(i.checked_sub(1).and_then(|i| options.get(i)).copied().unwrap_or_default()));
}
}
@ -257,13 +269,15 @@ fn values_of(d: &Dialog, vars: &Variables) -> Value {
fn confirm(app: &mut VectorcraftApp, d: &Dialog) -> Result<Value, String> {
let cmd = d.str("__command");
let name = d.str("name");
let vars = app.session.active().map(|st| st.doc.variables.clone()).unwrap_or_default();
let p = match cmd.as_str() {
"variable.define" => json!({"name": name, "kind": d.str("kind")}),
"variable.bind" => json!({"variable": d.str("variable")}),
"variable.rename" => json!({"name": d.str("variable"), "newName": d.str("newName")}),
"dataset.rename" => json!({"name": name, "newName": d.str("newName")}),
"dataset.new" | "dataset.set" => json!({"name": name, "values": values_of(d, &vars)}),
"dataset.new" | "dataset.set" => {
let values = app.session.active().map(|st| values_of(d, &st.doc.variables)).unwrap_or_else(|| json!({}));
json!({"name": name, "values": values})
}
"dataset.select" => json!({"name": name}),
_ => json!({}),
};

View File

@ -3954,3 +3954,46 @@ theme Appearance Erscheinungsbild
Frosted Mattiert
Tiny Lens Kleine Linsen
Canvas Leinwand
Highlight Variable Variable hervorheben
New Variable Neue Variable
Delete Variable Variable löschen
List Variables Variablen auflisten
Bind Variable Variable binden
Unbind Variable Variable lösen
New Data Set Neuer Datensatz
Delete Data Set Datensatz löschen
List Data Sets Datensätze auflisten
Select Data Set Datensatz auswählen
Next Data Set Nächster Datensatz
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
no document kein Dokument
no data set to edit kein Datensatz zum Bearbeiten
Make Text Dynamic Text dynamisch machen
Make Visibility Dynamic Sichtbarkeit dynamisch machen
Capture Data Set Datensatz erfassen
Update Data Set Datensatz aktualisieren
Rename Data Set… Datensatz umbenennen…
Variable Options… Variablenoptionen…
Select Bound Object Gebundenes Objekt auswählen
Select All Bound Objects Alle gebundenen Objekte auswählen
Multiple objects Mehrere Objekte
New Name Neuer Name
no variable to rename keine Variable zum Umbenennen
no data set to rename kein Datensatz zum Umbenennen
Rename Data Set Datensatz umbenennen
Rename Variable Variable umbenennen
Variable Options Variablenoptionen
Edit Data Set Datensatz bearbeiten
Define a variable first Definieren Sie zuerst eine Variable
Text Text
Visibility Sichtbarkeit

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

View File

@ -3079,7 +3079,6 @@
Variable Variable
no document no hay documento
no data set to edit no hay ningún conjunto de datos que editar
{name} · {kind} · {count} {name} · {kind} · {count}
Make Text Dynamic Hacer texto dinámico
Make Visibility Dynamic Hacer visible dinámico
Capture Data Set Capturar conjunto de datos
@ -4036,3 +4035,8 @@ axis Both Ambos
Tiny Lens Lente pequeña
Rename Data Set Renombrar conjunto de datos
Rename Variable Renombrar variable
Variable Options Opciones de variable
Edit Data Set Editar conjunto de datos
Define a variable first Defina primero una variable
Text Texto
Visibility Visibilidad

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

View File

@ -3076,7 +3076,6 @@
Variable Variable
no document aucun document
no data set to edit aucun jeu de données à modifier
{name} · {kind} · {count} {name} · {kind} · {count}
Make Text Dynamic Rendre le texte dynamique
Make Visibility Dynamic Rendre la visibilité dynamique
Capture Data Set Capturer le jeu de données
@ -4048,3 +4047,8 @@ effect Soft Doux
Tiny Lens Petites lentilles
Rename Data Set Renommer le jeu de données
Rename Variable Renommer la variable
Variable Options Options de variable
Edit Data Set Modifier le jeu de données
Define a variable first Définissez d’abord une variable
Text Texte
Visibility Visibilité

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

View File

@ -3079,7 +3079,6 @@
Variable Variabile
no document nessun documento
no data set to edit nessun set di dati da modificare
{name} · {kind} · {count} {name} · {kind} · {count}
Make Text Dynamic Rendi testo dinamico
Make Visibility Dynamic Rendi visibilità dinamica
Capture Data Set Cattura set di dati
@ -4038,3 +4037,8 @@ effect Regular Normale
Tiny Lens Lente piccola
Rename Data Set Rinomina set di dati
Rename Variable Rinomina variabile
Variable Options Opzioni variabile
Edit Data Set Modifica set di dati
Define a variable first Definisci prima una variabile
Text Testo
Visibility Visibilità

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

View File

@ -592,7 +592,6 @@
Variable 変数
no document ドキュメントがありません
no data set to edit 編集するデータセットがありません
{name} · {kind} · {count} {name} · {kind} · {count} 件
Make Text Dynamic テキストを動的にする
Make Visibility Dynamic 表示を動的にする
Capture Data Set データセットを取得
@ -4031,3 +4030,8 @@ effect Vertical 垂直
Frosted すりガラス
Tiny Lens 小さなレンズ
Rename Data Set データセットの名前を変更
Variable Options 変数のオプション
Edit Data Set データセットを編集
Define a variable first 先に変数を定義してください
Text テキスト
Visibility 表示状態

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

View File

@ -3075,7 +3075,6 @@
Variable Переменная
no document нет документа
no data set to edit нет набора данных для изменения
{name} · {kind} · {count} {name} · {kind} · {count}
Make Text Dynamic Сделать текст динамическим
Make Visibility Dynamic Сделать видимость динамической
Capture Data Set Записать набор данных
@ -4034,3 +4033,8 @@ axis Both Оба
Tiny Lens Мелкие линзы
Rename Data Set Переименовать набор данных
Rename Variable Переименовать переменную
Variable Options Параметры переменной
Edit Data Set Изменить набор данных
Define a variable first Сначала определите переменную
Text Текст
Visibility Видимость

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

View File

@ -3071,7 +3071,6 @@
Variable Змінна
no document немає документа
no data set to edit немає набору даних для зміни
{name} · {kind} · {count} {name} · {kind} · {count}
Make Text Dynamic Зробити текст динамічним
Make Visibility Dynamic Зробити видимість динамічною
Capture Data Set Записати набір даних
@ -4019,3 +4018,8 @@ axis Both Обидві
Tiny Lens Дрібні лінзи
Rename Data Set Перейменувати набір даних
Rename Variable Перейменувати змінну
Variable Options Параметри змінної
Edit Data Set Змінити набір даних
Define a variable first Спочатку визначте змінну
Text Текст
Visibility Видимість

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

View File

@ -2970,7 +2970,6 @@
Variable 变量
no document 没有文档
no data set to edit 没有可编辑的数据集
{name} · {kind} · {count} {name} · {kind} · {count}
Make Text Dynamic 使文本动态化
Make Visibility Dynamic 使可见性动态化
Capture Data Set 捕获数据集
@ -3667,3 +3666,8 @@ effect Vertical 垂直
Tiny Lens 小镜头
Rename Data Set 重命名数据集
Rename Variable 重命名变量
Variable Options 变量选项
Edit Data Set 编辑数据集
Define a variable first 请先定义一个变量
Text 文本
Visibility 可见性

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

View File

@ -3062,7 +3062,6 @@
Variable 變數
no document 沒有文件
no data set to edit 沒有可編輯的資料集
{name} · {kind} · {count} {name} · {kind} · {count}
Make Text Dynamic 使文字動態化
Make Visibility Dynamic 使可見性動態化
Capture Data Set 擷取資料集
@ -3448,3 +3447,8 @@ effect Soft 柔和
Tiny Lens 小鏡頭
Rename Data Set 重新命名資料集
Rename Variable 重新命名變數
Variable Options 變數選項
Edit Data Set 編輯資料集
Define a variable first 請先定義一個變數
Text 文字
Visibility 可見性

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

View File

@ -64,18 +64,14 @@ pub(crate) fn select_bound_object(app: &mut VectorcraftApp) {
}
}
/// Delete Variable: the highlighted variable only; the rest of the rows stay.
/// Delete Variable: the highlighted variable only (the command's default); the rest stay.
pub(crate) fn delete_highlighted(app: &mut VectorcraftApp) {
if let Some(name) = app.session.active().and_then(highlighted_row) {
app.run("variable.delete", json!({"names": [name]})).ok();
}
app.run("variable.delete", json!({})).ok();
}
/// Delete Data Set: the current one only; the rest of the rows stay.
/// Delete Data Set: the current one only (the command's default); the rest of the rows stay.
pub(crate) fn delete_active_dataset(app: &mut VectorcraftApp) {
if let Some(name) = app.session.active().and_then(|st| st.doc.variables.active_dataset.clone()) {
app.run("dataset.delete", json!({"names": [name]})).ok();
}
app.run("dataset.delete", json!({})).ok();
}
pub fn show(app: &mut VectorcraftApp, ui: &mut Ui) {
@ -91,14 +87,16 @@ pub fn show(app: &mut VectorcraftApp, ui: &mut Ui) {
.iter()
.map(|v| {
let bound: Vec<u64> = st.doc.variables.objects_of(&v.name).iter().map(|i| i.0).collect();
let object = match bound.len() {
0 => String::new(),
1 => st.doc.node(NodeId(bound[0])).map(|n| n.display_name()).unwrap_or_default(),
let object = match bound.as_slice() {
[] => String::new(),
[one] => st.doc.node(NodeId(*one)).map(|n| n.display_name()).unwrap_or_default(),
_ => tl!("Multiple objects").to_string(),
};
VarRow { name: v.name.clone(), kind: v.kind, bound, object }
})
.collect();
// What the art holds now, read once for every row's drift check.
let current = st.doc.captured_values();
let sets: Vec<SetRow> = st
.doc
.variables
@ -108,7 +106,7 @@ pub fn show(app: &mut VectorcraftApp, ui: &mut Ui) {
name: d.name.clone(),
values: d.values.len(),
active: st.doc.variables.active_dataset.as_deref() == Some(d.name.as_str()),
matches: st.doc.matches_dataset(&d.name),
matches: d.matches(&current),
})
.collect();
let active = sets.iter().find(|s| s.active);
@ -118,18 +116,18 @@ pub fn show(app: &mut VectorcraftApp, ui: &mut Ui) {
// 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 = selected.len() == 1 && st.doc.node(NodeId(selected[0])).is_some_and(|n| matches!(n.kind, NodeKind::Text(_)));
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| {
widgets::field_label(ui, tl!("Data Set"));
let names: Vec<&str> = sets.iter().map(|s| s.name.as_str()).collect();
let current = active.map(|s| s.name.as_str()).unwrap_or("");
let picked = widgets::dropdown(ui, "variables-active", current, &names, 160.0);
if let Some(i) = picked
&& names.get(i).is_some_and(|n| *n != current)
let picked = widgets::dropdown_names(ui, "variables-active", current, &names, 160.0);
if let Some(name) = picked.and_then(|i| names.get(i))
&& *name != current
{
app.run("dataset.select", json!({"name": names[i]})).ok();
app.run("dataset.select", json!({"name": name})).ok();
}
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
let has = !sets.is_empty();