fix(properties): preserve literal text
Separate numeric fields from names and content so only numeric property values are evaluated as expressions. Refs #809
This commit is contained in:
commit
5e62da464e
15 changed files with 107 additions and 31 deletions
|
|
@ -433,6 +433,9 @@ pub fn eval_to_string(input: &str) -> String {
|
||||||
let mut found = false;
|
let mut found = false;
|
||||||
// Try longest substring first (greedy)
|
// Try longest substring first (greedy)
|
||||||
for end in (i + 1..=len).rev() {
|
for end in (i + 1..=len).rev() {
|
||||||
|
if !starts_and_ends_on_word_boundary(&chars, i, end) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
let sub: String = chars[i..end].iter().collect();
|
let sub: String = chars[i..end].iter().collect();
|
||||||
if let Some(v) = eval_number(&sub) {
|
if let Some(v) = eval_number(&sub) {
|
||||||
result.push_str(&format!("{}", v));
|
result.push_str(&format!("{}", v));
|
||||||
|
|
@ -449,6 +452,37 @@ pub fn eval_to_string(input: &str) -> String {
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Rejects expression candidates cut from a surrounding identifier.
|
||||||
|
fn starts_and_ends_on_word_boundary(chars: &[char], start: usize, end: usize) -> bool {
|
||||||
|
fn is_word(c: char) -> bool {
|
||||||
|
c.is_alphanumeric() || c == '_'
|
||||||
|
}
|
||||||
|
fn starts_ident(c: char) -> bool {
|
||||||
|
c.is_alphabetic() || c == '_'
|
||||||
|
}
|
||||||
|
|
||||||
|
// First token is an identifier: a word character just before it means this
|
||||||
|
// is the tail of a longer word rather than a name of its own.
|
||||||
|
if starts_ident(chars[start]) && start > 0 && is_word(chars[start - 1]) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Last token is an identifier when the trailing run of word characters
|
||||||
|
// begins with a letter or `_`. A word character just after `end` means that
|
||||||
|
// identifier continues past the candidate.
|
||||||
|
if end < chars.len() && is_word(chars[end]) {
|
||||||
|
let mut run = end;
|
||||||
|
while run > start && is_word(chars[run - 1]) {
|
||||||
|
run -= 1;
|
||||||
|
}
|
||||||
|
if run < end && starts_ident(chars[run]) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
// ── Tests ───────────────────────────────────────────────────────────
|
// ── Tests ───────────────────────────────────────────────────────────
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|
@ -615,6 +649,27 @@ mod tests {
|
||||||
assert_eq!(eval_to_string("abs(-5),ceil(3.1)"), "5,4");
|
assert_eq!(eval_to_string("abs(-5),ceil(3.1)"), "5,4");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn embedded_identifier_inside_a_word_is_left_alone() {
|
||||||
|
// Known names must stay unchanged inside identifiers.
|
||||||
|
assert_eq!(eval_to_string("ASPIRATORE"), "ASPIRATORE");
|
||||||
|
assert_eq!(eval_to_string("PIRATE"), "PIRATE");
|
||||||
|
assert_eq!(eval_to_string("copies"), "copies");
|
||||||
|
assert_eq!(eval_to_string("TOPI"), "TOPI");
|
||||||
|
assert_eq!(eval_to_string("pi_2"), "pi_2");
|
||||||
|
assert_eq!(eval_to_string("şpiş"), "şpiş");
|
||||||
|
assert_eq!(eval_to_string("копияpi"), "копияpi");
|
||||||
|
// A standalone `pi` still is a constant.
|
||||||
|
assert_eq!(
|
||||||
|
eval_to_string("pipe,pi"),
|
||||||
|
format!("pipe,{}", std::f64::consts::PI)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
eval_to_string("2*pi,0"),
|
||||||
|
format!("{},0", 2.0 * std::f64::consts::PI)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn embedded_expr_no_evaluables() {
|
fn embedded_expr_no_evaluables() {
|
||||||
assert_eq!(eval_to_string("abc,def"), "abc,def");
|
assert_eq!(eval_to_string("abc,def"), "abc,def");
|
||||||
|
|
|
||||||
|
|
@ -837,7 +837,7 @@ impl OpenCADStudio {
|
||||||
geom.props.push(crate::scene::model::object::Property {
|
geom.props.push(crate::scene::model::object::Property {
|
||||||
label: t!("Frozen Layers").into_owned(),
|
label: t!("Frozen Layers").into_owned(),
|
||||||
field: "frozen_layers",
|
field: "frozen_layers",
|
||||||
value: crate::scene::model::object::PropValue::EditText(
|
value: crate::scene::model::object::PropValue::PlainText(
|
||||||
frozen_names.join(", "),
|
frozen_names.join(", "),
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
|
|
@ -903,15 +903,17 @@ impl OpenCADStudio {
|
||||||
.filter(|n| !n.is_empty())
|
.filter(|n| !n.is_empty())
|
||||||
.collect();
|
.collect();
|
||||||
if !dim_style_names.is_empty() {
|
if !dim_style_names.is_empty() {
|
||||||
// Current style is already shown as EditText in the geom section;
|
// Current style is already shown as text in the geom section;
|
||||||
// replace/upgrade it to a Choice if we have a list.
|
// replace/upgrade it to a Choice if we have a list.
|
||||||
if let Some(geom) = sections.last_mut() {
|
if let Some(geom) = sections.last_mut() {
|
||||||
// Find and replace the style_name EditText with a Choice.
|
// Replace the style name with a choice.
|
||||||
if let Some(prop) =
|
if let Some(prop) =
|
||||||
geom.props.iter_mut().find(|p| p.field == "style_name")
|
geom.props.iter_mut().find(|p| p.field == "style_name")
|
||||||
{
|
{
|
||||||
let current = match &prop.value {
|
let current = match &prop.value {
|
||||||
crate::scene::model::object::PropValue::EditText(s) => s.clone(),
|
crate::scene::model::object::PropValue::PlainText(s) => {
|
||||||
|
s.clone()
|
||||||
|
}
|
||||||
_ => String::new(),
|
_ => String::new(),
|
||||||
};
|
};
|
||||||
prop.value = crate::scene::model::object::PropValue::Choice {
|
prop.value = crate::scene::model::object::PropValue::Choice {
|
||||||
|
|
@ -1044,7 +1046,7 @@ impl OpenCADStudio {
|
||||||
.find(|p| p.field == "dimension_style")
|
.find(|p| p.field == "dimension_style")
|
||||||
{
|
{
|
||||||
let cur = match &p.value {
|
let cur = match &p.value {
|
||||||
crate::scene::model::object::PropValue::EditText(s) => {
|
crate::scene::model::object::PropValue::PlainText(s) => {
|
||||||
s.clone()
|
s.clone()
|
||||||
}
|
}
|
||||||
_ => ld.dimension_style.clone(),
|
_ => ld.dimension_style.clone(),
|
||||||
|
|
@ -1979,6 +1981,7 @@ fn make_sections_read_only(
|
||||||
let text = match &property.value {
|
let text = match &property.value {
|
||||||
PropValue::ReadOnly(value)
|
PropValue::ReadOnly(value)
|
||||||
| PropValue::EditText(value)
|
| PropValue::EditText(value)
|
||||||
|
| PropValue::PlainText(value)
|
||||||
| PropValue::LayerChoice(value)
|
| PropValue::LayerChoice(value)
|
||||||
| PropValue::LinetypeChoice(value)
|
| PropValue::LinetypeChoice(value)
|
||||||
| PropValue::HatchPatternChoice(value) => value.clone(),
|
| PropValue::HatchPatternChoice(value) => value.clone(),
|
||||||
|
|
@ -2159,6 +2162,9 @@ fn merge_prop_value(
|
||||||
(PropValue::EditText(_), PropValue::EditText(_)) => {
|
(PropValue::EditText(_), PropValue::EditText(_)) => {
|
||||||
PropValue::EditText(VARIES_LABEL.into())
|
PropValue::EditText(VARIES_LABEL.into())
|
||||||
}
|
}
|
||||||
|
(PropValue::PlainText(_), PropValue::PlainText(_)) => {
|
||||||
|
PropValue::PlainText(VARIES_LABEL.into())
|
||||||
|
}
|
||||||
(PropValue::ReadOnly(_), PropValue::ReadOnly(_)) => {
|
(PropValue::ReadOnly(_), PropValue::ReadOnly(_)) => {
|
||||||
PropValue::ReadOnly(VARIES_LABEL.into())
|
PropValue::ReadOnly(VARIES_LABEL.into())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ use crate::scene::pick::grip::{
|
||||||
};
|
};
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
use crate::plugin::v4_support;
|
use crate::plugin::v4_support;
|
||||||
use crate::scene::model::object::GripApply;
|
use crate::scene::model::object::{GripApply, PropValue};
|
||||||
use crate::scene::{
|
use crate::scene::{
|
||||||
self, hover_id, CubeRegion, Scene, VIEWCUBE_DRAW_PX, VIEWCUBE_PAD, VIEWCUBE_PX,
|
self, hover_id, CubeRegion, Scene, VIEWCUBE_DRAW_PX, VIEWCUBE_PAD, VIEWCUBE_PX,
|
||||||
};
|
};
|
||||||
|
|
@ -2168,17 +2168,24 @@ pub(super) fn on_tab_close(&mut self, idx: usize) -> Task<Message> {
|
||||||
self.tabs[i].properties.active_field = None;
|
self.tabs[i].properties.active_field = None;
|
||||||
let handles = self.property_target_handles(i);
|
let handles = self.property_target_handles(i);
|
||||||
if !handles.is_empty() {
|
if !handles.is_empty() {
|
||||||
|
let evaluates_expression = self.tabs[i]
|
||||||
|
.properties
|
||||||
|
.sections
|
||||||
|
.iter()
|
||||||
|
.flat_map(|section| section.props.iter())
|
||||||
|
.find(|property| property.field == field)
|
||||||
|
.is_some_and(|property| {
|
||||||
|
matches!(property.value, PropValue::EditText(_))
|
||||||
|
});
|
||||||
if let Some(raw_val) = self.tabs[i]
|
if let Some(raw_val) = self.tabs[i]
|
||||||
.properties
|
.properties
|
||||||
.edit_buf
|
.edit_buf
|
||||||
.remove(&crate::ui::properties::FieldKey::Geom(field))
|
.remove(&crate::ui::properties::FieldKey::Geom(field))
|
||||||
{
|
{
|
||||||
// Block names are free-form text — a name like "10-5"
|
let val = if evaluates_expression {
|
||||||
// must not be arithmetic-evaluated.
|
|
||||||
let val = if field == "block" {
|
|
||||||
raw_val
|
|
||||||
} else {
|
|
||||||
crate::app::expr_eval::eval_to_string(&raw_val)
|
crate::app::expr_eval::eval_to_string(&raw_val)
|
||||||
|
} else {
|
||||||
|
raw_val
|
||||||
};
|
};
|
||||||
if matches!(field, "current_fit_point" | "current_control_point") {
|
if matches!(field, "current_fit_point" | "current_control_point") {
|
||||||
let count = handles
|
let count = handles
|
||||||
|
|
|
||||||
|
|
@ -417,17 +417,17 @@ impl PropertyEditable for AttributeDefinition {
|
||||||
Property {
|
Property {
|
||||||
label: t!("Tag").into_owned(),
|
label: t!("Tag").into_owned(),
|
||||||
field: "att_tag",
|
field: "att_tag",
|
||||||
value: PropValue::EditText(self.tag.clone()),
|
value: PropValue::PlainText(self.tag.clone()),
|
||||||
},
|
},
|
||||||
Property {
|
Property {
|
||||||
label: t!("Prompt").into_owned(),
|
label: t!("Prompt").into_owned(),
|
||||||
field: "att_prompt",
|
field: "att_prompt",
|
||||||
value: PropValue::EditText(self.prompt.clone()),
|
value: PropValue::PlainText(self.prompt.clone()),
|
||||||
},
|
},
|
||||||
Property {
|
Property {
|
||||||
label: t!("Value").into_owned(),
|
label: t!("Value").into_owned(),
|
||||||
field: "att_default",
|
field: "att_default",
|
||||||
value: PropValue::EditText(self.default_value.clone()),
|
value: PropValue::PlainText(self.default_value.clone()),
|
||||||
},
|
},
|
||||||
Property {
|
Property {
|
||||||
label: t!("Style").into_owned(),
|
label: t!("Style").into_owned(),
|
||||||
|
|
@ -698,13 +698,13 @@ impl PropertyEditable for AttributeEntity {
|
||||||
Property {
|
Property {
|
||||||
label: t!("Tag").into_owned(),
|
label: t!("Tag").into_owned(),
|
||||||
field: "atte_tag",
|
field: "atte_tag",
|
||||||
value: PropValue::EditText(self.tag.clone()),
|
value: PropValue::PlainText(self.tag.clone()),
|
||||||
},
|
},
|
||||||
ro(t!("Prompt").as_ref(), "atte_prompt", String::new()),
|
ro(t!("Prompt").as_ref(), "atte_prompt", String::new()),
|
||||||
Property {
|
Property {
|
||||||
label: t!("Value").into_owned(),
|
label: t!("Value").into_owned(),
|
||||||
field: "atte_val",
|
field: "atte_val",
|
||||||
value: PropValue::EditText(self.value.clone()),
|
value: PropValue::PlainText(self.value.clone()),
|
||||||
},
|
},
|
||||||
Property {
|
Property {
|
||||||
label: t!("Style").into_owned(),
|
label: t!("Style").into_owned(),
|
||||||
|
|
|
||||||
|
|
@ -19,19 +19,19 @@ fn base_props(base: &DimensionBase) -> Vec<crate::scene::model::object::Property
|
||||||
crate::scene::model::object::Property {
|
crate::scene::model::object::Property {
|
||||||
label: t!("Text").into_owned(),
|
label: t!("Text").into_owned(),
|
||||||
field: "text",
|
field: "text",
|
||||||
value: crate::scene::model::object::PropValue::EditText(base.text.clone()),
|
value: crate::scene::model::object::PropValue::PlainText(base.text.clone()),
|
||||||
},
|
},
|
||||||
crate::scene::model::object::Property {
|
crate::scene::model::object::Property {
|
||||||
label: t!("User Text").into_owned(),
|
label: t!("User Text").into_owned(),
|
||||||
field: "user_text",
|
field: "user_text",
|
||||||
value: crate::scene::model::object::PropValue::EditText(
|
value: crate::scene::model::object::PropValue::PlainText(
|
||||||
base.user_text.clone().unwrap_or_default(),
|
base.user_text.clone().unwrap_or_default(),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
crate::scene::model::object::Property {
|
crate::scene::model::object::Property {
|
||||||
label: t!("Style").into_owned(),
|
label: t!("Style").into_owned(),
|
||||||
field: "style_name",
|
field: "style_name",
|
||||||
value: crate::scene::model::object::PropValue::EditText(base.style_name.clone()),
|
value: crate::scene::model::object::PropValue::PlainText(base.style_name.clone()),
|
||||||
},
|
},
|
||||||
edit(t!("Text X").as_ref(), "text_x", base.text_middle_point.x),
|
edit(t!("Text X").as_ref(), "text_x", base.text_middle_point.x),
|
||||||
edit(t!("Text Y").as_ref(), "text_y", base.text_middle_point.y),
|
edit(t!("Text Y").as_ref(), "text_y", base.text_middle_point.y),
|
||||||
|
|
@ -4129,4 +4129,3 @@ mod arch_format_tests {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,7 @@ fn text_prop(label: &str, field: &'static str, value: &str) -> Property {
|
||||||
Property {
|
Property {
|
||||||
label: label.into(),
|
label: label.into(),
|
||||||
field,
|
field,
|
||||||
value: PropValue::EditText(value.to_string()),
|
value: PropValue::PlainText(value.to_string()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -291,7 +291,7 @@ fn properties(leader: &Leader) -> Vec<PropSection> {
|
||||||
Property {
|
Property {
|
||||||
label: t!("Dim style").into_owned(),
|
label: t!("Dim style").into_owned(),
|
||||||
field: "dimension_style",
|
field: "dimension_style",
|
||||||
value: PropValue::EditText(leader.dimension_style.clone()),
|
value: PropValue::PlainText(leader.dimension_style.clone()),
|
||||||
},
|
},
|
||||||
choice_prop(
|
choice_prop(
|
||||||
t!("Type").as_ref(),
|
t!("Type").as_ref(),
|
||||||
|
|
|
||||||
|
|
@ -337,7 +337,7 @@ impl PropertyEditable for MLine {
|
||||||
Property {
|
Property {
|
||||||
label: t!("Style").into_owned(),
|
label: t!("Style").into_owned(),
|
||||||
field: "ml_style",
|
field: "ml_style",
|
||||||
value: PropValue::EditText(self.style_name.clone()),
|
value: PropValue::PlainText(self.style_name.clone()),
|
||||||
},
|
},
|
||||||
Property {
|
Property {
|
||||||
label: t!("Style justification").into_owned(),
|
label: t!("Style justification").into_owned(),
|
||||||
|
|
|
||||||
|
|
@ -260,7 +260,7 @@ fn properties(t: &MText, text_style_names: &[String]) -> Vec<PropSection> {
|
||||||
Property {
|
Property {
|
||||||
label: t!("Contents").into_owned(),
|
label: t!("Contents").into_owned(),
|
||||||
field: "content",
|
field: "content",
|
||||||
value: PropValue::EditText(t.value.clone()),
|
value: PropValue::PlainText(t.value.clone()),
|
||||||
},
|
},
|
||||||
Property {
|
Property {
|
||||||
label: t!("Style").into_owned(),
|
label: t!("Style").into_owned(),
|
||||||
|
|
|
||||||
|
|
@ -715,7 +715,7 @@ fn properties(ml: &MultiLeader) -> Vec<PropSection> {
|
||||||
Property {
|
Property {
|
||||||
label: t!("Contents").into_owned(),
|
label: t!("Contents").into_owned(),
|
||||||
field: "text_string",
|
field: "text_string",
|
||||||
value: PropValue::EditText(ctx.text_string.clone()),
|
value: PropValue::PlainText(ctx.text_string.clone()),
|
||||||
},
|
},
|
||||||
// Text-style name resolved from text_style_handle by the panel builder.
|
// Text-style name resolved from text_style_handle by the panel builder.
|
||||||
ro(t!("Text style").as_ref(), "text_style_handle", "Standard"),
|
ro(t!("Text style").as_ref(), "text_style_handle", "Standard"),
|
||||||
|
|
|
||||||
|
|
@ -279,7 +279,7 @@ fn properties(t: &Text, text_style_names: &[String]) -> Vec<PropSection> {
|
||||||
Property {
|
Property {
|
||||||
label: t!("Contents").into_owned(),
|
label: t!("Contents").into_owned(),
|
||||||
field: "content",
|
field: "content",
|
||||||
value: PropValue::EditText(t.value.clone()),
|
value: PropValue::PlainText(t.value.clone()),
|
||||||
},
|
},
|
||||||
Property {
|
Property {
|
||||||
label: t!("Style").into_owned(),
|
label: t!("Style").into_owned(),
|
||||||
|
|
|
||||||
2
src/scene/cache/properties.rs
vendored
2
src/scene/cache/properties.rs
vendored
|
|
@ -86,7 +86,7 @@ pub fn general_section(entity: &EntityType) -> PropSection {
|
||||||
Property {
|
Property {
|
||||||
label: t!("Hyperlink").into_owned(),
|
label: t!("Hyperlink").into_owned(),
|
||||||
field: "hyperlink",
|
field: "hyperlink",
|
||||||
value: PropValue::EditText(hyperlink),
|
value: PropValue::PlainText(hyperlink),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -8,8 +8,10 @@ use glam::DVec3;
|
||||||
pub enum PropValue {
|
pub enum PropValue {
|
||||||
/// Read-only display text.
|
/// Read-only display text.
|
||||||
ReadOnly(String),
|
ReadOnly(String),
|
||||||
/// Editable numeric/text field.
|
/// Editable numeric field.
|
||||||
EditText(String),
|
EditText(String),
|
||||||
|
/// Editable text that must not be expression-evaluated.
|
||||||
|
PlainText(String),
|
||||||
/// Layer name — rendered as a combo_box.
|
/// Layer name — rendered as a combo_box.
|
||||||
LayerChoice(String),
|
LayerChoice(String),
|
||||||
/// Generic string choice rendered as a combo_box.
|
/// Generic string choice rendered as a combo_box.
|
||||||
|
|
|
||||||
|
|
@ -417,6 +417,7 @@ impl Scene {
|
||||||
])
|
])
|
||||||
} else {
|
} else {
|
||||||
match prop.value {
|
match prop.value {
|
||||||
|
PropValue::PlainText(_) => QSelectValueEditor::Text,
|
||||||
PropValue::ReadOnly(ref value)
|
PropValue::ReadOnly(ref value)
|
||||||
| PropValue::EditText(ref value) => {
|
| PropValue::EditText(ref value) => {
|
||||||
let field = prop.field.to_ascii_lowercase();
|
let field = prop.field.to_ascii_lowercase();
|
||||||
|
|
@ -614,7 +615,9 @@ impl Scene {
|
||||||
.flat_map(|s| s.props)
|
.flat_map(|s| s.props)
|
||||||
.find(|p| p.field == field)?;
|
.find(|p| p.field == field)?;
|
||||||
Some(match prop.value {
|
Some(match prop.value {
|
||||||
PropValue::ReadOnly(s) | PropValue::EditText(s) => s,
|
PropValue::ReadOnly(s)
|
||||||
|
| PropValue::EditText(s)
|
||||||
|
| PropValue::PlainText(s) => s,
|
||||||
PropValue::LayerChoice(s) => s,
|
PropValue::LayerChoice(s) => s,
|
||||||
PropValue::Choice { selected, .. } => selected,
|
PropValue::Choice { selected, .. } => selected,
|
||||||
PropValue::EditChoice { value, .. } => value,
|
PropValue::EditChoice { value, .. } => value,
|
||||||
|
|
|
||||||
|
|
@ -314,7 +314,9 @@ pub fn build_field_key_map(
|
||||||
for section in sections {
|
for section in sections {
|
||||||
for prop in §ion.props {
|
for prop in §ion.props {
|
||||||
let key = match &prop.value {
|
let key = match &prop.value {
|
||||||
PropValue::EditText(_) => Some(FieldKey::Geom(prop.field)),
|
PropValue::EditText(_) | PropValue::PlainText(_) => {
|
||||||
|
Some(FieldKey::Geom(prop.field))
|
||||||
|
}
|
||||||
PropValue::AttrText { tag, .. } => Some(FieldKey::Attr(tag.clone())),
|
PropValue::AttrText { tag, .. } => Some(FieldKey::Attr(tag.clone())),
|
||||||
_ => None,
|
_ => None,
|
||||||
};
|
};
|
||||||
|
|
@ -765,7 +767,9 @@ impl PropertiesPanel {
|
||||||
}
|
}
|
||||||
PropValue::BoolToggle { field, value } => render_bool_row(label, *field, *value),
|
PropValue::BoolToggle { field, value } => render_bool_row(label, *field, *value),
|
||||||
PropValue::Stepper { display, .. } => render_stepper_row(label, display),
|
PropValue::Stepper { display, .. } => render_stepper_row(label, display),
|
||||||
PropValue::EditText(val) => self.render_edit_row(label, prop.field, val),
|
PropValue::EditText(val) | PropValue::PlainText(val) => {
|
||||||
|
self.render_edit_row(label, prop.field, val)
|
||||||
|
}
|
||||||
PropValue::ReadOnly(val) if prop.field == "annotative_scale" => {
|
PropValue::ReadOnly(val) if prop.field == "annotative_scale" => {
|
||||||
render_annotative_scale_row(label, val)
|
render_annotative_scale_row(label, val)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue