forked from 1b-agent/fieldcraft-upstream
ui: element faces and edges as element:side lists
A Sides field kind: element faces or edges typed as element:side with ranges (1-20:3, 25:1), no row cap, and Use selection for picked faces. Set and mesh-tag forms use it instead of a JSON box (#449). Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01UhhZMBTviDQxZvwnyP89fo
This commit is contained in:
1 parent
9b705f5334
commit
f8b92fcd89
6 files changed
+192
-6
No files matched your search
@@ -58,8 +58,14 @@ pub const SET: &[Field] = &[
|
|||||||
labels("elements", "Elements", LabelOf::Element)
|
labels("elements", "Elements", LabelOf::Element)
|
||||||
.help("Mesh element labels; Use selection takes the picked elements.")
|
.help("Mesh element labels; Use selection takes the picked elements.")
|
||||||
.hint("labels: 1, 2, 10-20"),
|
.hint("labels: 1, 2, 10-20"),
|
||||||
json("faces", "Element faces").help("Element faces, as a JSON list of element label and local face number.").hint("[{\"element\", \"face\"}]"),
|
sides("faces", "Element faces", Side::Face)
|
||||||
json("edges", "Element edges").help("Element edges, as a JSON list of element label and local edge number.").hint("[{\"element\", \"edge\"}]"),
|
.help(
|
||||||
|
"Element faces as element:face (local face number), ranges of elements sharing one: 1-20:3, 25:1. Use selection takes the picked faces.",
|
||||||
|
)
|
||||||
|
.hint("element:face, e.g. 1-20:3, 25:1"),
|
||||||
|
sides("edges", "Element edges", Side::Edge)
|
||||||
|
.help("Element edges as element:edge (local edge number), ranges of elements sharing one: 1-20:3, 25:1.")
|
||||||
|
.hint("element:edge, e.g. 1-20:3"),
|
||||||
];
|
];
|
||||||
|
|
||||||
const ELASTIC: Field = variants(
|
const ELASTIC: Field = variants(
|
||||||
@@ -1468,8 +1474,8 @@ pub const TAG: &[Field] = &[
|
|||||||
.help("The geometry the mesh entities are tagged with.")
|
.help("The geometry the mesh entities are tagged with.")
|
||||||
.req(),
|
.req(),
|
||||||
labels("elements", "Elements", LabelOf::Element).help("Element labels."),
|
labels("elements", "Elements", LabelOf::Element).help("Element labels."),
|
||||||
json("faces", "Element faces").help("Element faces, as JSON."),
|
sides("faces", "Element faces", Side::Face).help("Element faces as element:face: 1-20:3, 25:1.").hint("element:face"),
|
||||||
json("edges", "Element edges").help("Element edges, as JSON."),
|
sides("edges", "Element edges", Side::Edge).help("Element edges as element:edge: 1-20:3, 25:1.").hint("element:edge"),
|
||||||
labels("nodes", "Nodes", LabelOf::Node).help("Node labels."),
|
labels("nodes", "Nodes", LabelOf::Node).help("Node labels."),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@@ -137,6 +137,9 @@ pub enum Kind {
|
|||||||
/// A text per item: an object from the names of a collection's items to a string; an item
|
/// A text per item: an object from the names of a collection's items to a string; an item
|
||||||
/// left out takes the command's default.
|
/// left out takes the command's default.
|
||||||
RefText(Collection),
|
RefText(Collection),
|
||||||
|
/// A list of element faces or edges, `{"element", "face"}` or `{"element", "edge"}` (at most
|
||||||
|
/// [`MAX_LABELS`]).
|
||||||
|
Sides(Side),
|
||||||
/// Any JSON: what the schema does not describe.
|
/// Any JSON: what the schema does not describe.
|
||||||
Json,
|
Json,
|
||||||
}
|
}
|
||||||
@@ -148,6 +151,23 @@ pub enum LabelOf {
|
|||||||
Element,
|
Element,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// What a [`Kind::Sides`] list holds: element faces or element edges.
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub enum Side {
|
||||||
|
Face,
|
||||||
|
Edge,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Side {
|
||||||
|
/// The key of the local number in each entry, and its word.
|
||||||
|
pub fn key(self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
Side::Face => "face",
|
||||||
|
Side::Edge => "edge",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl LabelOf {
|
impl LabelOf {
|
||||||
pub fn label(self) -> &'static str {
|
pub fn label(self) -> &'static str {
|
||||||
match self {
|
match self {
|
||||||
@@ -520,6 +540,10 @@ pub const fn rows(key: &'static str, label: &'static str, fields: &'static [Fiel
|
|||||||
pub const fn labels(key: &'static str, label: &'static str, of: LabelOf) -> Field {
|
pub const fn labels(key: &'static str, label: &'static str, of: LabelOf) -> Field {
|
||||||
field(key, label, Kind::Labels(of))
|
field(key, label, Kind::Labels(of))
|
||||||
}
|
}
|
||||||
|
/// A list of element faces or edges.
|
||||||
|
pub const fn sides(key: &'static str, label: &'static str, side: Side) -> Field {
|
||||||
|
field(key, label, Kind::Sides(side))
|
||||||
|
}
|
||||||
/// The columns of a point, `[x, y, z]`.
|
/// The columns of a point, `[x, y, z]`.
|
||||||
pub const XYZ: &[&str] = &["x", "y", "z"];
|
pub const XYZ: &[&str] = &["x", "y", "z"];
|
||||||
/// A list of points `[x, y, z]`.
|
/// A list of points `[x, y, z]`.
|
||||||
@@ -1078,6 +1102,21 @@ fn value_problem(f: &'static Field, v: &Value, path: &str, out: &mut Vec<Problem
|
|||||||
(!ok).then_some(ProblemKind::NotAChoice)
|
(!ok).then_some(ProblemKind::NotAChoice)
|
||||||
}
|
}
|
||||||
Kind::Unit | Kind::Json => None,
|
Kind::Unit | Kind::Json => None,
|
||||||
|
Kind::Sides(side) => match v.as_array() {
|
||||||
|
Some(a)
|
||||||
|
if a.len() <= MAX_LABELS
|
||||||
|
&& a.iter().all(|x| {
|
||||||
|
x.as_object().is_some_and(|o| {
|
||||||
|
o.len() == 2
|
||||||
|
&& o.get("element").and_then(Value::as_u64).is_some_and(|n| n >= 1 && n <= u64::from(u32::MAX))
|
||||||
|
&& o.get(side.key()).and_then(Value::as_u64).is_some_and(|n| n >= 1 && n <= u64::from(u8::MAX))
|
||||||
|
})
|
||||||
|
}) =>
|
||||||
|
{
|
||||||
|
None
|
||||||
|
}
|
||||||
|
_ => Some(ProblemKind::NotAList { max: MAX_LABELS, what: if side == Side::Face { "element faces" } else { "element edges" } }),
|
||||||
|
},
|
||||||
Kind::Labels(_) => match v.as_array() {
|
Kind::Labels(_) => match v.as_array() {
|
||||||
Some(a) if a.len() <= MAX_LABELS && a.iter().all(|x| x.as_u64().is_some_and(|n| n >= 1 && n <= u64::from(u32::MAX))) => None,
|
Some(a) if a.len() <= MAX_LABELS && a.iter().all(|x| x.as_u64().is_some_and(|n| n >= 1 && n <= u64::from(u32::MAX))) => None,
|
||||||
_ => Some(ProblemKind::NotAList { max: MAX_LABELS, what: "labels" }),
|
_ => Some(ProblemKind::NotAList { max: MAX_LABELS, what: "labels" }),
|
||||||
@@ -1240,6 +1279,16 @@ fn field_schema(f: &Field, depth: usize) -> Value {
|
|||||||
}).collect::<Vec<_>>()}),
|
}).collect::<Vec<_>>()}),
|
||||||
Kind::Rows { fields, max } if depth <= MAX_DEPTH => json!({"type": "array", "items": object_schema(fields, depth + 1), "maxItems": max}),
|
Kind::Rows { fields, max } if depth <= MAX_DEPTH => json!({"type": "array", "items": object_schema(fields, depth + 1), "maxItems": max}),
|
||||||
Kind::Unit => json!({"const": f.key}),
|
Kind::Unit => json!({"const": f.key}),
|
||||||
|
Kind::Sides(side) => json!({
|
||||||
|
"type": "array",
|
||||||
|
"maxItems": MAX_LABELS,
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {"element": {"type": "integer", "minimum": 1, "maximum": u32::MAX}, side.key(): {"type": "integer", "minimum": 1, "maximum": u8::MAX}},
|
||||||
|
"required": ["element", side.key()],
|
||||||
|
"additionalProperties": false,
|
||||||
|
},
|
||||||
|
}),
|
||||||
Kind::Labels(of) => {
|
Kind::Labels(of) => {
|
||||||
json!({"type": "array", "items": {"type": "integer", "minimum": 1, "maximum": u32::MAX}, "maxItems": MAX_LABELS, "x-labels": of.label()})
|
json!({"type": "array", "items": {"type": "integer", "minimum": 1, "maximum": u32::MAX}, "maxItems": MAX_LABELS, "x-labels": of.label()})
|
||||||
}
|
}
|
||||||
@@ -1406,6 +1455,24 @@ mod tests {
|
|||||||
assert_eq!(s["properties"]["as"]["additionalProperties"]["type"], "string");
|
assert_eq!(s["properties"]["as"]["additionalProperties"]["type"], "string");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A sides list holds element and local face (or edge) objects, many more than a form's rows.
|
||||||
|
#[test]
|
||||||
|
fn sides_hold_element_and_local_number() {
|
||||||
|
const F: &[Field] = &[sides("faces", "Faces", Side::Face), sides("edges", "Edges", Side::Edge)];
|
||||||
|
let many: Vec<Value> = (1..=5000).map(|e| json!({"element": e, "face": 3})).collect();
|
||||||
|
assert!(problems(F, &json!({"faces": many, "edges": [{"element": 2, "edge": 12}]})).is_empty());
|
||||||
|
for bad in [
|
||||||
|
json!({"faces": [{"element": 0, "face": 1}]}),
|
||||||
|
json!({"faces": [{"element": 1, "face": 0}]}),
|
||||||
|
json!({"faces": [{"element": 1, "edge": 1}]}),
|
||||||
|
json!({"faces": [{"element": 1, "face": 1, "x": 1}]}),
|
||||||
|
json!({"edges": [3]}),
|
||||||
|
] {
|
||||||
|
assert_eq!(problems(F, &bad).len(), 1, "{bad}");
|
||||||
|
}
|
||||||
|
assert_eq!(json_schema(F)["properties"]["edges"]["items"]["required"], json!(["element", "edge"]));
|
||||||
|
}
|
||||||
|
|
||||||
/// A tuple takes its minimum to its full length of positive numbers, whole ones when asked.
|
/// A tuple takes its minimum to its full length of positive numbers, whole ones when asked.
|
||||||
#[test]
|
#[test]
|
||||||
fn tuple_takes_two_or_three_positive_numbers() {
|
fn tuple_takes_two_or_three_positive_numbers() {
|
||||||
|
|||||||
@@ -178,12 +178,13 @@ fn form(app: &mut ModelApp, ui: &mut egui::Ui, t: &Tokens) {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
// The nodes and elements picked in the viewport, for the label lists' Use selection.
|
// The nodes, elements and element faces picked in the viewport, for Use selection.
|
||||||
let mut picked = crate::form::Picked::default();
|
let mut picked = crate::form::Picked::default();
|
||||||
for item in app.pick.selection.items() {
|
for item in app.pick.selection.items() {
|
||||||
match item {
|
match item {
|
||||||
selection::Item::Node(n) => picked.nodes.push(u64::from(*n)),
|
selection::Item::Node(n) => picked.nodes.push(u64::from(*n)),
|
||||||
selection::Item::Element(e) => picked.elements.push(u64::from(*e)),
|
selection::Item::Element(e) => picked.elements.push(u64::from(*e)),
|
||||||
|
selection::Item::Face(f) => picked.faces.push((u64::from(f.element), u64::from(f.face))),
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -188,6 +188,13 @@ fn scalar(f: &Field, v: &Value, units: UnitSystem) -> String {
|
|||||||
_ => crate::context::value_text(v),
|
_ => crate::context::value_text(v),
|
||||||
},
|
},
|
||||||
Kind::Rows { .. } => v.as_array().map_or_else(|| crate::context::value_text(v), |a| plural(a.len(), "row")),
|
Kind::Rows { .. } => v.as_array().map_or_else(|| crate::context::value_text(v), |a| plural(a.len(), "row")),
|
||||||
|
Kind::Sides(side) => match v
|
||||||
|
.as_array()
|
||||||
|
.and_then(|a| a.iter().map(|x| Some((x.get("element")?.as_u64()?, x.get(side.key())?.as_u64()?))).collect::<Option<Vec<_>>>())
|
||||||
|
{
|
||||||
|
Some(l) => crate::fields::format_sides(&l),
|
||||||
|
None => crate::context::value_text(v),
|
||||||
|
},
|
||||||
Kind::Labels(_) => match v.as_array().and_then(|a| a.iter().map(Value::as_u64).collect::<Option<Vec<_>>>()) {
|
Kind::Labels(_) => match v.as_array().and_then(|a| a.iter().map(Value::as_u64).collect::<Option<Vec<_>>>()) {
|
||||||
Some(l) => crate::fields::format_labels(&l),
|
Some(l) => crate::fields::format_labels(&l),
|
||||||
None => crate::context::value_text(v),
|
None => crate::context::value_text(v),
|
||||||
|
|||||||
@@ -228,6 +228,44 @@ pub fn parse_labels(text: &str, max: usize) -> Option<Vec<u64>> {
|
|||||||
Some(out)
|
Some(out)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Element faces or edges as typed: `element:side`, runs of consecutive elements with the same
|
||||||
|
/// side as `a-b:side` ("1-5:3, 8:1").
|
||||||
|
pub fn format_sides(sides: &[(u64, u64)]) -> String {
|
||||||
|
let mut out: Vec<String> = Vec::new();
|
||||||
|
let mut i = 0;
|
||||||
|
while let Some(&(a, side)) = sides.get(i) {
|
||||||
|
let mut j = i;
|
||||||
|
while sides.get(j + 1).is_some_and(|&(n, s)| s == side && Some(n) == sides.get(j).and_then(|(m, _)| m.checked_add(1))) {
|
||||||
|
j += 1;
|
||||||
|
}
|
||||||
|
let b = sides.get(j).map_or(a, |(n, _)| *n);
|
||||||
|
out.push(match j - i {
|
||||||
|
0 => format!("{a}:{side}"),
|
||||||
|
1 => format!("{a}:{side}, {b}:{side}"),
|
||||||
|
_ => format!("{a}-{b}:{side}"),
|
||||||
|
});
|
||||||
|
i = j + 1;
|
||||||
|
}
|
||||||
|
out.join(", ")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Element faces or edges typed as `element:side` or `a-b:side`, separated by commas or spaces;
|
||||||
|
/// `None` when an element is not a label from 1, a side not 1–255, or there would be more than
|
||||||
|
/// `max`.
|
||||||
|
pub fn parse_sides(text: &str, max: usize) -> Option<Vec<(u64, u64)>> {
|
||||||
|
let mut out = Vec::new();
|
||||||
|
for part in text.split(|c: char| c == ',' || c.is_whitespace()).filter(|p| !p.is_empty()) {
|
||||||
|
let (elements, side) = part.split_once(':')?;
|
||||||
|
let side = side.parse::<u64>().ok().filter(|s| (1..=u64::from(u8::MAX)).contains(s))?;
|
||||||
|
let labels = parse_labels(elements, max.saturating_sub(out.len()))?;
|
||||||
|
if labels.is_empty() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
out.extend(labels.into_iter().map(|e| (e, side)));
|
||||||
|
}
|
||||||
|
Some(out)
|
||||||
|
}
|
||||||
|
|
||||||
/// A short label before a field ("X"), dim.
|
/// A short label before a field ("X"), dim.
|
||||||
pub fn caption(ui: &mut Ui, s: &Style, text: &str) {
|
pub fn caption(ui: &mut Ui, s: &Style, text: &str) {
|
||||||
ui.label(RichText::new(text).small().color(s.dim));
|
ui.label(RichText::new(text).small().color(s.dim));
|
||||||
@@ -257,6 +295,18 @@ mod tests {
|
|||||||
assert_eq!(parse_labels(&format_labels(&[3, 4, 5, 9]), 100), Some(vec![3, 4, 5, 9]));
|
assert_eq!(parse_labels(&format_labels(&[3, 4, 5, 9]), 100), Some(vec![3, 4, 5, 9]));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn sides_print_and_parse_as_ranges() {
|
||||||
|
let s = [(1, 3), (2, 3), (3, 3), (5, 1), (6, 2), (7, 2)];
|
||||||
|
assert_eq!(format_sides(&s), "1-3:3, 5:1, 6:2, 7:2");
|
||||||
|
assert_eq!(parse_sides(&format_sides(&s), 100).as_deref(), Some(&s[..]));
|
||||||
|
assert_eq!(parse_sides("4:1 9-10:6", 100), Some(vec![(4, 1), (9, 6), (10, 6)]));
|
||||||
|
assert_eq!(parse_sides("", 100), Some(vec![]));
|
||||||
|
for bad in ["4", "4:", ":3", "0:1", "4:0", "4:256", "1-200:1", "x:1"] {
|
||||||
|
assert_eq!(parse_sides(bad, 100), None, "{bad}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn numbers_print_as_typed() {
|
fn numbers_print_as_typed() {
|
||||||
assert_eq!(format_number(210000.0), "210000");
|
assert_eq!(format_number(210000.0), "210000");
|
||||||
|
|||||||
@@ -47,6 +47,8 @@ pub struct Context<'a> {
|
|||||||
pub struct Picked {
|
pub struct Picked {
|
||||||
pub nodes: Vec<u64>,
|
pub nodes: Vec<u64>,
|
||||||
pub elements: Vec<u64>,
|
pub elements: Vec<u64>,
|
||||||
|
/// Element faces: (element, local face).
|
||||||
|
pub faces: Vec<(u64, u64)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// What a frame of the form did.
|
/// What a frame of the form did.
|
||||||
@@ -333,6 +335,7 @@ impl Form<'_> {
|
|||||||
}
|
}
|
||||||
Kind::Json => self.json(ui, id, value, f.hint),
|
Kind::Json => self.json(ui, id, value, f.hint),
|
||||||
Kind::Labels(of) => self.labels(ui, id, value, of, f.hint),
|
Kind::Labels(of) => self.labels(ui, id, value, of, f.hint),
|
||||||
|
Kind::Sides(side) => self.sides(ui, id, value, side, f.hint),
|
||||||
Kind::Points { columns, max, .. } => self.points(ui, id, value, f, columns, max),
|
Kind::Points { columns, max, .. } => self.points(ui, id, value, f, columns, max),
|
||||||
Kind::Multi(options) => self.multi(ui, value, options),
|
Kind::Multi(options) => self.multi(ui, value, options),
|
||||||
Kind::NumberOrAuto { .. } => self.number_or_auto(ui, id, value, f),
|
Kind::NumberOrAuto { .. } => self.number_or_auto(ui, id, value, f),
|
||||||
@@ -739,6 +742,58 @@ impl Form<'_> {
|
|||||||
out
|
out
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Element faces or edges typed as `element:side` ranges ([`fields::parse_sides`]); for faces,
|
||||||
|
/// Use selection takes the faces picked in the viewport.
|
||||||
|
fn sides(&mut self, ui: &mut Ui, id: Id, value: Option<&Value>, side: schema::Side, hint: &str) -> Option<Value> {
|
||||||
|
let key = side.key();
|
||||||
|
let current: Option<Vec<(u64, u64)>> = match value {
|
||||||
|
None | Some(Value::Null) => Some(Vec::new()),
|
||||||
|
Some(v) => v.as_array().and_then(|a| a.iter().map(|x| Some((x.get("element")?.as_u64()?, x.get(key)?.as_u64()?))).collect()),
|
||||||
|
};
|
||||||
|
let Some(current) = current else {
|
||||||
|
self.json_only(ui);
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
let to_value =
|
||||||
|
|l: &[(u64, u64)]| if l.is_empty() { Value::Null } else { Value::Array(l.iter().map(|(e, s)| json!({"element": e, key: s})).collect()) };
|
||||||
|
let shown = fields::format_sides(¤t);
|
||||||
|
let mut text = ui.data(|d| d.get_temp::<(String, String)>(id)).filter(|(_, over)| *over == shown).map_or_else(|| shown.clone(), |(t, _)| t);
|
||||||
|
let mut out = None;
|
||||||
|
let mut over = shown.clone();
|
||||||
|
ui.vertical(|ui| {
|
||||||
|
let parsed = fields::parse_sides(&text, schema::MAX_LABELS);
|
||||||
|
let mut edit =
|
||||||
|
egui::TextEdit::singleline(&mut text).id(id).desired_width(fill(ui)).hint_text(RichText::new(hint).italics().color(self.style.dim));
|
||||||
|
if parsed.is_none() {
|
||||||
|
edit = edit.text_color(self.style.error);
|
||||||
|
self.out.invalid += 1;
|
||||||
|
}
|
||||||
|
if ui.add(edit).changed()
|
||||||
|
&& let Some(l) = fields::parse_sides(&text, schema::MAX_LABELS)
|
||||||
|
{
|
||||||
|
over = fields::format_sides(&l);
|
||||||
|
out = Some(to_value(&l));
|
||||||
|
}
|
||||||
|
if side == schema::Side::Face {
|
||||||
|
let picked = self.cx.picked.faces.clone();
|
||||||
|
let label = format!("Use selection ({} face{})", picked.len(), if picked.len() == 1 { "" } else { "s" });
|
||||||
|
let tip = "The element faces picked in the viewport (Select: Element faces)";
|
||||||
|
if ui
|
||||||
|
.add_enabled(!picked.is_empty(), egui::Button::new(RichText::new(label).small()))
|
||||||
|
.on_hover_text(tip)
|
||||||
|
.on_disabled_hover_text(tip)
|
||||||
|
.clicked()
|
||||||
|
{
|
||||||
|
text = fields::format_sides(&picked);
|
||||||
|
over = text.clone();
|
||||||
|
out = Some(to_value(&picked));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
ui.data_mut(|d| d.insert_temp(id, (text, over)));
|
||||||
|
out
|
||||||
|
}
|
||||||
|
|
||||||
/// A list of points: a row of x, y, z each, removable, and Add point.
|
/// A list of points: a row of x, y, z each, removable, and Add point.
|
||||||
fn points(&mut self, ui: &mut Ui, id: Id, value: Option<&Value>, f: &Field, columns: &[&str], max: usize) -> Option<Value> {
|
fn points(&mut self, ui: &mut Ui, id: Id, value: Option<&Value>, f: &Field, columns: &[&str], max: usize) -> Option<Value> {
|
||||||
let current: Vec<Value> = match value {
|
let current: Vec<Value> = match value {
|
||||||
@@ -1312,7 +1367,7 @@ mod tests {
|
|||||||
attempted: false,
|
attempted: false,
|
||||||
refused: None,
|
refused: None,
|
||||||
problems: &[],
|
problems: &[],
|
||||||
picked: Picked { nodes: vec![4, 5], elements: Vec::new() },
|
picked: Picked { nodes: vec![4, 5], ..Picked::default() },
|
||||||
};
|
};
|
||||||
show(ui, &Tokens::get(false), &cx, form, &mut params);
|
show(ui, &Tokens::get(false), &cx, form, &mut params);
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in new issue
Block a user