forked from 1b-agent/opencadstudio-upstream
Merge main into Pallet-placement
Resolved conflicts: - app/update/mod.rs: keep the dock's dock_open_at_default for the XREF manager; take main's stable xref row identity (row_index(&id)). - app/view/mod.rs: keep the edge layout from app::dock; drop main's copy of the old edge-stack code (main only reformatted it there). - ui/window/layers.rs: keep main's new visible_indices next to the branch's view_panel. Files the branch only reformatted take main's version. Adds the Lao (lo-LA) translations for the new dock texts. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_013RyGiMXcsf9HTKfqDxaQGt
This commit is contained in:
136 files changed
+12408
-949
No files matched your search
+294
-98
@@ -24,6 +24,7 @@ fn is_modal_blocked_key_msg(msg: &Message) -> bool {
|
||||
| Message::CommandLineArrowProbe { .. }
|
||||
| Message::CommandLineArrowResolved { .. }
|
||||
| Message::DynTabNext
|
||||
| Message::DynTabPrev
|
||||
| Message::MTextCaretMove(_)
|
||||
| Message::DeleteSelected
|
||||
| Message::ToggleSnapEnabled
|
||||
@@ -533,7 +534,9 @@ impl OpenCADStudio {
|
||||
}
|
||||
}
|
||||
if let Some(started) = perf_started {
|
||||
let elapsed_ms = started.elapsed().as_secs_f64() * 1000.0;
|
||||
let elapsed_us = started.elapsed().as_micros() as u64;
|
||||
crate::perf::note_update_time(elapsed_us);
|
||||
let elapsed_ms = elapsed_us as f64 / 1000.0;
|
||||
if elapsed_ms >= 5.0 {
|
||||
crate::perf_record!("[perf] update {:>7.1}ms message={perf_label}", elapsed_ms,);
|
||||
}
|
||||
@@ -2165,16 +2168,21 @@ impl OpenCADStudio {
|
||||
let i = self.active_tab;
|
||||
// Capture prompt/options while holding cmd borrow, then release before
|
||||
// borrowing command_line to satisfy borrow checker.
|
||||
let (should_update, opts, prompt) =
|
||||
let (should_update, opts, prompt, suggestions) =
|
||||
if let Some(cmd) = self.tabs[i].active_cmd.as_mut() {
|
||||
if cmd.on_live_input(&live_input) {
|
||||
(true, cmd.options(), cmd.prompt())
|
||||
let changed = cmd.on_live_input(&live_input);
|
||||
let suggestions = cmd.text_suggestions(&live_input);
|
||||
if changed {
|
||||
(true, cmd.options(), cmd.prompt(), suggestions)
|
||||
} else {
|
||||
(false, Vec::new(), String::new())
|
||||
(false, Vec::new(), String::new(), suggestions)
|
||||
}
|
||||
} else {
|
||||
(false, Vec::new(), String::new())
|
||||
(false, Vec::new(), String::new(), Vec::new())
|
||||
};
|
||||
|
||||
self.command_line.set_contextual_suggestions(suggestions);
|
||||
|
||||
if should_update {
|
||||
self.command_line.set_step_options(opts);
|
||||
// Update pinned prompt text live without pushing new history entry
|
||||
@@ -2203,6 +2211,12 @@ impl OpenCADStudio {
|
||||
}
|
||||
|
||||
Message::DynTabNext => {
|
||||
if self.command_line.has_contextual_suggestions()
|
||||
&& self.command_line.autocomplete_next()
|
||||
{
|
||||
return Task::none();
|
||||
}
|
||||
|
||||
let i = self.active_tab;
|
||||
let n = self.tabs[i].dyn_fields.len();
|
||||
if n > 0 {
|
||||
@@ -2214,6 +2228,13 @@ impl OpenCADStudio {
|
||||
self.focus_cmd_input()
|
||||
}
|
||||
|
||||
Message::DynTabPrev => {
|
||||
if self.command_line.has_contextual_suggestions() {
|
||||
self.command_line.autocomplete_prev();
|
||||
}
|
||||
self.focus_cmd_input()
|
||||
}
|
||||
|
||||
Message::SplitModelViewport(horizontal) => {
|
||||
let i = self.active_tab;
|
||||
self.tabs[i].scene.split_active_pane(horizontal);
|
||||
@@ -2250,7 +2271,8 @@ impl OpenCADStudio {
|
||||
}
|
||||
let i = self.active_tab;
|
||||
if !self.command_line.history_navigation_active()
|
||||
&& self.tabs[i].active_cmd.is_none()
|
||||
&& (self.tabs[i].active_cmd.is_none()
|
||||
|| self.command_line.has_contextual_suggestions())
|
||||
&& self.command_line.autocomplete_prev()
|
||||
{
|
||||
return Task::none();
|
||||
@@ -2276,7 +2298,8 @@ impl OpenCADStudio {
|
||||
}
|
||||
let i = self.active_tab;
|
||||
if !self.command_line.history_navigation_active()
|
||||
&& self.tabs[i].active_cmd.is_none()
|
||||
&& (self.tabs[i].active_cmd.is_none()
|
||||
|| self.command_line.has_contextual_suggestions())
|
||||
&& self.command_line.autocomplete_next()
|
||||
{
|
||||
return Task::none();
|
||||
@@ -2477,11 +2500,20 @@ impl OpenCADStudio {
|
||||
}
|
||||
}
|
||||
|
||||
Message::CommandSuggestionPick(cmd) => {
|
||||
Message::CommandSuggestionPick(value) => {
|
||||
self.command_line.close_history();
|
||||
|
||||
if self.tabs[self.active_tab].active_cmd.is_some()
|
||||
&& self.command_line.has_contextual_suggestions()
|
||||
{
|
||||
self.command_line.input = value;
|
||||
self.command_line.autocomplete_cursor = None;
|
||||
return self.on_command_submit();
|
||||
}
|
||||
|
||||
self.command_line.input.clear();
|
||||
self.command_line.autocomplete_cursor = None;
|
||||
self.command_line.close_history();
|
||||
self.dispatch_command(&cmd)
|
||||
self.dispatch_command(&value)
|
||||
}
|
||||
|
||||
Message::CommandOptionPick(kw) => {
|
||||
@@ -2584,7 +2616,7 @@ impl OpenCADStudio {
|
||||
self.refresh_xref_manager();
|
||||
Task::none()
|
||||
}
|
||||
Message::XrefManagerSelect(index) => {
|
||||
Message::XrefManagerSelect(id) => {
|
||||
use crate::ui::window::xref_manager::SelectExtend;
|
||||
// Normal GUI list behavior off the globally tracked
|
||||
// modifiers (same source as the layer list): plain click
|
||||
@@ -2596,12 +2628,12 @@ impl OpenCADStudio {
|
||||
} else {
|
||||
SelectExtend::Single
|
||||
};
|
||||
self.xref_manager.click_select(index, extend);
|
||||
self.xref_manager.click_select(&id, extend);
|
||||
Task::none()
|
||||
}
|
||||
Message::XrefRowRightClick(index) => {
|
||||
Message::XrefRowRightClick(id) => {
|
||||
self.xref_manager.row_change_path_open = false;
|
||||
self.xref_manager.right_click_select(index);
|
||||
self.xref_manager.right_click_select(&id);
|
||||
Task::none()
|
||||
}
|
||||
Message::XrefManagerToggleTree => {
|
||||
@@ -2744,67 +2776,76 @@ impl OpenCADStudio {
|
||||
self.xref_manager_op(op);
|
||||
Task::none()
|
||||
}
|
||||
Message::XrefRowOp(index, op) => {
|
||||
Message::XrefRowOp(id, op) => {
|
||||
// Open is not a palette op — it navigates to the file.
|
||||
if op == crate::ui::window::xref_manager::XrefPaletteOp::Open {
|
||||
self.xref_manager.right_click_select(index);
|
||||
self.xref_manager.right_click_select(&id);
|
||||
self.xref_manager.row_change_path_open = false;
|
||||
if let Some(entry) = self.xref_manager.entries.get(index) {
|
||||
if let Some(found) = entry.found_at.clone() {
|
||||
let is_dwg = found.to_ascii_lowercase().ends_with(".dwg")
|
||||
|| found.to_ascii_lowercase().ends_with(".dxf");
|
||||
self.command_line
|
||||
.push_output(crate::tf!("XOPEN: opening \"{}\".", found).as_ref());
|
||||
if is_dwg {
|
||||
return self
|
||||
.update(Message::OpenRecent(std::path::PathBuf::from(found)));
|
||||
if let Some(index) = self.xref_manager.row_index(&id) {
|
||||
if let Some(entry) = self.xref_manager.entries.get(index) {
|
||||
if let Some(found) = entry.found_at.clone() {
|
||||
let is_dwg = found.to_ascii_lowercase().ends_with(".dwg")
|
||||
|| found.to_ascii_lowercase().ends_with(".dxf");
|
||||
self.command_line.push_output(
|
||||
crate::tf!("XOPEN: opening \"{}\".", found).as_ref(),
|
||||
);
|
||||
if is_dwg {
|
||||
return self.update(Message::OpenRecent(
|
||||
std::path::PathBuf::from(found),
|
||||
));
|
||||
} else {
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
let _ = open::that_detached(&found);
|
||||
}
|
||||
} else {
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
let _ = open::that_detached(&found);
|
||||
self.command_line.push_error(
|
||||
crate::t!("File not found — check the saved path.").as_ref(),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
self.command_line.push_error(
|
||||
crate::t!("File not found — check the saved path.").as_ref(),
|
||||
);
|
||||
}
|
||||
}
|
||||
return Task::none();
|
||||
}
|
||||
// Attach prompts the file dialog to pick a drawing to attach.
|
||||
if matches!(op, crate::ui::window::xref_manager::XrefPaletteOp::Attach) {
|
||||
self.xref_manager.right_click_select(index);
|
||||
self.xref_manager.right_click_select(&id);
|
||||
self.xref_manager.row_change_path_open = false;
|
||||
self.command_line.push_output(crate::t!("XATTACH").as_ref());
|
||||
return self.update(Message::XAttachPick);
|
||||
}
|
||||
// Row-scoped op: select the row first, then run the op.
|
||||
self.xref_manager.right_click_select(index);
|
||||
self.xref_manager.right_click_select(&id);
|
||||
self.xref_manager.row_change_path_open = false;
|
||||
self.xref_manager_op(op);
|
||||
Task::none()
|
||||
}
|
||||
Message::XrefRowPathPick(index) => {
|
||||
self.xref_manager.right_click_select(index);
|
||||
Message::XrefRowPathPick(id) => {
|
||||
self.xref_manager.right_click_select(&id);
|
||||
self.xref_manager.row_change_path_open = false;
|
||||
self.update(Message::XrefPathPick)
|
||||
}
|
||||
Message::XrefRowFindReplacePrompt(index) => {
|
||||
self.xref_manager.right_click_select(index);
|
||||
Message::XrefRowFindReplacePrompt(id) => {
|
||||
self.xref_manager.right_click_select(&id);
|
||||
self.xref_manager.row_change_path_open = false;
|
||||
let prefill = if let Some(entry) = self.xref_manager.entries.get(index) {
|
||||
let saved = &entry.saved_path;
|
||||
if let Some(parent) = std::path::Path::new(saved)
|
||||
.parent()
|
||||
.and_then(|p| p.to_str())
|
||||
{
|
||||
if !parent.is_empty() {
|
||||
format!("XREF Path Find \"{}\" ", parent)
|
||||
} else {
|
||||
"XREF Path Find ".to_string()
|
||||
}
|
||||
} else {
|
||||
"XREF Path Find ".to_string()
|
||||
}
|
||||
let prefill = if let Some(index) = self.xref_manager.row_index(&id) {
|
||||
self.xref_manager.entries.get(index).map_or_else(
|
||||
|| "XREF Path Find ".to_string(),
|
||||
|entry| {
|
||||
let saved = &entry.saved_path;
|
||||
if let Some(parent) = std::path::Path::new(saved)
|
||||
.parent()
|
||||
.and_then(|p| p.to_str())
|
||||
{
|
||||
if !parent.is_empty() {
|
||||
format!("XREF Path Find \"{}\" ", parent)
|
||||
} else {
|
||||
"XREF Path Find ".to_string()
|
||||
}
|
||||
} else {
|
||||
"XREF Path Find ".to_string()
|
||||
}
|
||||
},
|
||||
)
|
||||
} else {
|
||||
"XREF Path Find ".to_string()
|
||||
};
|
||||
@@ -3559,10 +3600,14 @@ impl OpenCADStudio {
|
||||
panel.selected_multi.push(idx);
|
||||
}
|
||||
} else if shift {
|
||||
// Shift-click selects the range from the anchor to here.
|
||||
// Shift-click selects the range from the anchor to here —
|
||||
// visible rows only, so filtered-out rows between them
|
||||
// cannot join bulk edits/deletes (#50).
|
||||
let anchor = panel.selected.unwrap_or(idx);
|
||||
let (lo, hi) = (anchor.min(idx), anchor.max(idx));
|
||||
panel.selected_multi = (lo..=hi).collect();
|
||||
let visible: std::collections::HashSet<usize> =
|
||||
panel.visible_indices().into_iter().collect();
|
||||
panel.selected_multi = (lo..=hi).filter(|r| visible.contains(r)).collect();
|
||||
} else if !panel.selected_multi.contains(&idx) {
|
||||
// Plain click collapses to this row — but NOT when it is
|
||||
// already part of a multi-selection, so clicking a property
|
||||
@@ -3581,10 +3626,8 @@ impl OpenCADStudio {
|
||||
};
|
||||
self.tabs[i].layers.selected = Some(idx);
|
||||
self.tabs[i].layers.selected_multi = vec![idx];
|
||||
if let Some(layer) = self.tabs[i].layers.layers.get(idx) {
|
||||
self.tabs[i].layers.edit_buf = layer.name.clone();
|
||||
}
|
||||
self.tabs[i].layers.editing = Some(idx);
|
||||
self.tabs[i].layers.edit_buf = name.clone();
|
||||
self.tabs[i].layers.editing = Some(name);
|
||||
Task::none()
|
||||
}
|
||||
|
||||
@@ -3602,11 +3645,11 @@ impl OpenCADStudio {
|
||||
return Task::none();
|
||||
};
|
||||
let panel = &mut self.tabs[i].layers;
|
||||
if panel.color_picker_row == Some(idx) {
|
||||
if panel.color_picker_row.as_deref() == Some(name.as_str()) {
|
||||
panel.color_picker_row = None;
|
||||
panel.color_full_palette = false;
|
||||
} else {
|
||||
panel.color_picker_row = Some(idx);
|
||||
panel.color_picker_row = Some(name);
|
||||
panel.color_full_palette = false;
|
||||
panel.selected = Some(idx);
|
||||
// Opening the swatch on a row outside the current
|
||||
@@ -3719,23 +3762,25 @@ impl OpenCADStudio {
|
||||
// Apply the edited transparency to every selected layer (#236).
|
||||
if let Some(v) = val {
|
||||
let targets = self.layer_row_action_targets(i, idx);
|
||||
for name in &targets {
|
||||
if let Some(layer) = self.tabs[i].scene.document.layers.get_mut(name) {
|
||||
layer.transparency =
|
||||
codec::types::Transparency::from_percent(v as f64 / 100.0);
|
||||
}
|
||||
if let Some(pl) = self.tabs[i]
|
||||
.layers
|
||||
.layers
|
||||
.iter_mut()
|
||||
.find(|l| &l.name == name)
|
||||
{
|
||||
pl.transparency = v;
|
||||
}
|
||||
}
|
||||
if !targets.is_empty() {
|
||||
let undo = self.begin_layer_undo(i, "LAYER TRANSPARENCY", &targets);
|
||||
for name in &targets {
|
||||
if let Some(layer) = self.tabs[i].scene.document.layers.get_mut(name) {
|
||||
layer.transparency =
|
||||
codec::types::Transparency::from_percent(v as f64 / 100.0);
|
||||
}
|
||||
if let Some(pl) = self.tabs[i]
|
||||
.layers
|
||||
.layers
|
||||
.iter_mut()
|
||||
.find(|l| &l.name == name)
|
||||
{
|
||||
pl.transparency = v;
|
||||
}
|
||||
}
|
||||
self.tabs[i].scene.invalidate_layer_dependencies(&targets);
|
||||
self.tabs[i].dirty = true;
|
||||
self.commit_layer_undo(i, undo);
|
||||
}
|
||||
}
|
||||
Task::none()
|
||||
@@ -4558,28 +4603,36 @@ impl OpenCADStudio {
|
||||
}
|
||||
Message::AnnoObjectScaleToggle(name) => {
|
||||
let i = self.active_tab;
|
||||
if let Some(entity) = self.anno_object_scale_target {
|
||||
if self.tabs[i].scene.is_layer_locked(entity) {
|
||||
return Task::none();
|
||||
}
|
||||
if let Some(sh) = self.tabs[i].scene.scale_handle_ensuring(&name) {
|
||||
self.push_undo_snapshot(i, "OBJECTSCALE");
|
||||
let doc = &mut self.tabs[i].scene.document;
|
||||
let is_member =
|
||||
crate::scene::annotative::object_scale_memberships(doc, entity)
|
||||
.iter()
|
||||
.any(|(_, h)| *h == sh);
|
||||
if is_member {
|
||||
crate::scene::annotative::remove_annotation_context_for_scale(
|
||||
doc, entity, sh,
|
||||
);
|
||||
} else {
|
||||
crate::scene::annotative::create_annotation_context(doc, entity, sh);
|
||||
}
|
||||
self.tabs[i].dirty = true;
|
||||
self.invalidate_property_targets(i, &[entity]);
|
||||
self.refresh_properties();
|
||||
let Some(entity) = self.anno_object_scale_target else {
|
||||
self.command_line.push_info(
|
||||
crate::t!("Select one object first, then run OBJECTSCALE.").as_ref(),
|
||||
);
|
||||
return Task::none();
|
||||
};
|
||||
if self.tabs[i].scene.is_layer_locked(entity) {
|
||||
self.command_line
|
||||
.push_info(crate::t!("The object is on a locked layer.").as_ref());
|
||||
return Task::none();
|
||||
}
|
||||
if let Some(sh) = self.tabs[i].scene.scale_handle_ensuring(&name) {
|
||||
self.push_undo_snapshot(i, "OBJECTSCALE");
|
||||
let doc = &mut self.tabs[i].scene.document;
|
||||
let is_member = crate::scene::annotative::object_scale_memberships(doc, entity)
|
||||
.iter()
|
||||
.any(|(_, h)| *h == sh);
|
||||
if is_member {
|
||||
crate::scene::annotative::remove_annotation_context_for_scale(
|
||||
doc, entity, sh,
|
||||
);
|
||||
} else {
|
||||
crate::scene::annotative::create_annotation_context(doc, entity, sh);
|
||||
}
|
||||
self.tabs[i].dirty = true;
|
||||
self.invalidate_property_targets(i, &[entity]);
|
||||
self.refresh_properties();
|
||||
} else {
|
||||
self.command_line
|
||||
.push_info(crate::tf!("Unknown annotation scale '{name}'.").as_ref());
|
||||
}
|
||||
Task::none()
|
||||
}
|
||||
@@ -6892,11 +6945,12 @@ impl OpenCADStudio {
|
||||
};
|
||||
let Some((sx, sy, sz)) = scales else { return };
|
||||
let eq = (sx - sy).abs() < 1e-12 && (sx - sz).abs() < 1e-12;
|
||||
let checked = eq && !app.props_asym_scale.contains(&handle.value());
|
||||
let checked =
|
||||
eq && !app.tabs[i].props_asym_scale.contains(&handle.value());
|
||||
if checked {
|
||||
app.props_asym_scale.insert(handle.value());
|
||||
app.tabs[i].props_asym_scale.insert(handle.value());
|
||||
} else {
|
||||
app.props_asym_scale.remove(&handle.value());
|
||||
app.tabs[i].props_asym_scale.remove(&handle.value());
|
||||
if let Some(codec::EntityType::Insert(ins)) =
|
||||
app.tabs[i].scene.document.get_entity_mut(handle)
|
||||
{
|
||||
@@ -8411,6 +8465,18 @@ impl OpenCADStudio {
|
||||
Task::none()
|
||||
}
|
||||
|
||||
Message::TouchpadPanChanged(enabled) => {
|
||||
self.touchpad_pan = enabled;
|
||||
self.persist_settings_if_changed();
|
||||
Task::none()
|
||||
}
|
||||
|
||||
Message::UiScaleChanged(percent) => {
|
||||
self.ui_scale_percent = crate::app::settings::clamp_ui_scale(percent);
|
||||
self.persist_settings_if_changed();
|
||||
Task::none()
|
||||
}
|
||||
|
||||
Message::ZoomFactorChanged(factor) => {
|
||||
self.zoom_factor = crate::app::settings::clamp_zoom_factor(factor);
|
||||
// The field beside the slider reads the same value, so the
|
||||
@@ -11124,6 +11190,136 @@ mod prop_pointer_tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod anno_scale_toggle_tests {
|
||||
// #59: silent no-op toggles must explain themselves on the command line.
|
||||
use super::Message;
|
||||
use crate::app::OpenCADStudio;
|
||||
|
||||
fn app_with_target() -> (OpenCADStudio, usize) {
|
||||
let mut app = OpenCADStudio::new_for_test();
|
||||
app.automation_op(r#"{"op":"new"}"#);
|
||||
let i = app.active_tab;
|
||||
let mut line = codec::entities::Line::new();
|
||||
line.start = codec::types::Vector3::ZERO;
|
||||
line.end = codec::types::Vector3::new(1.0, 0.0, 0.0);
|
||||
let h = app
|
||||
.commit_entity_handle(codec::EntityType::Line(line))
|
||||
.expect("line commits");
|
||||
app.anno_object_scale_target = Some(h);
|
||||
(app, i)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn locked_layer_toggle_explains_itself() {
|
||||
let (mut app, i) = app_with_target();
|
||||
app.tabs[i]
|
||||
.scene
|
||||
.document
|
||||
.layers
|
||||
.get_mut("0")
|
||||
.expect("layer 0 exists")
|
||||
.flags
|
||||
.locked = true;
|
||||
let before = app.command_line.history.len();
|
||||
let _ = app.update(Message::AnnoObjectScaleToggle("1:50".to_string()));
|
||||
let new: Vec<String> = app.command_line.history[before..]
|
||||
.iter()
|
||||
.map(|e| e.text.clone())
|
||||
.collect();
|
||||
assert!(
|
||||
new.iter().any(|t| t.to_lowercase().contains("locked")),
|
||||
"locked-layer toggle must say so, got {new:?}"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unknown_scale_toggle_explains_itself() {
|
||||
let (mut app, _i) = app_with_target();
|
||||
let before = app.command_line.history.len();
|
||||
let _ = app.update(Message::AnnoObjectScaleToggle("NOPE".to_string()));
|
||||
let new: Vec<String> = app.command_line.history[before..]
|
||||
.iter()
|
||||
.map(|e| e.text.clone())
|
||||
.collect();
|
||||
assert!(
|
||||
!new.is_empty(),
|
||||
"unknown-scale toggle must say something, got silence"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn missing_target_toggle_explains_itself() {
|
||||
let mut app = OpenCADStudio::new_for_test();
|
||||
app.automation_op(r#"{"op":"new"}"#);
|
||||
app.anno_object_scale_target = None;
|
||||
let before = app.command_line.history.len();
|
||||
let _ = app.update(Message::AnnoObjectScaleToggle("1:50".to_string()));
|
||||
let new: Vec<String> = app.command_line.history[before..]
|
||||
.iter()
|
||||
.map(|e| e.text.clone())
|
||||
.collect();
|
||||
assert!(
|
||||
!new.is_empty(),
|
||||
"target-less toggle must say something, got silence"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod dock_visibility_tests {
|
||||
// #57: one visibility predicate for edge rendering and drag math. Pins
|
||||
// the full 8-panel table so a newly added panel cannot silently diverge
|
||||
// (behavior-preserving dedup: passes before and after by construction).
|
||||
use crate::app::OpenCADStudio;
|
||||
use crate::ui::dock::PanelId;
|
||||
|
||||
const ALL: [PanelId; 8] = [
|
||||
PanelId::Properties,
|
||||
PanelId::BlockPalette,
|
||||
PanelId::ExternalReferences,
|
||||
PanelId::Browser,
|
||||
PanelId::NodeGraph,
|
||||
PanelId::PointCloudManager,
|
||||
PanelId::Count,
|
||||
PanelId::SheetSetManager,
|
||||
];
|
||||
|
||||
#[test]
|
||||
fn dock_panel_visible_covers_every_panel() {
|
||||
let mut app = OpenCADStudio::new_for_test();
|
||||
app.automation_op(r#"{"op":"new"}"#);
|
||||
app.show_properties = true;
|
||||
app.show_block_palette = true;
|
||||
app.show_external_references = true;
|
||||
app.show_browser = true;
|
||||
app.show_node_graph = true;
|
||||
app.pc_manager.show = true;
|
||||
app.count_palette.show = true;
|
||||
app.sheet_set.show = true;
|
||||
for id in ALL {
|
||||
assert!(app.dock_panel_visible(id), "{id:?} must be visible");
|
||||
}
|
||||
app.clean_screen = true;
|
||||
for id in ALL {
|
||||
assert!(
|
||||
!app.dock_panel_visible(id),
|
||||
"{id:?} must hide on clean screen"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dock_panel_hidden_on_start_tab() {
|
||||
// Fresh app, no drawing opened: the welcome tab shows no panels.
|
||||
let app = OpenCADStudio::new_for_test();
|
||||
assert!(app.tabs[app.active_tab].is_start);
|
||||
for id in ALL {
|
||||
assert!(!app.dock_panel_visible(id), "{id:?} must hide on Start");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod free_text_entry_tests {
|
||||
//! The free-form text prompts (table cell content, TEXT / MTEXT bodies)
|
||||
|
||||
Reference in new issue
Block a user