mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-15 21:57:04 +00:00
Merge pull request #7773 from dotCooCoo/feat/variables-panel-sort-columns
Variables: Variables, Objects and type columns sort the variable list
This commit is contained in:
@@ -899,6 +899,9 @@ pub use brushsym::reset_transformation_changes;
|
||||
/// Data window reads files by it too).
|
||||
pub use variablecmds::utf16_order_without_mark;
|
||||
|
||||
/// The Variables panel's sort (`variable.sort`) and its rows in that order.
|
||||
pub use variablecmds::{VariableSort, VariableSortKey, variable_order};
|
||||
|
||||
/// Command ids kept only so older scripts keep working: `(alias, the command it duplicates)`.
|
||||
/// They run as before but the command palette leaves them out (it lists the command once).
|
||||
pub const ALIASES: &[(&str, &str)] = &[("object.convertDocumentColorMode", "file.documentColorMode")];
|
||||
|
||||
@@ -966,7 +966,7 @@ fn an_ai_file_opens_its_variable_library() {
|
||||
{"name": "v_file", "kind": "image", "bindings": 0},
|
||||
{"name": "v_graph", "kind": "graph", "bindings": 0},
|
||||
{"name": "v_unbound", "kind": "unknown", "bindings": 0},
|
||||
], "locked": true})
|
||||
], "locked": true, "sort": {"by": "none", "descending": false}})
|
||||
);
|
||||
let r = s.execute("dataset.list", &json!({})).unwrap();
|
||||
assert_eq!(r["active"], "ds_two", "{r}");
|
||||
|
||||
@@ -694,6 +694,7 @@ pub(super) const ALLOWED: &[&str] = &[
|
||||
"variable.makeTextDynamic",
|
||||
"variable.makeVisibilityDynamic",
|
||||
"variable.rename",
|
||||
"variable.sort",
|
||||
"variable.unbind",
|
||||
];
|
||||
|
||||
|
||||
@@ -330,18 +330,19 @@ fn delete(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
}
|
||||
|
||||
fn list(s: &mut Session, _p: &Value) -> Result<Value> {
|
||||
let xml_id = s.prefs.identify_objects_by == "xmlId";
|
||||
let st = s.doc()?;
|
||||
let variables: Vec<Value> = st
|
||||
.doc
|
||||
.variables
|
||||
.variables
|
||||
.iter()
|
||||
let vars = &st.doc.variables.variables;
|
||||
let variables: Vec<Value> = variable_order(st, xml_id)
|
||||
.into_iter()
|
||||
.filter_map(|i| vars.get(i))
|
||||
.map(|v| {
|
||||
let bindings = st.doc.variables.objects_of(&v.name).len();
|
||||
json!({"name": v.name, "kind": v.kind.label(), "bindings": bindings})
|
||||
})
|
||||
.collect();
|
||||
Ok(json!({"variables": variables, "locked": st.doc.variables.locked}))
|
||||
let sort = json!({"by": st.variables_sort.by.id(), "descending": st.variables_sort.descending});
|
||||
Ok(json!({"variables": variables, "locked": st.doc.variables.locked, "sort": sort}))
|
||||
}
|
||||
|
||||
/// Lock Variables: without `locked` it toggles. Setting the state the document already has
|
||||
@@ -417,6 +418,120 @@ fn highlight(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
Ok(json!({"variable": name}))
|
||||
}
|
||||
|
||||
/// The column the Variables panel sorts its list by (`variable.sort`).
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
|
||||
pub enum VariableSortKey {
|
||||
/// The order the variables were defined in.
|
||||
#[default]
|
||||
Defined,
|
||||
/// The Variables column: the variable's name.
|
||||
Name,
|
||||
/// The Objects column: the name of the object the variable is bound to.
|
||||
Object,
|
||||
/// The type column: text, image, graph, visibility, then variables of unknown kind.
|
||||
Type,
|
||||
}
|
||||
|
||||
impl VariableSortKey {
|
||||
/// The values `variable.sort` takes for `by`, in the order of [`Self::id`].
|
||||
const IDS: [&'static str; 4] = ["name", "object", "type", "none"];
|
||||
|
||||
/// The value `variable.sort` takes for `by` and `variable.list` reports.
|
||||
pub fn id(self) -> &'static str {
|
||||
match self {
|
||||
Self::Name => "name",
|
||||
Self::Object => "object",
|
||||
Self::Type => "type",
|
||||
Self::Defined => "none",
|
||||
}
|
||||
}
|
||||
|
||||
fn parse(s: &str) -> Option<Self> {
|
||||
match s {
|
||||
"name" => Some(Self::Name),
|
||||
"object" => Some(Self::Object),
|
||||
"type" => Some(Self::Type),
|
||||
"none" => Some(Self::Defined),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// How the Variables panel orders its list: a column, and whether the order is reversed. Panel
|
||||
/// state, not saved and not an undo step.
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
|
||||
pub struct VariableSort {
|
||||
pub by: VariableSortKey,
|
||||
pub descending: bool,
|
||||
}
|
||||
|
||||
/// The place of a variable's kind in the order the type column sorts by.
|
||||
fn kind_rank(kind: VariableKind) -> u8 {
|
||||
match kind {
|
||||
VariableKind::Text => 0,
|
||||
VariableKind::Image => 1,
|
||||
VariableKind::Graph => 2,
|
||||
VariableKind::Visibility => 3,
|
||||
VariableKind::Unknown => 4,
|
||||
}
|
||||
}
|
||||
|
||||
/// The Variables panel's rows in its sort order, as indices into the document's variables.
|
||||
/// Names compare without regard to case. Sorted by object, a variable bound to nothing comes
|
||||
/// first and one bound to several objects comes after those bound to one; `xml_id` names objects
|
||||
/// by their XML ids, as the panel does with Identify Objects By set to XML ID. Ties go by name.
|
||||
pub fn variable_order(st: &crate::DocState, xml_id: bool) -> Vec<usize> {
|
||||
let vars = &st.doc.variables.variables;
|
||||
let sort = st.variables_sort;
|
||||
let mut order: Vec<usize> = (0..vars.len()).collect();
|
||||
if sort.by != VariableSortKey::Defined {
|
||||
order.sort_by_cached_key(|&i| {
|
||||
let v = &vars[i];
|
||||
let (rank, label) = match sort.by {
|
||||
VariableSortKey::Object => match st.doc.variables.objects_of(&v.name).as_slice() {
|
||||
[] => (0, String::new()),
|
||||
[one] => (1, st.doc.node(*one).map(|n| n.identify_label(xml_id)).unwrap_or_default()),
|
||||
_ => (2, String::new()),
|
||||
},
|
||||
VariableSortKey::Type => (kind_rank(v.kind), String::new()),
|
||||
VariableSortKey::Name | VariableSortKey::Defined => (0, String::new()),
|
||||
};
|
||||
(rank, label.to_lowercase(), label, v.name.to_lowercase(), v.name.clone())
|
||||
});
|
||||
}
|
||||
if sort.descending {
|
||||
order.reverse();
|
||||
}
|
||||
order
|
||||
}
|
||||
|
||||
/// Sort the Variables panel's list. Panel state, like `variable.highlight`: not saved and not an
|
||||
/// undo step. Setting the order the panel already has changes nothing.
|
||||
fn sort(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
const C: &str = "variable.sort";
|
||||
let key = "by";
|
||||
let by = match p.get(key) {
|
||||
None => return Err(bad(C, format!("missing `{key}`"))),
|
||||
Some(v) => v
|
||||
.as_str()
|
||||
.and_then(VariableSortKey::parse)
|
||||
.ok_or_else(|| bad(C, format!("`{key}` must be one of {}", VariableSortKey::IDS.join(", "))))?,
|
||||
};
|
||||
// A `descending` that is given must be true or false: null is refused, not read as omitted.
|
||||
let key = "descending";
|
||||
let descending = match p.get(key) {
|
||||
None => false,
|
||||
Some(v) => v.as_bool().ok_or_else(|| bad(C, format!("`{key}` must be true or false")))?,
|
||||
};
|
||||
let sort = VariableSort { by, descending };
|
||||
let st = s.doc_mut()?;
|
||||
if st.variables_sort != sort {
|
||||
st.variables_sort = sort;
|
||||
st.revision += 1;
|
||||
}
|
||||
Ok(json!({"by": by.id(), "descending": descending}))
|
||||
}
|
||||
|
||||
fn dataset_new(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
const C: &str = "dataset.new";
|
||||
let name = var_name(p, C)?;
|
||||
@@ -726,7 +841,15 @@ pub fn specs() -> Vec<CommandSpec> {
|
||||
variables_unlocked,
|
||||
rename
|
||||
),
|
||||
cmd!(query "variable.list", "List Variables", [], None, "{} → variables with kinds and binding counts, and whether they are locked → {variables, locked}", has_doc, list),
|
||||
cmd!(
|
||||
query "variable.list",
|
||||
"List Variables",
|
||||
[],
|
||||
None,
|
||||
"{} → variables with kinds and binding counts in the Variables panel's order, whether they are locked, and the panel's sort (as variable.sort sets it) → {variables, locked, sort: {by, descending}}",
|
||||
has_doc,
|
||||
list
|
||||
),
|
||||
cmd!(
|
||||
"variable.makeTextDynamic",
|
||||
"Make Text Dynamic",
|
||||
@@ -799,6 +922,15 @@ pub fn specs() -> Vec<CommandSpec> {
|
||||
has_doc,
|
||||
highlight
|
||||
),
|
||||
cmd!(
|
||||
"variable.sort",
|
||||
"Sort Variables",
|
||||
[],
|
||||
None,
|
||||
"{by: name|object|type|none, descending?: bool} sort the Variables panel's list (a click on a column header) by variable name, by the name of the bound object, or by type (text, image, graph, visibility, then unknown); `none` is the order the variables were defined in, and `descending` reverses the order. Names compare without regard to case; by object, unbound variables come first and variables bound to several objects last, and ties go by name. Panel state: not saved and not an undo step; variable.list reports it → {by, descending}",
|
||||
has_doc,
|
||||
sort
|
||||
),
|
||||
cmd!("dataset.new", "New Data Set…", ["Window", "Variables"], None, "{name, values?: {variable: value}} add a dataset", has_doc, dataset_new),
|
||||
cmd!(
|
||||
"dataset.set",
|
||||
|
||||
@@ -29,6 +29,9 @@ pub struct DocState {
|
||||
/// Bound Object act on. Panel state, not art selection: not saved, not undoable
|
||||
/// (`variable.highlight`). A name, so renaming a variable moves the highlight with it.
|
||||
pub variables_highlight: Option<String>,
|
||||
/// The order of the Variables panel's list: the column it is sorted by, and whether it is
|
||||
/// reversed. Panel state: not saved, not undoable (`variable.sort`).
|
||||
pub variables_sort: cmd::VariableSort,
|
||||
/// Isolation mode container.
|
||||
pub isolation: Option<NodeId>,
|
||||
/// Select › Start Global Edit: the objects edited together until Stop Global Edit or Escape
|
||||
@@ -316,6 +319,7 @@ impl DocState {
|
||||
active_layer,
|
||||
layer_rows: vec![],
|
||||
variables_highlight: None,
|
||||
variables_sort: Default::default(),
|
||||
isolation: None,
|
||||
global_edit: None,
|
||||
similar_outlines: Default::default(),
|
||||
|
||||
@@ -3382,3 +3382,93 @@ fn an_svg_without_readable_variable_data_opens_its_art() {
|
||||
assert!(d.variables.variables.is_empty());
|
||||
assert_eq!(vectorcraft_testkit::svg_shapes(&vectorcraft_svg::export(d, &Default::default())), 1, "the art opens");
|
||||
}
|
||||
|
||||
/// Five variables of three kinds, defined out of every order: `beta` (text, on type named Mike),
|
||||
/// `Alpha` (visibility, on a rectangle named apple), `gamma` (image, bound to nothing), `Delta`
|
||||
/// (visibility, on two rectangles) and `Echo` (text, on type named Bravo).
|
||||
fn sortable_variables() -> Session {
|
||||
let mut s = session();
|
||||
let mike = text(&mut s, "m");
|
||||
let bravo = text(&mut s, "b");
|
||||
let apple = rect(&mut s);
|
||||
let (r1, r2) = (rect(&mut s), rect(&mut s));
|
||||
for (id, name) in [(mike, "Mike"), (bravo, "Bravo"), (apple, "apple")] {
|
||||
s.execute("layer.setProps", &json!({"id": id.0, "name": name})).unwrap();
|
||||
}
|
||||
for (name, kind, ids) in [
|
||||
("beta", "text", vec![mike]),
|
||||
("Alpha", "visibility", vec![apple]),
|
||||
("gamma", "image", vec![]),
|
||||
("Delta", "visibility", vec![r1, r2]),
|
||||
("Echo", "text", vec![bravo]),
|
||||
] {
|
||||
s.execute("variable.define", &json!({"name": name, "kind": kind})).unwrap();
|
||||
if !ids.is_empty() {
|
||||
s.execute("variable.bind", &json!({"variable": name, "ids": ids.iter().map(|i| i.0).collect::<Vec<_>>()})).unwrap();
|
||||
}
|
||||
}
|
||||
s
|
||||
}
|
||||
|
||||
fn listed_names(s: &mut Session) -> Vec<String> {
|
||||
let list = s.execute("variable.list", &json!({})).unwrap();
|
||||
list["variables"].as_array().unwrap().iter().map(|v| v["name"].as_str().unwrap().to_string()).collect()
|
||||
}
|
||||
|
||||
/// `variable.sort` orders the Variables panel's list, which `variable.list` follows, by each
|
||||
/// column: names without regard to case; by object, unbound first and several objects last; by
|
||||
/// type, text, image, graph, visibility. `descending` reverses each order, and `none` goes back
|
||||
/// to the order the variables were defined in.
|
||||
#[test]
|
||||
fn variable_sort_orders_the_list_by_each_column() {
|
||||
let mut s = sortable_variables();
|
||||
let defined = ["beta", "Alpha", "gamma", "Delta", "Echo"];
|
||||
assert_eq!(listed_names(&mut s), defined);
|
||||
assert_eq!(s.execute("variable.list", &json!({})).unwrap()["sort"], json!({"by": "none", "descending": false}));
|
||||
for (by, order) in [
|
||||
("name", ["Alpha", "beta", "Delta", "Echo", "gamma"]),
|
||||
("object", ["gamma", "Alpha", "Echo", "beta", "Delta"]),
|
||||
("type", ["beta", "Echo", "gamma", "Alpha", "Delta"]),
|
||||
("none", defined),
|
||||
] {
|
||||
let r = s.execute("variable.sort", &json!({"by": by})).unwrap();
|
||||
assert_eq!(r, json!({"by": by, "descending": false}));
|
||||
assert_eq!(listed_names(&mut s), order, "{by}");
|
||||
s.execute("variable.sort", &json!({"by": by, "descending": true})).unwrap();
|
||||
let reversed: Vec<&str> = order.iter().rev().copied().collect();
|
||||
assert_eq!(listed_names(&mut s), reversed, "{by}, descending");
|
||||
assert_eq!(s.execute("variable.list", &json!({})).unwrap()["sort"], json!({"by": by, "descending": true}));
|
||||
}
|
||||
}
|
||||
|
||||
/// The sort is panel state: it is not an undo step and does not count as a change to save, and
|
||||
/// setting the order the list already has does not redraw anything. A bad `by` or `descending`
|
||||
/// is an error that leaves the order as it was.
|
||||
#[test]
|
||||
fn variable_sort_refuses_bad_params_and_is_not_an_undo_step() {
|
||||
let mut s = sortable_variables();
|
||||
let steps = s.doc().unwrap().history.undo.len();
|
||||
let dirty = s.doc().unwrap().is_dirty();
|
||||
s.execute("variable.sort", &json!({"by": "name", "descending": true})).unwrap();
|
||||
assert_eq!(s.doc().unwrap().history.undo.len(), steps, "no undo step");
|
||||
assert_eq!(s.doc().unwrap().is_dirty(), dirty);
|
||||
let revision = s.doc().unwrap().revision;
|
||||
s.execute("variable.sort", &json!({"by": "name", "descending": true})).unwrap();
|
||||
assert_eq!(s.doc().unwrap().revision, revision, "the same order changes nothing");
|
||||
|
||||
for (p, error) in [
|
||||
(json!({}), "missing `by`"),
|
||||
(json!({"by": null}), "`by` must be one of name, object, type, none"),
|
||||
(json!({"by": "name", "descending": null}), "`descending` must be true or false"),
|
||||
(json!({"by": "size"}), "`by` must be one of name, object, type, none"),
|
||||
(json!({"by": "Name"}), "`by` must be one of name, object, type, none"),
|
||||
(json!({"by": 1}), "`by` must be one of name, object, type, none"),
|
||||
(json!({"by": "type", "descending": "yes"}), "`descending` must be true or false"),
|
||||
] {
|
||||
let e = s.execute("variable.sort", &p).unwrap_err().to_string();
|
||||
assert!(e.contains(error), "{p}: {e}");
|
||||
}
|
||||
let sort = s.doc().unwrap().variables_sort;
|
||||
assert_eq!((sort.by, sort.descending), (crate::cmd::VariableSortKey::Name, true), "errors leave the order alone");
|
||||
assert_eq!(s.doc().unwrap().history.undo.len(), steps);
|
||||
}
|
||||
|
||||
@@ -1519,6 +1519,7 @@ font-technology Variable Променливи
|
||||
Highlight anchors on mouse over Маркиране на опорните точки при задържане на мишката
|
||||
Highlight Rows Маркиране на редовете
|
||||
Highlight Variable Маркиране на променливи
|
||||
Sort Variables Сортиране на променливите
|
||||
Highlight Substituted Fonts Маркиране на заменени шрифтове
|
||||
Highlight Substituted Glyphs Маркиране на заменени глифи
|
||||
History История
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -4216,6 +4216,7 @@ axis Both Obje
|
||||
Replace: Zamijeni:
|
||||
Type on a Path Options Opcije teksta na putanji
|
||||
Highlight Variable Istakni varijablu
|
||||
Sort Variables Sortiraj varijable
|
||||
New Variable Nova varijabla
|
||||
List Variables Izlistaj varijable
|
||||
Bind Variable Veži varijablu
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -2074,6 +2074,7 @@ digits Malayalam Malaiàlam
|
||||
Highlight Substituted Fonts Ressalta els tipus de lletra substituïts
|
||||
Highlight Substituted Glyphs Ressalta els glifs substituïts
|
||||
Highlight Variable Ressalta la variable
|
||||
Sort Variables Ordena les variables
|
||||
Highlight: Ressalta:
|
||||
Hindi Hindi
|
||||
History Historial
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -4314,6 +4314,7 @@ axis Both Obě
|
||||
Replace: Nahradit:
|
||||
Type on a Path Options Možnosti textu na cestě
|
||||
Highlight Variable Zvýraznit proměnnou
|
||||
Sort Variables Seřadit proměnné
|
||||
New Variable Nová proměnná
|
||||
List Variables Vypsat proměnné
|
||||
Bind Variable Svázat proměnnou
|
||||
|
||||
|
Can't render this file because it is too large.
|
@@ -4634,6 +4634,7 @@ axis Both Begge
|
||||
Replace: Erstat:
|
||||
Type on a Path Options Indstillinger for tekst på kurve
|
||||
Highlight Variable Fremhæv variabel
|
||||
Sort Variables Sortér variabler
|
||||
New Variable Ny variabel
|
||||
List Variables Oplist variabler
|
||||
Bind Variable Tilknyt variabel
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -5436,6 +5436,7 @@ theme Appearance Erscheinungsbild
|
||||
Texture Image Strukturbild
|
||||
Canvas Leinwand
|
||||
Highlight Variable Variable hervorheben
|
||||
Sort Variables Variablen sortieren
|
||||
New Variable Neue Variable
|
||||
Delete Variable Variable löschen
|
||||
Lock Variables Variablen sperren
|
||||
|
||||
|
Can't render this file because it is too large.
|
@@ -1494,6 +1494,7 @@ font-technology Variable Μεταβλητές
|
||||
Highlight anchors on mouse over Επισήμανση σημείων αγκύρωσης με το ποντίκι
|
||||
Highlight Rows Επισήμανση γραμμών
|
||||
Highlight Variable Επισήμανση μεταβλητής
|
||||
Sort Variables Ταξινόμηση μεταβλητών
|
||||
Highlight Substituted Fonts Επισήμανση υποκατεστημένων γραμματοσειρών
|
||||
Highlight Substituted Glyphs Επισήμανση υποκατεστημένων γλύφων
|
||||
History Ιστορικό
|
||||
|
||||
|
Can't render this file because it is too large.
|
@@ -1574,6 +1574,7 @@ font-technology Variable Variables
|
||||
Highlight anchors on mouse over Resaltar anclas al pasar el puntero
|
||||
Highlight Rows Resaltar filas
|
||||
Highlight Variable Resaltar variable
|
||||
Sort Variables Ordenar variables
|
||||
Highlight Substituted Fonts Resaltar fuentes sustituidas
|
||||
Highlight Substituted Glyphs Resaltar glifos sustituidos
|
||||
History Historial
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -4233,6 +4233,7 @@ axis Both Molemmat
|
||||
Replace: Korvaa:
|
||||
Type on a Path Options Polulla olevan tekstin asetukset
|
||||
Highlight Variable Korosta muuttuja
|
||||
Sort Variables Lajittele muuttujat
|
||||
New Variable Uusi muuttuja
|
||||
List Variables Luettele muuttujat
|
||||
Bind Variable Sido muuttuja
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -1568,6 +1568,7 @@ font-technology Variable Variables
|
||||
Highlight anchors on mouse over Mettre en évidence les points d’ancrage au survol
|
||||
Highlight Rows Surligner les lignes
|
||||
Highlight Variable Surligner la variable
|
||||
Sort Variables Trier les variables
|
||||
Highlight Substituted Fonts Surligner les polices remplacées
|
||||
Highlight Substituted Glyphs Surligner les glyphes remplacés
|
||||
History Historique
|
||||
|
||||
|
Can't render this file because it is too large.
|
@@ -1679,6 +1679,7 @@
|
||||
Highlight Substituted Glyphs બદલાયેલા ગ્લિફ હાઇલાઇટ કરો
|
||||
Highlight Tracking ટ્રેકિંગ હાઇલાઇટ કરો
|
||||
Highlight Variable વેરિએબલ હાઇલાઇટ કરો
|
||||
Sort Variables વેરિએબલનો ક્રમ લગાવો
|
||||
Highlight anchors on mouse over માઉસ ફેરવતાં એન્કર હાઇલાઇટ કરો
|
||||
Highlight: હાઇલાઇટ:
|
||||
Hindi હિન્દી
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 72 and column 5.
|
@@ -2305,6 +2305,7 @@ antiAlias Sharp शार्प
|
||||
Highlight Substituted Glyphs बदले हुए ग्लिफ़ हाइलाइट करें
|
||||
Highlight Tracking हाइलाइट ट्रैकिंग
|
||||
Highlight Variable वेरिएबल हाइलाइट करें
|
||||
Sort Variables वेरिएबल क्रमबद्ध करें
|
||||
Highlight: हाइलाइट:
|
||||
Hindi हिन्दी
|
||||
History हिस्ट्री
|
||||
|
||||
|
Can't render this file because it is too large.
|
@@ -1985,6 +1985,7 @@ file-filter Templates Sablonok
|
||||
Highlight Substituted Fonts Helyettesített betűtípusok kiemelése
|
||||
Highlight Substituted Glyphs Helyettesített glifák kiemelése
|
||||
Highlight Variable Változó kiemelése
|
||||
Sort Variables Változók rendezése
|
||||
Hindi Hindi
|
||||
History Előzmények
|
||||
History Options… Előzmények beállításai…
|
||||
|
||||
|
Can't render this file because it is too large.
|
@@ -3982,6 +3982,7 @@ axis Both Keduanya
|
||||
Replace: Ganti:
|
||||
Type on a Path Options Opsi teks pada jalur
|
||||
Highlight Variable Sorot variabel
|
||||
Sort Variables Urutkan variabel
|
||||
New Variable Variabel baru
|
||||
List Variables Daftar variabel
|
||||
Bind Variable Ikat variabel
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2459 and column 5.
|
@@ -1595,6 +1595,7 @@ file-filter Templates Modelli
|
||||
Highlight anchors on mouse over Evidenzia ancoraggi al passaggio del mouse
|
||||
Highlight Rows Evidenzia righe
|
||||
Highlight Variable Evidenzia variabile
|
||||
Sort Variables Ordina variabili
|
||||
Highlight Substituted Fonts Evidenzia font sostituiti
|
||||
Highlight Substituted Glyphs Evidenzia glifi sostituiti
|
||||
History Cronologia
|
||||
|
||||
|
Can't render this file because it is too large.
|
@@ -1943,6 +1943,7 @@ antiAlias Sharp シャープ
|
||||
Highlight Alternate Glyphs 代替字形をハイライト
|
||||
Highlight Rows 行をハイライト
|
||||
Highlight Variable 変数をハイライト表示
|
||||
Sort Variables 変数を並べ替え
|
||||
Highlight Substituted Fonts 置換フォントをハイライト
|
||||
Highlight Substituted Glyphs 置換字形をハイライト
|
||||
Highlight anchors on mouse over マウスオーバー時にアンカーをハイライト
|
||||
|
||||
|
Can't render this file because it is too large.
|
@@ -3845,6 +3845,7 @@ axis Both ორივე
|
||||
Replace: ჩანაცვლება:
|
||||
Type on a Path Options ტრაექტორიაზე ტექსტის პარამეტრები
|
||||
Highlight Variable ცვლადის გამოკვეთა
|
||||
Sort Variables ცვლადების დალაგება
|
||||
New Variable ახალი ცვლადი
|
||||
List Variables ცვლადების სია
|
||||
Bind Variable ცვლადის მიბმა
|
||||
|
||||
|
Can't render this file because it is too large.
|
@@ -1933,6 +1933,7 @@ antiAlias Sharp 날카롭게
|
||||
Highlight Alternate Glyphs 대체 글리프 강조 표시
|
||||
Highlight Rows 행 강조 표시
|
||||
Highlight Variable 가변 요소 강조 표시
|
||||
Sort Variables 가변 요소 정렬
|
||||
Highlight Substituted Fonts 대체된 글꼴 강조 표시
|
||||
Highlight Substituted Glyphs 대체된 글리프 강조 표시
|
||||
Highlight anchors on mouse over 마우스를 올리면 앵커 강조 표시
|
||||
|
||||
|
Can't render this file because it is too large.
|
@@ -4542,6 +4542,7 @@ axis Both Beide
|
||||
Replace: Vervangen:
|
||||
Type on a Path Options Opties voor tekst op pad
|
||||
Highlight Variable Variabele markeren
|
||||
Sort Variables Variabelen sorteren
|
||||
New Variable Nieuwe variabele
|
||||
List Variables Variabelen weergeven
|
||||
Bind Variable Variabele binden
|
||||
|
||||
|
Can't render this file because it is too large.
|
@@ -4156,6 +4156,7 @@ axis Both Obie
|
||||
Replace: Zastąp:
|
||||
Type on a Path Options Opcje tekstu na ścieżce
|
||||
Highlight Variable Wyróżnij zmienną
|
||||
Sort Variables Sortuj zmienne
|
||||
New Variable Nowa zmienna
|
||||
List Variables Wyświetl zmienne
|
||||
Bind Variable Powiąż zmienną
|
||||
|
||||
|
Can't render this file because it is too large.
|
@@ -3616,6 +3616,7 @@ font-technology Variable Variáveis
|
||||
Graphics Gráficos
|
||||
Highlight Rows Destacar linhas
|
||||
Highlight Variable Destacar variável
|
||||
Sort Variables Ordenar variáveis
|
||||
Palette: Paleta:
|
||||
Limited Limitada
|
||||
Full Tone Tom completo
|
||||
|
||||
|
Can't render this file because it is too large.
|
@@ -1584,6 +1584,7 @@ file-filter Templates Шаблоны
|
||||
Highlight anchors on mouse over Выделять опорные точки при наведении
|
||||
Highlight Rows Выделять строки
|
||||
Highlight Variable Подсветить переменную
|
||||
Sort Variables Сортировать переменные
|
||||
Highlight Substituted Fonts Выделять заменённые шрифты
|
||||
Highlight Substituted Glyphs Выделять заменённые глифы
|
||||
History История
|
||||
|
||||
|
Can't render this file because it is too large.
|
@@ -1532,6 +1532,7 @@ font-technology Variable Variabla
|
||||
Highlight anchors on mouse over Framhäv ankarpunkter när musen är över dem
|
||||
Highlight Rows Framhäv rader
|
||||
Highlight Variable Framhäv variabel
|
||||
Sort Variables Sortera variabler
|
||||
Highlight Substituted Fonts Framhäv ersatta typsnitt
|
||||
Highlight Substituted Glyphs Framhäv ersatta glyfer
|
||||
History Historik
|
||||
|
||||
|
Can't render this file because it is too large.
|
@@ -1496,6 +1496,7 @@ font-technology Variable Değişken
|
||||
Highlight anchors on mouse over Fareyle üzerine gelindiğinde bağlantı noktalarını vurgulayın
|
||||
Highlight Rows Satırları Vurgula
|
||||
Highlight Variable Değişkeni Vurgula
|
||||
Sort Variables Değişkenleri Sırala
|
||||
Highlight Substituted Fonts Değiştirilen Yazı Tiplerini Vurgula
|
||||
Highlight Substituted Glyphs Değiştirilen Glifleri Vurgula
|
||||
History Geçmiş
|
||||
|
||||
|
Can't render this file because it is too large.
|
@@ -1589,6 +1589,7 @@ file-filter Templates Шаблони
|
||||
Highlight anchors on mouse over Виділяти опорні точки при наведенні
|
||||
Highlight Rows Виділяти рядки
|
||||
Highlight Variable Підсвітити змінну
|
||||
Sort Variables Сортувати змінні
|
||||
Highlight Substituted Fonts Виділяти замінені шрифти
|
||||
Highlight Substituted Glyphs Виділяти замінені гліфи
|
||||
History Історія
|
||||
|
||||
|
Can't render this file because it is too large.
|
@@ -1822,6 +1822,7 @@ file-filter Templates Mẫu
|
||||
Highlight Substituted Fonts Đánh dấu phông thay thế
|
||||
Highlight Substituted Glyphs Đánh dấu Glyph bị thay thế
|
||||
Highlight Variable Đánh dấu biến
|
||||
Sort Variables Sắp xếp biến
|
||||
Hindi Tiếng Hindi
|
||||
digits Hindi Hindi
|
||||
History Lịch sử
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 204 and column 5.
|
@@ -5511,6 +5511,7 @@ font-technology Variable 可变
|
||||
High Performance (discrete) 高性能 (独立显卡)
|
||||
Highlight Rows 突出显示行
|
||||
Highlight Variable 突出显示变量
|
||||
Sort Variables 对变量排序
|
||||
Lock Others 锁定其他
|
||||
Merge Selected 合并所选图层
|
||||
Move Rows 移动行
|
||||
|
||||
|
Can't render this file because it is too large.
|
@@ -1560,6 +1560,7 @@ file-filter Templates 範本
|
||||
Highlight anchors on mouse over 滑鼠移過時醒目提示錨點
|
||||
Highlight Rows 反白顯示列
|
||||
Highlight Variable 突出顯示變數
|
||||
Sort Variables 排序變數
|
||||
Highlight Substituted Fonts 醒目提示被取代的字體
|
||||
Highlight Substituted Glyphs 醒目提示被取代的字符
|
||||
History 步驟記錄
|
||||
|
||||
|
Can't render this file because it is too large.
|
@@ -8,17 +8,117 @@
|
||||
//! the document then no longer matches the active dataset, and "Update Data Set" writes the
|
||||
//! art back into it.
|
||||
|
||||
use egui::Ui;
|
||||
use egui::{Rect, Response, Sense, Ui, pos2, vec2};
|
||||
use serde_json::json;
|
||||
use vectorcraft_doc::{NodeId, NodeKind, VariableKind};
|
||||
use vectorcraft_engine::DocState;
|
||||
use vectorcraft_engine::cmd::{VariableSort, VariableSortKey, variable_order};
|
||||
|
||||
use crate::VectorcraftApp;
|
||||
use crate::theme::Tokens;
|
||||
use crate::widgets;
|
||||
|
||||
/// The panel id, as the dock, the Window menu and `window.panel` name it.
|
||||
pub const ID: &str = "variables";
|
||||
|
||||
/// The width of the variable list's type column, which holds each row's kind icon.
|
||||
const TYPE_COLUMN: f32 = 22.0;
|
||||
|
||||
/// The width of the Variables column in a list `width` wide; the Objects column takes the rest.
|
||||
fn name_column(width: f32) -> f32 {
|
||||
((width - TYPE_COLUMN) / 2.0).max(0.0)
|
||||
}
|
||||
|
||||
/// The sort a click on `key`'s header asks for: that column in ascending order, or the reverse
|
||||
/// of the order the list has when it is already sorted by that column.
|
||||
fn clicked_sort(sort: VariableSort, key: VariableSortKey) -> VariableSort {
|
||||
VariableSort { by: key, descending: sort.by == key && !sort.descending }
|
||||
}
|
||||
|
||||
/// The arrow a header shows while the list is sorted by its column.
|
||||
fn sort_arrow(sort: VariableSort, key: VariableSortKey) -> Option<&'static str> {
|
||||
(sort.by == key).then_some(if sort.descending { "▼" } else { "▲" })
|
||||
}
|
||||
|
||||
/// Draw the list's three column headers (type, Variables, Objects); a click on one returns the
|
||||
/// sort it asks for.
|
||||
fn headers(ui: &mut Ui, sort: VariableSort) -> Option<VariableSort> {
|
||||
let t = Tokens::get(ui.ctx());
|
||||
let h = ui.spacing().interact_size.y;
|
||||
let (rect, _) = ui.allocate_exact_size(vec2(ui.available_width(), h), Sense::hover());
|
||||
let name_w = name_column(rect.width());
|
||||
let name_left = rect.left() + TYPE_COLUMN;
|
||||
let cells = [
|
||||
(VariableSortKey::Type, tl!("Type"), Rect::from_min_max(rect.min, pos2(name_left, rect.bottom()))),
|
||||
(VariableSortKey::Name, tl!("Variables"), Rect::from_min_max(pos2(name_left, rect.top()), pos2(name_left + name_w, rect.bottom()))),
|
||||
(VariableSortKey::Object, tl!("Objects"), Rect::from_min_max(pos2(name_left + name_w, rect.top()), rect.max)),
|
||||
];
|
||||
let font = egui::TextStyle::Body.resolve(ui.style());
|
||||
let mut clicked = None;
|
||||
for (key, label, cell) in cells {
|
||||
let r = ui.interact(cell, ui.scope_id().with(("variables-sort", key.id())), Sense::click());
|
||||
if r.hovered() {
|
||||
ui.painter().rect_filled(cell, 0.0, t.hover);
|
||||
}
|
||||
let arrow = sort_arrow(sort, key);
|
||||
let p = ui.painter().with_clip_rect(cell);
|
||||
if key == VariableSortKey::Type {
|
||||
// The type column is too narrow for a word: its icon, or the arrow once it sorts.
|
||||
match arrow {
|
||||
Some(a) => {
|
||||
p.text(cell.center(), egui::Align2::CENTER_CENTER, a, font.clone(), t.text_dim);
|
||||
}
|
||||
None => crate::icons::paint(ui, "arrow-up-down", Rect::from_center_size(cell.center(), vec2(14.0, 14.0)), t.icon),
|
||||
}
|
||||
} else {
|
||||
p.text(cell.left_center() + vec2(4.0, 0.0), egui::Align2::LEFT_CENTER, label, font.clone(), t.text_strong);
|
||||
if let Some(a) = arrow {
|
||||
p.text(cell.right_center() - vec2(4.0, 0.0), egui::Align2::RIGHT_CENTER, a, font.clone(), t.text_dim);
|
||||
}
|
||||
}
|
||||
r.widget_info(|| egui::WidgetInfo::selected(egui::Role::Button, true, arrow.is_some(), label));
|
||||
if r.on_hover_text(label).clicked() {
|
||||
clicked = Some(clicked_sort(sort, key));
|
||||
}
|
||||
}
|
||||
clicked
|
||||
}
|
||||
|
||||
/// Draw one variable row across the three columns: its kind's icon, its name and the object it
|
||||
/// drives. Each column clips its text to its own width.
|
||||
fn variable_row(ui: &mut Ui, row: &VarRow, selected: bool) -> Response {
|
||||
let t = Tokens::get(ui.ctx());
|
||||
let h = ui.spacing().interact_size.y;
|
||||
let (rect, r) = ui.allocate_exact_size(vec2(ui.available_width(), h), Sense::click());
|
||||
if selected {
|
||||
ui.painter().rect_filled(rect, 0.0, t.row_selected);
|
||||
} else if r.hovered() {
|
||||
ui.painter().rect_filled(rect, 0.0, t.hover);
|
||||
}
|
||||
// The kind's own icon, so a text variable and a visibility one read apart.
|
||||
let icon = match row.kind {
|
||||
VariableKind::Text => "type",
|
||||
VariableKind::Visibility => "eye",
|
||||
VariableKind::Image => "image",
|
||||
VariableKind::Graph => "chart-column",
|
||||
VariableKind::Unknown => "square-dashed",
|
||||
};
|
||||
let icon_rect = Rect::from_center_size(pos2(rect.left() + TYPE_COLUMN / 2.0, rect.center().y), vec2(14.0, 14.0));
|
||||
crate::icons::paint(ui, icon, icon_rect, t.icon);
|
||||
let name_left = rect.left() + TYPE_COLUMN;
|
||||
let name_right = name_left + name_column(rect.width());
|
||||
let font = egui::TextStyle::Body.resolve(ui.style());
|
||||
let (pad, left) = (vec2(4.0, 0.0), egui::Align2::LEFT_CENTER);
|
||||
let name_cell = Rect::from_min_max(pos2(name_left, rect.top()), pos2(name_right - 4.0, rect.bottom()));
|
||||
ui.painter().with_clip_rect(name_cell).text(name_cell.left_center() + pad, left, &row.name, font.clone(), t.text);
|
||||
let object_cell = Rect::from_min_max(pos2(name_right, rect.top()), rect.max);
|
||||
ui.painter().with_clip_rect(object_cell).text(object_cell.left_center() + pad, left, &row.object, font, t.text_dim);
|
||||
let full = if row.object.is_empty() { row.name.clone() } else { format!("{} · {}", row.name, row.object) };
|
||||
r.widget_info(|| egui::WidgetInfo::selected(egui::Role::ListItem, true, selected, &full));
|
||||
// A column clips a long name, so the whole of it shows on hover.
|
||||
r.on_hover_text(full)
|
||||
}
|
||||
|
||||
/// One variable row: its kind's icon, its name, and the object it drives.
|
||||
struct VarRow {
|
||||
name: String,
|
||||
@@ -105,11 +205,11 @@ pub fn show(app: &mut VectorcraftApp, ui: &mut Ui) {
|
||||
};
|
||||
// Owned snapshots: the rows below run commands, which borrow the app mutably.
|
||||
let xml_id = app.session.prefs.identify_objects_by == "xmlId";
|
||||
let vars: Vec<VarRow> = st
|
||||
.doc
|
||||
.variables
|
||||
.variables
|
||||
.iter()
|
||||
// In the order `variable.sort` set, which `variable.list` reports too.
|
||||
let all = &st.doc.variables.variables;
|
||||
let vars: Vec<VarRow> = variable_order(st, xml_id)
|
||||
.into_iter()
|
||||
.filter_map(|i| all.get(i))
|
||||
.map(|v| {
|
||||
let bound: Vec<u64> = st.doc.variables.objects_of(&v.name).iter().map(|i| i.0).collect();
|
||||
let object = match bound.as_slice() {
|
||||
@@ -139,6 +239,7 @@ pub fn show(app: &mut VectorcraftApp, ui: &mut Ui) {
|
||||
let highlighted = highlighted_row(st);
|
||||
// Lock Variables: no variable is defined, deleted or renamed until they are unlocked.
|
||||
let locked = st.doc.variables.locked;
|
||||
let sort = st.variables_sort;
|
||||
|
||||
// The current data set across the top: its name, and the arrows that step through them.
|
||||
ui.horizontal(|ui| {
|
||||
@@ -162,33 +263,24 @@ pub fn show(app: &mut VectorcraftApp, ui: &mut Ui) {
|
||||
});
|
||||
});
|
||||
|
||||
widgets::subheader(ui, tl!("Variables"));
|
||||
// The list's column headers: a click sorts the list by that column, and a second click
|
||||
// reverses the order.
|
||||
if let Some(next) = headers(ui, sort) {
|
||||
app.run("variable.sort", json!({"by": next.by.id(), "descending": next.descending})).ok();
|
||||
}
|
||||
if vars.is_empty() {
|
||||
widgets::dim_label(ui, tl!("None"));
|
||||
}
|
||||
for row in &vars {
|
||||
ui.horizontal(|ui| {
|
||||
// The kind's own icon, so a text variable and a visibility one read apart.
|
||||
let icon = match row.kind {
|
||||
VariableKind::Text => "type",
|
||||
VariableKind::Visibility => "eye",
|
||||
VariableKind::Image => "image",
|
||||
VariableKind::Graph => "chart-column",
|
||||
VariableKind::Unknown => "square-dashed",
|
||||
};
|
||||
ui.add(egui::Image::new(crate::icons::source(icon)).fit_to_exact_size(egui::vec2(14.0, 14.0)));
|
||||
// The object a variable drives is named beside it, as the reference panel does.
|
||||
let text = if row.object.is_empty() { row.name.clone() } else { format!("{} · {}", row.name, row.object) };
|
||||
let r = ui.selectable_label(highlighted.as_deref() == Some(row.name.as_str()), text);
|
||||
// A click highlights the row and selects what it drives; a double click opens
|
||||
// Variable Options for it.
|
||||
if r.clicked() {
|
||||
click_row(app, &row.name, &row.bound);
|
||||
}
|
||||
if r.double_clicked() && !locked {
|
||||
crate::dialogs::variables::open(app, "variable.rename", Some(&row.name));
|
||||
}
|
||||
});
|
||||
let r = variable_row(ui, row, highlighted.as_deref() == Some(row.name.as_str()));
|
||||
// A click highlights the row and selects what it drives; a double click opens
|
||||
// Variable Options for it.
|
||||
if r.clicked() {
|
||||
click_row(app, &row.name, &row.bound);
|
||||
}
|
||||
if r.double_clicked() && !locked {
|
||||
crate::dialogs::variables::open(app, "variable.rename", Some(&row.name));
|
||||
}
|
||||
}
|
||||
|
||||
widgets::subheader(ui, tl!("Data Sets"));
|
||||
@@ -406,6 +498,69 @@ mod tests {
|
||||
assert!(text.contains("Name") && text.contains("Data Sets") && text.contains("Data Set 1"), "{text}");
|
||||
}
|
||||
|
||||
/// A click on a column header sorts the list by that column, and a second click on it
|
||||
/// reverses the order; the rows are drawn in the order `variable.list` reports.
|
||||
#[test]
|
||||
fn a_header_click_sorts_the_list_and_a_second_click_reverses_it() {
|
||||
use super::super::tests_appearance::{click, frame_events, text_rect};
|
||||
let mut app = app_with_data();
|
||||
let t = app.session.execute("text.create", &json!({"x": 10, "y": 80, "text": "Bob"})).unwrap()["id"].as_u64().unwrap();
|
||||
app.session.execute("layer.setProps", &json!({"id": t, "name": "Aaron"})).unwrap();
|
||||
let alice = app.session.doc().unwrap().doc.variables.objects_of("Name")[0];
|
||||
app.session.execute("layer.setProps", &json!({"id": alice.0, "name": "Bella"})).unwrap();
|
||||
app.session.execute("variable.define", &json!({"name": "age", "kind": "visibility"})).unwrap();
|
||||
app.session.execute("variable.bind", &json!({"variable": "age", "ids": [t]})).unwrap();
|
||||
let ctx = egui::Context::default();
|
||||
let drawn = |ctx: &egui::Context, app: &mut VectorcraftApp| -> Vec<String> {
|
||||
let texts = frame_events(ctx, app, vec![], show);
|
||||
let mut rows: Vec<(f32, String)> = ["Name", "age"].iter().map(|n| (text_rect(&texts, n).center().y, n.to_string())).collect();
|
||||
rows.sort_by(|a, b| a.0.total_cmp(&b.0));
|
||||
rows.into_iter().map(|(_, n)| n).collect()
|
||||
};
|
||||
let listed = |app: &mut VectorcraftApp| -> Vec<String> {
|
||||
let list = app.run("variable.list", json!({})).unwrap();
|
||||
list["variables"].as_array().unwrap().iter().map(|v| v["name"].as_str().unwrap().to_string()).collect()
|
||||
};
|
||||
let sort = |app: &VectorcraftApp| {
|
||||
let s = app.session.doc().unwrap().variables_sort;
|
||||
(s.by, s.descending)
|
||||
};
|
||||
assert_eq!(drawn(&ctx, &mut app), ["Name", "age"], "the order they were defined in");
|
||||
let steps = app.session.doc().unwrap().history.undo.len();
|
||||
|
||||
let header = |ctx: &egui::Context, app: &mut VectorcraftApp, label: &str| text_rect(&frame_events(ctx, app, vec![], show), label).center();
|
||||
let at = header(&ctx, &mut app, "Variables");
|
||||
click(&ctx, &mut app, at, show);
|
||||
assert_eq!(sort(&app), (VariableSortKey::Name, false));
|
||||
assert_eq!(drawn(&ctx, &mut app), ["age", "Name"]);
|
||||
assert_eq!(listed(&mut app), ["age", "Name"]);
|
||||
let at = header(&ctx, &mut app, "Variables");
|
||||
click(&ctx, &mut app, at, show);
|
||||
assert_eq!(sort(&app), (VariableSortKey::Name, true));
|
||||
assert_eq!(drawn(&ctx, &mut app), ["Name", "age"]);
|
||||
|
||||
// Objects: Aaron (age's type) before Bella (Name's), then the reverse.
|
||||
let at = header(&ctx, &mut app, "Objects");
|
||||
click(&ctx, &mut app, at, show);
|
||||
assert_eq!(sort(&app), (VariableSortKey::Object, false));
|
||||
assert_eq!(drawn(&ctx, &mut app), ["age", "Name"]);
|
||||
let at = header(&ctx, &mut app, "Objects");
|
||||
click(&ctx, &mut app, at, show);
|
||||
assert_eq!(drawn(&ctx, &mut app), ["Name", "age"]);
|
||||
|
||||
// The type column's header sits left of the Variables header: text before visibility.
|
||||
let left = text_rect(&frame_events(&ctx, &mut app, vec![], show), "Variables").left() - 4.0 - TYPE_COLUMN / 2.0;
|
||||
let y = header(&ctx, &mut app, "Variables").y;
|
||||
click(&ctx, &mut app, egui::pos2(left, y), show);
|
||||
assert_eq!(sort(&app), (VariableSortKey::Type, false));
|
||||
assert_eq!(drawn(&ctx, &mut app), ["Name", "age"]);
|
||||
click(&ctx, &mut app, egui::pos2(left, y), show);
|
||||
assert_eq!(sort(&app), (VariableSortKey::Type, true));
|
||||
assert_eq!(drawn(&ctx, &mut app), ["age", "Name"]);
|
||||
// Sorting is not an edit.
|
||||
assert_eq!(app.session.doc().unwrap().history.undo.len(), steps, "no undo step");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_row_the_art_left_behind_is_in_italics() {
|
||||
let mut app = app_with_data();
|
||||
|
||||
+8
-1
@@ -3418,7 +3418,14 @@ already walks Image Trace. Short agent entry points:
|
||||
without a kind VectorCraft reads is listed with `kind: "unknown"`: it takes no values, and `variable.bind`
|
||||
refuses it. When an opened `.ai`'s layers are read, the objects it binds to its text, visibility and image
|
||||
variables come in bound when they are of the variable's kind, locked and hidden objects included, and
|
||||
`variable.list` counts them in `bindings`.
|
||||
`variable.list` counts them in `bindings`. `variable.sort {by: name|object|type|none, descending?}` sorts the
|
||||
Variables panel's list, as a click on its Variables, Objects or type column header does (a second click on the
|
||||
same header sends `descending: true`): by variable name, by the name of the bound object, or by type in
|
||||
VectorCraft's order (text, image, graph, visibility, then unknown); `none` is the order the variables were
|
||||
defined in. Names compare without regard to case; by object, unbound variables come first and variables bound
|
||||
to several objects last, and ties go by name. Another `by` value, or a `descending` that is not true or false,
|
||||
is an error. The sort is panel state, not saved and not an undo step; `variable.list` lists the variables in
|
||||
that order and reports it as `sort: {by, descending}`.
|
||||
- Data sets: `dataset.list`, `dataset.new {name, values?}`, `dataset.set {name, newName?, values?}` (`newName` also
|
||||
renames the row, in the same undo step; Edit Data Set's Name field sends it), `dataset.rename`,
|
||||
`dataset.delete`, `dataset.capture`, `dataset.update`, `dataset.select {name}`, `dataset.next` /
|
||||
|
||||
@@ -788,6 +788,7 @@ Moved from the ROADMAP's "Shipped so far" on 2026-10-10. Update it when a featur
|
||||
- Variables: `dataset.select`, `dataset.next` and `dataset.prev` name the area type and type on a path whose text they rewrote and that doesn't fit, in the reply's `overflow` key, as `text.setText` does.
|
||||
- Variables: when no object is bound to a variable that the data set has a value for, for example after Import Data Sets… defines new variables without binding them, choosing the data set in the Variables panel or stepping to it with the panel's arrows shows a status message, and `dataset.select`, `dataset.next` and `dataset.prev` add the message to the reply as `warning`.
|
||||
- Variables: Lock Variables (`variable.lock`, in the Variables panel's bottom bar and menu) stops variables from being defined, deleted or renamed until they are unlocked, while objects can still be bound and unbound and data sets still change and apply; the lock is saved with the document.
|
||||
- Variables: the Variables panel lists its variables in three columns with headers (type, Variables, Objects), and a click on a header sorts the list by variable type, variable name or bound object name, with a second click reversing the order (`variable.sort`; `variable.list` follows the order), as the [Variable panel overview](https://helpx.adobe.com/illustrator/desktop/automate-visualize-data/automate-actions/variable-panel-overview.html) describes.
|
||||
- Batch…: Override Save and Override Export name each file with the extension of the format the step writes, however the step spells the format (`.svg` and `SVG` give `.svg`, `png8` gives `.png`), in `action.batch`.
|
||||
- Batch…: Override Export writes the files of an Export for Screens or Export Assets step into a folder for each source file or data set inside the destination folder, under the names the export gives them (`action.batch`). The folder and file names drop trailing periods and spaces (`a..svg` gives `a`). When two source files would get the same name that way (compared without case), or a later file's folder or file would take the name of an output an earlier file wrote, of a source file in the destination folder or, for data sets, of a file already there, Batch adds `-2`, `-3`… to the later one's name.
|
||||
- Batch…: when a step fails on a file or data set, the error names the step by its place in the action's steps, from 1, counting the Open steps the batch skips (`action.batch`). This numbering is VectorCraft's own rule. Tests `cmd::fileio::action_batch::tests::{a_folder_batch_error_names_the_step_in_the_commands_sent, a_recorded_open_that_fails_is_the_first_step, a_data_set_batch_error_names_the_step_in_the_commands_sent, a_redirected_export_that_fails_keeps_its_place, a_source_that_does_not_open_names_no_step}`.
|
||||
|
||||
Reference in New Issue
Block a user