fix(ui): refine empty drawing state

Show drawing defaults when nothing is selected and disable layout controls on Start.

Refs #514
Refs #515
This commit is contained in:
Hakan Seven committed 2026-07-27 17:37:17 +03:00
1 parent 759f2316aa
commit 4a30928f5a
8 files changed
+341 -57

No files matched your search

+191 -4
View File
@@ -142,11 +142,198 @@ impl OpenCADStudio {
let selected = self.tabs[i].scene.selected_entities();
let mut panel = match selected.len() {
0 => {
let sections = crate::entities::object_data::cached_document_sections(
&self.tabs[i].scene.object_data_cache,
);
use crate::scene::model::object::{PropSection, PropValue, Property};
let tab = &self.tabs[i];
let scene = &tab.scene;
let doc = &scene.document;
let header = &doc.header;
let camera = scene.camera.borrow();
let (viewport_width, viewport_height) = scene.selection.borrow().vp_size;
let aspect = if viewport_height > 0.0 {
viewport_width as f64 / viewport_height as f64
} else {
1.0
};
let view_height = camera.ortho_size() as f64 * 2.0;
let view_width = view_height * aspect;
let format_length = crate::entities::common::format_length;
let read_only = |label: &str, value: String| Property {
label: label.to_string(),
field: "drawing_property",
value: PropValue::ReadOnly(value),
};
let current_layer = if header.current_layer_name.is_empty() {
tab.active_layer.clone()
} else {
header.current_layer_name.clone()
};
let current_linetype = if !header.current_linetype_name.is_empty() {
header.current_linetype_name.clone()
} else if !header.current_linetype_handle.is_null() {
doc.line_types
.iter()
.find(|line_type| {
line_type.handle == header.current_linetype_handle
})
.map(|line_type| line_type.name.clone())
.unwrap_or_else(|| "ByLayer".to_string())
} else {
"ByLayer".to_string()
};
let material = if header.current_material_handle.is_null() {
"ByLayer".to_string()
} else {
doc.objects
.get(&header.current_material_handle)
.and_then(|object| match object {
acadrust::objects::ObjectType::Material(material) => {
Some(material.name.clone())
}
_ => None,
})
.unwrap_or_else(|| "ByLayer".to_string())
};
let plot_style = match header.current_plotstyle_type {
1 => "ByBlock",
2 => "ByColor",
3 => "ByObject",
_ => "ByLayer",
};
let layout_plot_table = doc.objects.values().find_map(|object| {
let acadrust::objects::ObjectType::Layout(layout) = object else {
return None;
};
(layout.name == scene.current_layout
&& !layout.plot_style_sheet.trim().is_empty())
.then(|| layout.plot_style_sheet.clone())
});
let plot_table = layout_plot_table
.or_else(|| {
(!header.stylesheet.trim().is_empty())
.then(|| header.stylesheet.clone())
})
.unwrap_or_else(|| "None".to_string());
let ucs_per_viewport = scene
.active_viewport
.and_then(|handle| doc.get_entity(handle))
.and_then(|entity| match entity {
acadrust::EntityType::Viewport(viewport) => {
Some(viewport.ucs_per_viewport)
}
_ => None,
})
.unwrap_or(false);
let ucs_name = tab
.active_ucs
.as_ref()
.map(|ucs| ucs.name.trim())
.filter(|name| !name.is_empty())
.unwrap_or("World")
.to_string();
let annotation_scale = if header.current_annotation_scale.trim().is_empty() {
"1:1".to_string()
} else {
header.current_annotation_scale.clone()
};
let sections = vec![
PropSection {
title: "General".to_string(),
props: vec![
read_only("AC Version", doc.version.as_str().to_string()),
Property {
label: "Color".to_string(),
field: "color",
value: PropValue::ColorChoice(header.current_entity_color),
},
Property {
label: "Layer".to_string(),
field: "layer",
value: PropValue::LayerChoice(current_layer),
},
Property {
label: "Linetype".to_string(),
field: "linetype",
value: PropValue::LinetypeChoice(current_linetype),
},
read_only(
"Linetype scale",
format_length(header.current_entity_linetype_scale),
),
Property {
label: "Lineweight".to_string(),
field: "line_weight",
value: PropValue::LwChoice(
acadrust::types::LineWeight::from_value(
header.current_line_weight,
),
),
},
read_only("Transparency", "ByLayer".to_string()),
read_only("Thickness", format_length(header.thickness)),
],
},
PropSection {
title: "3D Visualization".to_string(),
props: vec![read_only("Material", material)],
},
PropSection {
title: "Plot style".to_string(),
props: vec![
read_only("Plot style", plot_style.to_string()),
read_only("Plot style table", plot_table.clone()),
read_only(
"Plot table attached to",
if plot_table == "None" {
"None".to_string()
} else {
scene.current_layout.clone()
},
),
read_only(
"Plot table type",
if header.plotstyle_mode {
"Named plot styles".to_string()
} else {
"Color-dependent plot styles".to_string()
},
),
],
},
PropSection {
title: "View".to_string(),
props: vec![
read_only("Center X", format_length(camera.target.x)),
read_only("Center Y", format_length(camera.target.y)),
read_only("Center Z", format_length(camera.target.z)),
read_only("Height", format_length(view_height)),
read_only("Width", format_length(view_width)),
],
},
PropSection {
title: "Misc".to_string(),
props: vec![
read_only("Annotation scale", annotation_scale),
read_only(
"UCS icon On",
if self.show_ucs_icon { "Yes" } else { "No" }.to_string(),
),
read_only(
"UCS icon at origin",
if self.ucs_icon_at_origin { "Yes" } else { "No" }
.to_string(),
),
read_only(
"UCS per viewport",
if ucs_per_viewport { "Yes" } else { "No" }.to_string(),
),
read_only("UCS Name", ucs_name),
read_only("Visual Style", tab.visual_style.clone()),
],
},
];
ui::PropertiesPanel {
title: "Drawing".to_string(),
title: "No selection".to_string(),
sections,
layer_combo: iced::widget::combo_box::State::new(layer_names.clone()),
linetype_combo: iced::widget::combo_box::State::new(
+1 -1
View File
@@ -864,7 +864,7 @@ pub(super) fn on_open_file(&mut self) -> Task<Message> {
self.tabs[i].last_synced_camera_gen = self.tabs[i].scene.camera_generation;
self.tabs[i].dirty = false;
self.tabs[i].history = crate::app::document::HistoryState::default();
self.refresh_selected_grips();
self.refresh_properties();
#[cfg(not(target_arch = "wasm32"))]
let interaction_task = {
let wires = self.tabs[i].scene.hit_test_wires();
+77 -40
View File
@@ -920,6 +920,9 @@ impl OpenCADStudio {
Message::TabSwitch(idx) => {
self.doc_tab_context_menu = None;
self.layout_list_open = false;
self.layout_context_menu = None;
self.layout_rename_state = None;
if idx < self.tabs.len() {
if idx != self.active_tab {
// The attribute editor is tab-scoped; leaving its tab
@@ -931,6 +934,11 @@ impl OpenCADStudio {
self.stamp_header_sysvars(prev);
}
self.active_tab = idx;
if self.tabs[idx].is_start
&& self.active_modal == Some(super::ModalKind::LayoutManager)
{
self.close_active_modal();
}
self.sync_ribbon_layers();
self.sync_ribbon_styles();
// #21: also re-seed ribbon Color / Linetype / Lineweight
@@ -2334,6 +2342,10 @@ impl OpenCADStudio {
Task::none()
}
Message::ToggleLayoutList => {
if self.tabs[self.active_tab].is_start {
self.layout_list_open = false;
return Task::none();
}
self.layout_list_open ^= true;
Task::none()
}
@@ -2919,74 +2931,89 @@ impl OpenCADStudio {
Message::PropLayerChanged(layer) => {
let i = self.active_tab;
let handles = self.property_target_handles(i);
if !handles.is_empty() {
self.push_undo_snapshot(i, "CHPROP");
for &handle in &handles {
if let Some(entity) = self.tabs[i].scene.document.get_entity_mut(handle) {
crate::scene::view::dispatch::apply_common_prop(
entity, "layer", &layer,
);
}
}
self.invalidate_property_targets(i, &handles);
self.tabs[i].dirty = true;
if handles.is_empty() {
let task = self.on_ribbon_layer_changed(layer);
self.refresh_properties();
return task;
}
self.push_undo_snapshot(i, "CHPROP");
for &handle in &handles {
if let Some(entity) = self.tabs[i].scene.document.get_entity_mut(handle) {
crate::scene::view::dispatch::apply_common_prop(
entity, "layer", &layer,
);
}
}
self.invalidate_property_targets(i, &handles);
self.tabs[i].dirty = true;
self.refresh_properties();
Task::none()
}
Message::PropColorChanged(color) => {
let i = self.active_tab;
let handles = self.property_target_handles(i);
if !handles.is_empty() {
self.push_undo_snapshot(i, "CHPROP");
for &handle in &handles {
if let Some(entity) = self.tabs[i].scene.document.get_entity_mut(handle) {
crate::scene::view::dispatch::apply_color(entity, color);
}
}
self.invalidate_property_targets(i, &handles);
self.tabs[i].properties.color_picker_open = false;
if handles.is_empty() {
self.tabs[i].properties.color_palette_open = false;
self.tabs[i].dirty = true;
let task = self.on_ribbon_color_changed(color);
self.refresh_properties();
return task;
}
self.push_undo_snapshot(i, "CHPROP");
for &handle in &handles {
if let Some(entity) = self.tabs[i].scene.document.get_entity_mut(handle) {
crate::scene::view::dispatch::apply_color(entity, color);
}
}
self.invalidate_property_targets(i, &handles);
self.tabs[i].properties.color_picker_open = false;
self.tabs[i].properties.color_palette_open = false;
self.tabs[i].dirty = true;
self.refresh_properties();
Task::none()
}
Message::PropLwChanged(lw) => {
let i = self.active_tab;
let handles = self.property_target_handles(i);
if !handles.is_empty() {
self.push_undo_snapshot(i, "CHPROP");
for &handle in &handles {
if let Some(entity) = self.tabs[i].scene.document.get_entity_mut(handle) {
crate::scene::view::dispatch::apply_line_weight(entity, lw);
}
}
self.invalidate_property_targets(i, &handles);
if handles.is_empty() {
self.tabs[i].scene.document.header.current_line_weight = lw.value();
self.tabs[i].dirty = true;
self.ribbon.active_lineweight = lw;
self.refresh_properties();
return Task::none();
}
self.push_undo_snapshot(i, "CHPROP");
for &handle in &handles {
if let Some(entity) = self.tabs[i].scene.document.get_entity_mut(handle) {
crate::scene::view::dispatch::apply_line_weight(entity, lw);
}
}
self.invalidate_property_targets(i, &handles);
self.tabs[i].dirty = true;
self.refresh_properties();
Task::none()
}
Message::PropLinetypeChanged(lt) => {
let i = self.active_tab;
let handles = self.property_target_handles(i);
if !handles.is_empty() {
self.push_undo_snapshot(i, "CHPROP");
for &handle in &handles {
if let Some(entity) = self.tabs[i].scene.document.get_entity_mut(handle) {
crate::scene::view::dispatch::apply_common_prop(
entity, "linetype", &lt,
);
}
}
self.invalidate_property_targets(i, &handles);
self.tabs[i].dirty = true;
if handles.is_empty() {
let task = self.on_ribbon_linetype_changed(lt);
self.refresh_properties();
return task;
}
self.push_undo_snapshot(i, "CHPROP");
for &handle in &handles {
if let Some(entity) = self.tabs[i].scene.document.get_entity_mut(handle) {
crate::scene::view::dispatch::apply_common_prop(
entity, "linetype", &lt,
);
}
}
self.invalidate_property_targets(i, &handles);
self.tabs[i].dirty = true;
self.refresh_properties();
Task::none()
}
@@ -3364,6 +3391,11 @@ impl OpenCADStudio {
// ── Layout Manager Panel ──────────────────────────────────────────
Message::LayoutManagerOpen => {
let i = self.active_tab;
if self.tabs[i].is_start {
self.command_line
.push_info("Open or create a drawing to manage layouts.");
return Task::none();
}
let current = self.tabs[i].scene.current_layout.clone();
self.layout_manager_selected = current.clone();
self.layout_manager_rename_buf = if current == "Model" {
@@ -3417,6 +3449,11 @@ impl OpenCADStudio {
}
Message::LayoutManagerNew => {
let i = self.active_tab;
if self.tabs[i].is_start {
self.command_line
.push_info("Open or create a drawing to add a layout.");
return Task::none();
}
let existing = self.tabs[i].scene.layout_names();
let n = (1usize..)
.find(|n| !existing.contains(&format!("Layout{n}")))
+10
View File
@@ -3677,6 +3677,11 @@ impl OpenCADStudio {
pub(super) fn on_layout_switch(&mut self, name: String) -> Task<Message> {
let i = self.active_tab;
if self.tabs[i].is_start {
self.command_line
.push_info("Open or create a drawing to switch layouts.");
return Task::none();
}
// A BEDIT block editor locks the active space; finish it with
// Save Block or Discard before switching spaces. (#261)
if self.tabs[i].block_edit.is_some() {
@@ -3745,6 +3750,11 @@ impl OpenCADStudio {
pub(super) fn on_layout_create(&mut self) -> Task<Message> {
let i = self.active_tab;
if self.tabs[i].is_start {
self.command_line
.push_info("Open or create a drawing to add a layout.");
return Task::none();
}
// Find a unique name (e.g. Layout2, Layout3, ...).
let existing = self.tabs[i].scene.layout_names();
let mut idx = existing.len();
+1
View File
@@ -1446,6 +1446,7 @@ impl OpenCADStudio {
.as_ref()
.map(|be| be.block_name.clone())
.unwrap_or_else(|| tab.scene.current_layout.clone()),
tab.is_start,
self.layout_rename_state.as_ref(),
tab.scene.first_viewport_scale(),
tab.scene.viewport_count(),