feat(block-editor): add functional Visibility ribbon controls

Add the current-state selector, Visibility States dialog and mode/show/hide
controls to the contextual ribbon, using shared icons, tooltips, modal UI
and adaptive flyouts. Disable the controls until a Visibility parameter exists.

Stage state creation, rename, deletion, current-state changes and reordering
until OK. Support copied, show-all and hide-all new states in one undo step,
preserve native graph metadata, and restore current-state identity on undo/redo.

Implement nonsaved BVMODE and display-only reveal during BVSHOW selection.
Cancelling selection leaves saved visibility flags and history unchanged.
Keep the existing command-line state workflows available.

Validation: 6 targeted tests; 277 block, 31 app dock, 35 ribbon and 12 history
checks passed (overlapping filters). All 17 local DWG fixtures passed in-memory
DWG/DXF preservation checks with unchanged original checksums.
This commit is contained in:
Karim Jerbi committed 2026-10-15 15:08:21 +01:00
1 parent 308990e672
commit daa26007f9
24 files changed
+1341 -34

No files matched your search

+1 -1
View File
@@ -1254,7 +1254,7 @@ impl OpenCADStudio {
}
}
pub(super) fn on_command_escape(&mut self) -> Task<Message> {
pub(in crate::app) fn on_command_escape(&mut self) -> Task<Message> {
self.tabs[self.active_tab].measurement.clear();
// Escape answers a pending client getpoint request first: no point.
if self.control.get_point.is_some() {
+6
View File
@@ -236,6 +236,7 @@ impl OpenCADStudio {
Some(AttMan) => self.attman = None,
Some(BlockEditPicker) => self.block_edit_picker = None,
Some(BlockSaveAs) => self.block_save_as = None,
Some(BlockVisibility) => self.block_visibility = None,
Some(PdfAttach) => self.pdf_attach = None,
Some(UnderlayLayers) => self.underlay_layers = None,
Some(PdfImportSettings) => self.pdf_import_settings = None,
@@ -390,6 +391,7 @@ impl OpenCADStudio {
let before = self.tabs.get(self.active_tab).map(|tab| tab.id);
let task = self.update_message(msg);
self.restore_dynamic_ucs_after_command();
self.sync_block_visibility_display();
if self.tabs.get(self.active_tab).map(|tab| tab.id) != before {
self.sync_underlay_tab();
}
@@ -9658,6 +9660,9 @@ impl OpenCADStudio {
}
Message::CloseModal => {
if self.active_modal == Some(super::ModalKind::BlockVisibility) {
self.block_visibility = None;
}
// The Invalid Area dialog's ✕ undoes the boundary change, as its Cancel does.
if self.active_modal == Some(super::ModalKind::CountInvalidArea) {
return self.update(Message::Count(
@@ -10670,6 +10675,7 @@ impl OpenCADStudio {
Task::none()
}
Message::BlockSaveAsApply => self.apply_block_save_as_dialog(),
Message::BlockVisibility(action) => self.handle_block_visibility(action),
Message::BlockEditPickerName(name) => {
if let Some(state) = self.block_edit_picker.as_mut() {
state.name = name.clone();