forked from 1b-agent/opencadstudio-upstream
feat(properties): make inherited properties editable, align dropdowns, and switch transparency to EditChoice
Enable in-place editing of inherited properties in the Properties panel: - Transparency: Switch to PropValue::EditChoice with ByLayer and ByBlock presets to allow direct typing of any numeric percentage value (0..90%) without combo_box autocomplete interference. - Select-all-on-click: Integrate EditChoice text inputs into build_field_key_map with dedicated widget IDs, preserving click-to-select-all across editable fields. - Material: Provide bounded preset choices (ByLayer, ByBlock, Global, and active custom material) to avoid scanning large drawing dictionaries. - Plot style: Implement mode-aware behavior with interactive Choice in STB mode and ReadOnlyWithTooltip in CTB mode. - Read-only tooltips: Introduce PropValue::ReadOnlyWithTooltip to provide contextual explanations for locked fields while preserving mouse text selection and clipboard copying. - Dropdown alignment & unified borders: Fix EditChoice internal input/caret background clipping so the container's 1px neutral/active border surrounds the entire control identically to Lineweight and text inputs. - Document defaults & aggregation: Synchronize document header defaults when editing with No selection, and aggregate heterogeneous multi-selections (*VARIES*).
This commit is contained in:
1 parent
7794736271
commit
7215faae44
11 files changed
+461
-144
No files matched your search
+159
-42
@@ -273,18 +273,60 @@ impl OpenCADStudio {
|
||||
),
|
||||
),
|
||||
},
|
||||
read_only(t!("Transparency").as_ref(), "ByLayer".to_string()),
|
||||
Property {
|
||||
label: t!("Transparency").into_owned(),
|
||||
field: "transparency",
|
||||
value: PropValue::EditChoice {
|
||||
value: "ByLayer".to_string(),
|
||||
options: vec!["ByLayer".to_string(), "ByBlock".to_string()],
|
||||
},
|
||||
},
|
||||
read_only(t!("Thickness").as_ref(), format_length(header.thickness)),
|
||||
],
|
||||
},
|
||||
PropSection {
|
||||
title: t!("3D Visualization").into_owned(),
|
||||
props: vec![read_only(t!("Material").as_ref(), material)],
|
||||
props: vec![Property {
|
||||
label: t!("Material").into_owned(),
|
||||
field: "material",
|
||||
value: PropValue::Choice {
|
||||
selected: material.clone(),
|
||||
options: {
|
||||
let mut opts = vec![
|
||||
"ByLayer".to_string(),
|
||||
"ByBlock".to_string(),
|
||||
"Global".to_string(),
|
||||
];
|
||||
if !opts.contains(&material) && !material.is_empty() {
|
||||
opts.push(material.clone());
|
||||
}
|
||||
opts
|
||||
},
|
||||
},
|
||||
}],
|
||||
},
|
||||
PropSection {
|
||||
title: t!("Plot style").into_owned(),
|
||||
props: vec![
|
||||
read_only(t!("Plot style").as_ref(), plot_style.to_string()),
|
||||
Property {
|
||||
label: t!("Plot style").into_owned(),
|
||||
field: "plot_style",
|
||||
value: if header.plotstyle_mode {
|
||||
PropValue::Choice {
|
||||
selected: plot_style.to_string(),
|
||||
options: vec![
|
||||
"ByLayer".to_string(),
|
||||
"ByBlock".to_string(),
|
||||
"Normal".to_string(),
|
||||
],
|
||||
}
|
||||
} else {
|
||||
PropValue::ReadOnlyWithTooltip {
|
||||
value: plot_style.to_string(),
|
||||
tooltip: t!("Plot style is locked to color in Color-Dependent (CTB) mode").into_owned(),
|
||||
}
|
||||
},
|
||||
},
|
||||
read_only(t!("Plot style table").as_ref(), plot_table.clone()),
|
||||
read_only(
|
||||
t!("Plot table attached to").as_ref(),
|
||||
@@ -338,6 +380,23 @@ impl OpenCADStudio {
|
||||
];
|
||||
ui::PropertiesPanel {
|
||||
title: t!("No selection").into_owned(),
|
||||
choice_combos: sections
|
||||
.iter()
|
||||
.flat_map(|section| section.props.iter())
|
||||
.filter_map(|prop| match &prop.value {
|
||||
crate::scene::model::object::PropValue::Choice { options, .. } => Some((
|
||||
prop.field.to_string(),
|
||||
iced::widget::combo_box::State::new(
|
||||
options
|
||||
.iter()
|
||||
.cloned()
|
||||
.map(ui::properties::LocalizedChoice::new)
|
||||
.collect(),
|
||||
),
|
||||
)),
|
||||
_ => None,
|
||||
})
|
||||
.collect(),
|
||||
sections,
|
||||
layer_combo: iced::widget::combo_box::State::new(layer_names.clone()),
|
||||
linetype_combo: iced::widget::combo_box::State::new(
|
||||
@@ -381,17 +440,10 @@ impl OpenCADStudio {
|
||||
{
|
||||
let doc = &self.tabs[i].scene.document;
|
||||
let common = entity.common();
|
||||
let mat_names: Vec<String> = doc
|
||||
.objects
|
||||
.iter()
|
||||
.filter_map(|(_, o)| match o {
|
||||
acadrust::objects::ObjectType::Material(m) => Some(m.name.clone()),
|
||||
_ => None,
|
||||
})
|
||||
.collect();
|
||||
let selected = match common.material_flags {
|
||||
0 => "ByLayer".to_string(),
|
||||
1 => "ByBlock".to_string(),
|
||||
2 => "Global".to_string(),
|
||||
_ => common
|
||||
.material_handle
|
||||
.and_then(|mh| {
|
||||
@@ -402,10 +454,16 @@ impl OpenCADStudio {
|
||||
_ => None,
|
||||
})
|
||||
})
|
||||
.unwrap_or_else(|| "ByLayer".to_string()),
|
||||
.unwrap_or_else(|| "Global".to_string()),
|
||||
};
|
||||
let mut options = vec!["ByLayer".to_string(), "ByBlock".to_string()];
|
||||
options.extend(mat_names);
|
||||
let mut options = vec![
|
||||
"ByLayer".to_string(),
|
||||
"ByBlock".to_string(),
|
||||
"Global".to_string(),
|
||||
];
|
||||
if !options.contains(&selected) && !selected.is_empty() {
|
||||
options.push(selected.clone());
|
||||
}
|
||||
for section in sections.iter_mut() {
|
||||
if let Some(row) =
|
||||
section.props.iter_mut().find(|p| p.field == "material")
|
||||
@@ -665,32 +723,61 @@ impl OpenCADStudio {
|
||||
if self.tabs[i].scene.document.header.plotstyle_mode {
|
||||
let doc = &self.tabs[i].scene.document;
|
||||
let dict_h = doc.header.acad_plotstylename_dict_handle;
|
||||
let common = entity.common();
|
||||
let mut options = vec![
|
||||
"ByLayer".to_string(),
|
||||
"ByBlock".to_string(),
|
||||
"Normal".to_string(),
|
||||
];
|
||||
if let Some(dict) = crate::scene::annotative::as_dict(doc, dict_h) {
|
||||
let common = entity.common();
|
||||
let mut options = vec!["ByLayer".to_string(), "ByBlock".to_string()];
|
||||
options.extend(dict.entries.iter().map(|(n, _)| n.clone()));
|
||||
let selected = match common.plotstyle_flags {
|
||||
0 => "ByLayer".to_string(),
|
||||
1 => "ByBlock".to_string(),
|
||||
_ => common
|
||||
.plotstyle_handle
|
||||
.and_then(|ph| {
|
||||
for (n, _) in &dict.entries {
|
||||
if !options.contains(n) {
|
||||
options.push(n.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
let selected = match common.plotstyle_flags {
|
||||
0 => "ByLayer".to_string(),
|
||||
1 => "ByBlock".to_string(),
|
||||
2 => "Normal".to_string(),
|
||||
_ => common
|
||||
.plotstyle_handle
|
||||
.and_then(|ph| {
|
||||
crate::scene::annotative::as_dict(doc, dict_h).and_then(|dict| {
|
||||
dict.entries
|
||||
.iter()
|
||||
.find(|(_, h)| *h == ph)
|
||||
.map(|(n, _)| n.clone())
|
||||
})
|
||||
.unwrap_or_else(|| "ByLayer".to_string()),
|
||||
};
|
||||
for section in sections.iter_mut() {
|
||||
if let Some(row) =
|
||||
section.props.iter_mut().find(|p| p.field == "plot_style")
|
||||
{
|
||||
row.value = crate::scene::model::object::PropValue::Choice {
|
||||
selected: selected.clone(),
|
||||
options: options.clone(),
|
||||
};
|
||||
}
|
||||
})
|
||||
.unwrap_or_else(|| "ByLayer".to_string()),
|
||||
};
|
||||
for section in sections.iter_mut() {
|
||||
if let Some(row) =
|
||||
section.props.iter_mut().find(|p| p.field == "plot_style")
|
||||
{
|
||||
row.value = crate::scene::model::object::PropValue::Choice {
|
||||
selected: selected.clone(),
|
||||
options: options.clone(),
|
||||
};
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for section in sections.iter_mut() {
|
||||
if let Some(row) =
|
||||
section.props.iter_mut().find(|p| p.field == "plot_style")
|
||||
{
|
||||
let common = entity.common();
|
||||
let plot_style_str = match common.plotstyle_flags {
|
||||
0 => "ByLayer",
|
||||
1 => "ByBlock",
|
||||
2 => "Normal",
|
||||
_ => "ByColor",
|
||||
};
|
||||
row.value = crate::scene::model::object::PropValue::ReadOnlyWithTooltip {
|
||||
value: plot_style_str.to_string(),
|
||||
tooltip: t!("Plot style is locked to color in Color-Dependent (CTB) mode").into_owned(),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2017,6 +2104,7 @@ fn make_sections_read_only(
|
||||
{
|
||||
let text = match &property.value {
|
||||
PropValue::ReadOnly(value)
|
||||
| PropValue::ReadOnlyWithTooltip { value, .. }
|
||||
| PropValue::EditText(value)
|
||||
| PropValue::PlainText(value)
|
||||
| PropValue::LayerChoice(value)
|
||||
@@ -2199,20 +2287,36 @@ fn merge_prop_value(
|
||||
options: other_options,
|
||||
..
|
||||
},
|
||||
) if options == other_options => PropValue::Choice {
|
||||
selected: VARIES_LABEL.into(),
|
||||
options: options.clone(),
|
||||
},
|
||||
) => {
|
||||
let mut merged_options = options.clone();
|
||||
for opt in other_options {
|
||||
if !merged_options.contains(opt) {
|
||||
merged_options.push(opt.clone());
|
||||
}
|
||||
}
|
||||
PropValue::Choice {
|
||||
selected: VARIES_LABEL.into(),
|
||||
options: merged_options,
|
||||
}
|
||||
}
|
||||
(
|
||||
PropValue::EditChoice { options, .. },
|
||||
PropValue::EditChoice {
|
||||
options: other_options,
|
||||
..
|
||||
},
|
||||
) if options == other_options => PropValue::EditChoice {
|
||||
value: VARIES_LABEL.into(),
|
||||
options: options.clone(),
|
||||
},
|
||||
) => {
|
||||
let mut merged_options = options.clone();
|
||||
for opt in other_options {
|
||||
if !merged_options.contains(opt) {
|
||||
merged_options.push(opt.clone());
|
||||
}
|
||||
}
|
||||
PropValue::EditChoice {
|
||||
value: VARIES_LABEL.into(),
|
||||
options: merged_options,
|
||||
}
|
||||
}
|
||||
(PropValue::EditText(_), PropValue::EditText(_)) => {
|
||||
PropValue::EditText(VARIES_LABEL.into())
|
||||
}
|
||||
@@ -2222,6 +2326,19 @@ fn merge_prop_value(
|
||||
(PropValue::ReadOnly(_), PropValue::ReadOnly(_)) => {
|
||||
PropValue::ReadOnly(VARIES_LABEL.into())
|
||||
}
|
||||
(PropValue::ReadOnlyWithTooltip { tooltip, .. }, PropValue::ReadOnlyWithTooltip { .. }) => {
|
||||
PropValue::ReadOnlyWithTooltip {
|
||||
value: VARIES_LABEL.into(),
|
||||
tooltip: tooltip.clone(),
|
||||
}
|
||||
}
|
||||
(PropValue::ReadOnly(_), PropValue::ReadOnlyWithTooltip { tooltip, .. })
|
||||
| (PropValue::ReadOnlyWithTooltip { tooltip, .. }, PropValue::ReadOnly(_)) => {
|
||||
PropValue::ReadOnlyWithTooltip {
|
||||
value: VARIES_LABEL.into(),
|
||||
tooltip: tooltip.clone(),
|
||||
}
|
||||
}
|
||||
(PropValue::HatchPatternChoice(_), PropValue::HatchPatternChoice(_)) => {
|
||||
PropValue::HatchPatternChoice(VARIES_LABEL.into())
|
||||
}
|
||||
|
||||
@@ -2109,6 +2109,15 @@ pub(super) fn on_tab_close(&mut self, idx: usize) -> Task<Message> {
|
||||
self.tabs[i].scene.bump_entities(&changes);
|
||||
}
|
||||
}
|
||||
} else if field == "transparency" {
|
||||
for &handle in &handles {
|
||||
if self.tabs[i].scene.is_layer_locked(handle) {
|
||||
continue;
|
||||
}
|
||||
if let Some(entity) = self.tabs[i].scene.document.get_entity_mut(handle) {
|
||||
crate::scene::view::dispatch::apply_common_prop(entity, "transparency", &value);
|
||||
}
|
||||
}
|
||||
} else if field == "plot_style" {
|
||||
// Named plot-style pick: ByLayer / ByBlock clear the
|
||||
// handle; a named style resolves through the drawing's
|
||||
@@ -2139,6 +2148,10 @@ pub(super) fn on_tab_close(&mut self, idx: usize) -> Task<Message> {
|
||||
common.plotstyle_flags = 1;
|
||||
common.plotstyle_handle = None;
|
||||
}
|
||||
"Normal" => {
|
||||
common.plotstyle_flags = 2;
|
||||
common.plotstyle_handle = None;
|
||||
}
|
||||
_ => {
|
||||
if let Some(h) = ph {
|
||||
common.plotstyle_flags = 3;
|
||||
@@ -2179,6 +2192,10 @@ pub(super) fn on_tab_close(&mut self, idx: usize) -> Task<Message> {
|
||||
common.material_flags = 1;
|
||||
common.material_handle = None;
|
||||
}
|
||||
"Global" => {
|
||||
common.material_flags = 2;
|
||||
common.material_handle = None;
|
||||
}
|
||||
_ => {
|
||||
if let Some(h) = mat_handle {
|
||||
common.material_flags = 3;
|
||||
@@ -2233,11 +2250,60 @@ pub(super) fn on_tab_close(&mut self, idx: usize) -> Task<Message> {
|
||||
}
|
||||
self.invalidate_property_targets(i, &handles);
|
||||
self.tabs[i].dirty = true;
|
||||
self.tabs[i].properties.edit_choice_open = false;
|
||||
if field == "spline_method" {
|
||||
self.tabs[i].properties.prop_vertex = 0;
|
||||
self.tabs[i].properties.prop_vertex_indicator_active = false;
|
||||
}
|
||||
self.refresh_properties();
|
||||
} else {
|
||||
match field {
|
||||
"transparency" => {
|
||||
self.tabs[i].dirty = true;
|
||||
self.refresh_properties();
|
||||
}
|
||||
"material" => {
|
||||
let mat_handle: Option<acadrust::Handle> = self.tabs[i]
|
||||
.scene
|
||||
.document
|
||||
.objects
|
||||
.iter()
|
||||
.find_map(|(h, o)| match o {
|
||||
acadrust::objects::ObjectType::Material(m) if m.name == value => {
|
||||
Some(*h)
|
||||
}
|
||||
_ => None,
|
||||
});
|
||||
match value.as_str() {
|
||||
"ByLayer" | "ByBlock" | "Global" => {
|
||||
self.tabs[i].scene.document.header.current_material_handle =
|
||||
acadrust::Handle::NULL;
|
||||
}
|
||||
_ => {
|
||||
if let Some(h) = mat_handle {
|
||||
self.tabs[i].scene.document.header.current_material_handle =
|
||||
h;
|
||||
}
|
||||
}
|
||||
}
|
||||
self.tabs[i].dirty = true;
|
||||
self.refresh_properties();
|
||||
}
|
||||
"plot_style" => {
|
||||
match value.as_str() {
|
||||
"ByBlock" => {
|
||||
self.tabs[i].scene.document.header.current_plotstyle_type = 1
|
||||
}
|
||||
"Normal" | "ByColor" => {
|
||||
self.tabs[i].scene.document.header.current_plotstyle_type = 2
|
||||
}
|
||||
_ => self.tabs[i].scene.document.header.current_plotstyle_type = 0,
|
||||
}
|
||||
self.tabs[i].dirty = true;
|
||||
self.refresh_properties();
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
Task::none()
|
||||
}
|
||||
@@ -2470,6 +2536,12 @@ pub(super) fn on_tab_close(&mut self, idx: usize) -> Task<Message> {
|
||||
self.tabs[i].dirty = true;
|
||||
self.refresh_properties();
|
||||
}
|
||||
} else {
|
||||
let _ = self.tabs[i]
|
||||
.properties
|
||||
.edit_buf
|
||||
.remove(&crate::ui::properties::FieldKey::Geom(field));
|
||||
self.refresh_properties();
|
||||
}
|
||||
Task::none()
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user