forked from 1b-agent/opencadstudio-upstream
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:
1 parent
308990e672
commit
daa26007f9
24 files changed
+1341
-34
No files matched your search
@@ -59,6 +59,239 @@ fn feed(app: &mut OpenCADStudio, text: &str) {
|
||||
let _ = app.update(Message::CommandSubmit);
|
||||
}
|
||||
|
||||
fn visibility_editor() -> (OpenCADStudio, crate::ui::window::block_visibility::Target) {
|
||||
let (mut app, reference) = fixture();
|
||||
begin(&mut app, reference);
|
||||
let _ = app.run_command_line("BPARAMETER VISIBILITY 0,0");
|
||||
let target = app
|
||||
.block_visibility_context()
|
||||
.expect("placed parameter")
|
||||
.target;
|
||||
(app, target)
|
||||
}
|
||||
|
||||
fn visibility_action(app: &mut OpenCADStudio, action: crate::ui::window::block_visibility::Action) {
|
||||
let _ = app.update(Message::BlockVisibility(action));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn visibility_ribbon_manager_cancel_validation_and_single_undo() {
|
||||
use crate::ui::window::block_visibility::Action as A;
|
||||
let (mut app, target) = visibility_editor();
|
||||
let i = app.active_tab;
|
||||
let before = app.tabs[i].scene.document.clone();
|
||||
let depth = app.tabs[i].history.undo_stack.len();
|
||||
visibility_action(&mut app, A::Open(target));
|
||||
visibility_action(&mut app, A::Name("Alternate".into()));
|
||||
visibility_action(&mut app, A::New);
|
||||
assert_eq!(app.tabs[i].scene.document, before, "dialog is staged");
|
||||
let _ = app.update(Message::CommandEscape);
|
||||
assert!(app.block_visibility.is_none());
|
||||
assert_eq!(app.tabs[i].history.undo_stack.len(), depth);
|
||||
assert_eq!(app.tabs[i].scene.document, before);
|
||||
visibility_action(&mut app, A::Open(target));
|
||||
visibility_action(&mut app, A::Name("visibilitystate0".into()));
|
||||
visibility_action(&mut app, A::New);
|
||||
assert!(app.block_visibility.as_ref().unwrap().error.is_some());
|
||||
visibility_action(&mut app, A::Delete);
|
||||
assert_eq!(
|
||||
app.block_visibility.as_ref().unwrap().edited.states.len(),
|
||||
1
|
||||
);
|
||||
visibility_action(&mut app, A::Name("Alternate".into()));
|
||||
visibility_action(&mut app, A::New);
|
||||
visibility_action(&mut app, A::SetCurrent);
|
||||
visibility_action(&mut app, A::Name("Alternate renamed".into()));
|
||||
visibility_action(&mut app, A::Rename);
|
||||
visibility_action(&mut app, A::Move(true));
|
||||
visibility_action(&mut app, A::Apply);
|
||||
assert_eq!(app.tabs[i].history.undo_stack.len(), depth + 1);
|
||||
assert_eq!(
|
||||
app.block_visibility_context().unwrap().current.as_deref(),
|
||||
Some("Alternate renamed")
|
||||
);
|
||||
app.undo_steps(1);
|
||||
assert_eq!(
|
||||
app.block_visibility_context().unwrap().names,
|
||||
["VisibilityState0"]
|
||||
);
|
||||
app.redo_steps(1);
|
||||
assert_eq!(
|
||||
app.block_visibility_context().unwrap().names,
|
||||
["Alternate renamed", "VisibilityState0"]
|
||||
);
|
||||
assert_eq!(
|
||||
app.block_visibility_context().unwrap().current.as_deref(),
|
||||
Some("Alternate renamed")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn visibility_ribbon_new_modes_state_selection_and_native_roundtrip() {
|
||||
use crate::ui::window::block_visibility::{Action as A, NewVisibility as V};
|
||||
let (mut app, target) = visibility_editor();
|
||||
let i = app.active_tab;
|
||||
let member = members(&app)[0];
|
||||
visibility_action(&mut app, A::Open(target));
|
||||
for (name, mode) in [("Hidden", V::Hide), ("Shown", V::Show), ("Copy", V::Keep)] {
|
||||
visibility_action(&mut app, A::Name(name.into()));
|
||||
visibility_action(&mut app, A::NewVisibility(mode));
|
||||
visibility_action(&mut app, A::New);
|
||||
}
|
||||
visibility_action(&mut app, A::Apply);
|
||||
let parameter =
|
||||
crate::scene::block_visibility::parameter(&app.tabs[i].scene.document, target.definition)
|
||||
.unwrap();
|
||||
assert!(parameter.states[0].visible_blocks.contains(&member));
|
||||
assert!(parameter.states[1].visible_blocks.is_empty());
|
||||
assert!(parameter.states[2].visible_blocks.contains(&member));
|
||||
assert!(parameter.states[3].visible_blocks.contains(&member));
|
||||
visibility_action(&mut app, A::Current(target, "Hidden".into()));
|
||||
assert!(
|
||||
app.tabs[i]
|
||||
.scene
|
||||
.document
|
||||
.get_entity(member)
|
||||
.unwrap()
|
||||
.common()
|
||||
.invisible
|
||||
);
|
||||
visibility_action(&mut app, A::Current(target, "Shown".into()));
|
||||
visibility_action(&mut app, A::Current(target, "Copy".into()));
|
||||
app.undo_steps(1);
|
||||
assert_eq!(
|
||||
app.block_visibility_context().unwrap().current.as_deref(),
|
||||
Some("Shown"),
|
||||
"identical states retain their identity in history"
|
||||
);
|
||||
app.redo_steps(1);
|
||||
assert_eq!(
|
||||
app.block_visibility_context().unwrap().current.as_deref(),
|
||||
Some("Copy")
|
||||
);
|
||||
app.save_block_edit(i);
|
||||
for ext in ["dwg", "dxf"] {
|
||||
let document = &app.tabs[i].scene.document;
|
||||
let bytes = crate::io::save_to_bytes(document, ext, document.version).unwrap();
|
||||
let loaded = crate::io::load_bytes(&format!("visibility.{ext}"), bytes).unwrap();
|
||||
let parameter =
|
||||
crate::scene::block_visibility::parameter(&loaded, target.definition).unwrap();
|
||||
assert_eq!(
|
||||
parameter
|
||||
.states
|
||||
.iter()
|
||||
.map(|state| state.name.as_str())
|
||||
.collect::<Vec<_>>(),
|
||||
["VisibilityState0", "Hidden", "Shown", "Copy"]
|
||||
);
|
||||
assert!(parameter.states[1].visible_blocks.is_empty());
|
||||
assert!(parameter.states[2].visible_blocks.contains(&member));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn visibility_ribbon_mode_and_make_visible_cancel_do_not_change_saved_flags() {
|
||||
use crate::ui::window::block_visibility::Action as A;
|
||||
let (mut app, target) = visibility_editor();
|
||||
let i = app.active_tab;
|
||||
let member = members(&app)[0];
|
||||
app.tabs[i].scene.select_entity(member, true);
|
||||
let _ = app.run_command_line("BVHIDE");
|
||||
app.finish_pending_history(i);
|
||||
let before = app.tabs[i].scene.document.clone();
|
||||
let depth = app.tabs[i].history.undo_stack.len();
|
||||
let dirty = app.tabs[i].dirty;
|
||||
let wires = |app: &OpenCADStudio| {
|
||||
app.tabs[i]
|
||||
.scene
|
||||
.entity_wires()
|
||||
.iter()
|
||||
.filter(|wire| crate::scene::Scene::handle_from_wire_name(&wire.name) == Some(member))
|
||||
.cloned()
|
||||
.collect::<Vec<_>>()
|
||||
};
|
||||
assert!(wires(&app).is_empty());
|
||||
visibility_action(&mut app, A::Mode(target));
|
||||
assert!(
|
||||
!wires(&app).is_empty(),
|
||||
"hidden member is rendered for picking"
|
||||
);
|
||||
assert_eq!(app.tabs[i].scene.document, before);
|
||||
visibility_action(&mut app, A::Mode(target));
|
||||
assert!(wires(&app).is_empty());
|
||||
let _ = app.run_command_line("BVSHOW");
|
||||
app.sync_block_visibility_display();
|
||||
assert!(!wires(&app).is_empty());
|
||||
let _ = app.update(Message::CommandEscape);
|
||||
assert!(wires(&app).is_empty());
|
||||
assert_eq!(app.tabs[i].scene.document, before);
|
||||
assert_eq!(app.tabs[i].history.undo_stack.len(), depth);
|
||||
assert_eq!(app.tabs[i].dirty, dirty);
|
||||
let _ = app.run_command_line("BVMODE 1");
|
||||
assert!(app.block_visibility_mode);
|
||||
let _ = app.run_command_line("SETVAR BVMODE 2");
|
||||
assert!(app.block_visibility_mode);
|
||||
let _ = app.run_command_line("BVMODE 0");
|
||||
assert!(!app.block_visibility_mode);
|
||||
assert!(
|
||||
app.pending_setvar.is_none(),
|
||||
"inline variable must close its probe prompt"
|
||||
);
|
||||
let _ = app.run_command_line("BVSHOW");
|
||||
app.tabs[i].scene.select_entity(member, true);
|
||||
let result = app.tabs[i]
|
||||
.active_cmd
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.on_selection_complete(vec![member]);
|
||||
let _ = app.apply_cmd_result(result);
|
||||
feed(&mut app, "");
|
||||
assert!(
|
||||
!app.tabs[i]
|
||||
.scene
|
||||
.document
|
||||
.get_entity(member)
|
||||
.unwrap()
|
||||
.common()
|
||||
.invisible
|
||||
);
|
||||
app.undo_steps(1);
|
||||
assert!(
|
||||
app.tabs[i]
|
||||
.scene
|
||||
.document
|
||||
.get_entity(member)
|
||||
.unwrap()
|
||||
.common()
|
||||
.invisible,
|
||||
"undo uses the hidden flags from before picking"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn visibility_ribbon_manager_rejects_stale_target_and_stale_document() {
|
||||
use crate::ui::window::block_visibility::Action as A;
|
||||
let (mut app, target) = visibility_editor();
|
||||
visibility_action(&mut app, A::Open(target));
|
||||
visibility_action(&mut app, A::Name("Alternate".into()));
|
||||
visibility_action(&mut app, A::New);
|
||||
app.tabs[app.active_tab].scene.bump_geometry();
|
||||
visibility_action(&mut app, A::Apply);
|
||||
assert!(app.block_visibility.as_ref().unwrap().error.is_some());
|
||||
assert_eq!(
|
||||
app.block_visibility_context().unwrap().names,
|
||||
["VisibilityState0"]
|
||||
);
|
||||
let _ = app.update(Message::CloseModal);
|
||||
let mut stale = target;
|
||||
stale.document += 1;
|
||||
visibility_action(&mut app, A::Open(stale));
|
||||
assert!(app.block_visibility.is_none());
|
||||
assert!(app.active_modal.is_none());
|
||||
let _ = app.run_command_line("BVSTATE DIALOG");
|
||||
assert_eq!(app.active_modal, Some(ModalKind::BlockVisibility));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn local_undo_preserves_imported_native_networks_when_constraints_are_unchanged() {
|
||||
use codec::objects::{
|
||||
|
||||
Reference in new issue
Block a user