forked from 1b-agent/opencadstudio-upstream
Merge branch 'main' into feature/parametric-constraint-system
Brings in 54 commits from origin/main: a selection-performance overhaul, surface thickening, GPU-native wide/tapered stroke rendering, continued ocs_doc_api evolution, and assorted fixes/polish (block double-click editing, DWG hatch/database repair, clipboard, collapsible Properties sections). Six real conflicts, all where this branch had independently re-applied a fix upstream later revised or already carried natively: - src/app/automation.rs, src/app/command_driver.rs: combined both sides — kept this branch's sketch-constraint/named-parameter loading on "new"/"open", adopted main's Scene::clear()/material_base_dir and its newer rebuild_derived_caches() over the older bump_geometry(). Scene::clear() (src/scene/entity.rs) now also resets sketch_constraints/named_parameters, so every caller gets this for free instead of each call site repeating it. - src/io/mod.rs, src/scene/render_graph.rs: adopted main's design outright (commit973af87d, authored by Hakan directly) — a large MINSERT is no longer purged as "corrupt" at load, only its rendering is bounded, preserving the source data. This branch's own earlier design (reject at load) predates that revision. - src/scene/pipeline/mesh_gpu.rs: adopted main's valid_rgba_size() guard (a real robustness improvement over this branch's version). - src/snap.rs: replaced wholesale with main's version — this branch's copy was an explicit verbatim snapshot from an earlier upstream PR (commit4c264b8c) with zero constraint-system-specific content, and main has since evolved it considerably further. Also removed two now-dead duplicates the merge surfaced without a conflict marker (both branches had independently fixed the same things): the OPTIONS/OP command dispatch/autocomplete entries, and automation.rs's open_finalizes_and_purges_like_the_ui_open_path test (kept main's version, which covers strictly more: solid_models/ material_base_dir reset on "new"). Full workspace suite passes (812 lib tests + this branch's own 24 constraint/persistence integration tests) except the one pre-existing, unrelated font-test failure already present on main. Zero clippy findings in the hand-written merge resolution itself; the ~1,171 warnings clippy reports are pre-existing in the 54 absorbed commits, not introduced here. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
commit
d675244fb8
89 files changed
+5700
-1101
No files matched your search
@@ -365,6 +365,9 @@ impl OpenCADStudio {
|
||||
zoom_factor: self.zoom_factor,
|
||||
cursor_size: self.cursor_size,
|
||||
pick_box: self.pick_box,
|
||||
double_click_block_refedit: self.double_click_block_refedit,
|
||||
double_click_block_attedit: self.double_click_block_attedit,
|
||||
grip_object_limit: self.grip_object_limit,
|
||||
cursor_type: self.cursor_type,
|
||||
crosshair_color: self.crosshair_color,
|
||||
lineweight_display_scale: self.lineweight_display_scale,
|
||||
@@ -420,6 +423,9 @@ impl OpenCADStudio {
|
||||
self.zoom_factor = s.zoom_factor.clamp(3, 100);
|
||||
self.cursor_size = s.cursor_size.clamp(1, 100);
|
||||
self.pick_box = s.pick_box.clamp(0, 50);
|
||||
self.double_click_block_refedit = s.double_click_block_refedit;
|
||||
self.double_click_block_attedit = s.double_click_block_attedit;
|
||||
self.grip_object_limit = s.grip_object_limit.clamp(0, 32767);
|
||||
self.cursor_type = s.cursor_type;
|
||||
self.crosshair_color = s.crosshair_color;
|
||||
self.crosshair_color_input = s
|
||||
|
||||
+121
-3
@@ -1839,10 +1839,32 @@ impl OpenCADStudio {
|
||||
Message::CommandHistoryCopy => {
|
||||
let text = self.command_line.history_plain_text();
|
||||
if text.is_empty() {
|
||||
Task::none()
|
||||
} else {
|
||||
iced::clipboard::write(text).discard()
|
||||
return Task::none();
|
||||
}
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
{
|
||||
let promise = crate::sys::copy_history_text(
|
||||
&text,
|
||||
crate::t!("Clipboard access is unavailable. Press Ctrl+C or Command+C to copy the selected history.").as_ref(),
|
||||
crate::t!("Close").as_ref(),
|
||||
);
|
||||
Task::perform(async move {
|
||||
wasm_bindgen_futures::JsFuture::from(promise).await
|
||||
.ok().and_then(|value| value.as_bool()).unwrap_or(false)
|
||||
}, Message::CommandHistoryCopied)
|
||||
}
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
iced::clipboard::write(text).discard()
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
Message::CommandHistoryCopied(copied) => {
|
||||
if copied {
|
||||
self.command_line.push_info(crate::t!("Copied").as_ref());
|
||||
} else {
|
||||
self.command_line.push_error(crate::t!("Clipboard access is unavailable. Press Ctrl+C or Command+C to copy the selected history.").as_ref());
|
||||
}
|
||||
Task::none()
|
||||
}
|
||||
|
||||
Message::CommandHistoryClear => {
|
||||
@@ -5108,6 +5130,20 @@ impl OpenCADStudio {
|
||||
Task::none()
|
||||
}
|
||||
|
||||
Message::PropSectionToggle(title) => {
|
||||
let sections = &mut self.collapsed_property_sections;
|
||||
if !sections.remove(&title) {
|
||||
sections.insert(title);
|
||||
}
|
||||
// Keep already-built panels in other open documents in sync,
|
||||
// rather than waiting for each one to rebuild on selection.
|
||||
let sections = self.collapsed_property_sections.clone();
|
||||
for tab in &mut self.tabs {
|
||||
tab.properties.collapsed_sections = sections.clone();
|
||||
}
|
||||
Task::none()
|
||||
}
|
||||
|
||||
Message::PropEditChoiceToggle => {
|
||||
let panel = &mut self.tabs[self.active_tab].properties;
|
||||
panel.edit_choice_open = !panel.edit_choice_open;
|
||||
@@ -5216,6 +5252,21 @@ impl OpenCADStudio {
|
||||
Message::PropColorFieldChanged { field, color } => {
|
||||
let i = self.active_tab;
|
||||
let handles = self.property_target_handles(i);
|
||||
if field == "indicator_fill_color" {
|
||||
self.apply_property_op(i, "CHPROP", &handles, |app, handle| {
|
||||
if let Some(acadrust::EntityType::Extended(extended)) =
|
||||
app.tabs[i].scene.document.get_entity_mut(handle)
|
||||
{
|
||||
if let acadrust::entities::ExtendedEntityData::SectionObject(data) =
|
||||
&mut extended.data
|
||||
{
|
||||
data.indicator_color = color;
|
||||
}
|
||||
}
|
||||
});
|
||||
self.tabs[i].properties.open_color_field = None;
|
||||
return Task::none();
|
||||
}
|
||||
// Dim-line colour override (Leader / Dimension): write it as an
|
||||
// ACAD_DSTYLE code-176 override (an ACI index) so it round-trips
|
||||
// through DWG and DXF. RGB picks collapse to the nearest ACI, in
|
||||
@@ -5921,6 +5972,18 @@ impl OpenCADStudio {
|
||||
Task::none()
|
||||
}
|
||||
|
||||
Message::DoubleClickBlockRefeditChanged(enabled) => {
|
||||
self.double_click_block_refedit = enabled;
|
||||
self.persist_settings_if_changed();
|
||||
Task::none()
|
||||
}
|
||||
|
||||
Message::DoubleClickBlockAtteditChanged(enabled) => {
|
||||
self.double_click_block_attedit = enabled;
|
||||
self.persist_settings_if_changed();
|
||||
Task::none()
|
||||
}
|
||||
|
||||
Message::CursorTypeChanged(value) => {
|
||||
self.cursor_type = value;
|
||||
self.persist_settings_if_changed();
|
||||
@@ -6148,6 +6211,48 @@ impl OpenCADStudio {
|
||||
Task::none()
|
||||
}
|
||||
|
||||
Message::GripObjectLimitChanged(limit) => {
|
||||
self.grip_object_limit = limit.clamp(0, 32767);
|
||||
self.persist_settings_if_changed();
|
||||
Task::none()
|
||||
}
|
||||
|
||||
Message::SelectionEffectToggled(enabled) => {
|
||||
self.model_space.selection_effect = enabled;
|
||||
self.sync_model_space_theme(false);
|
||||
self.persist_settings_if_changed();
|
||||
Task::none()
|
||||
}
|
||||
|
||||
// SELECTIONPREVIEW is a bitmask and the two checkboxes own one bit
|
||||
// each: 1 = rollover while idle, 2 = rollover during a command.
|
||||
Message::SelectionPreviewIdleToggled(enabled) => {
|
||||
self.model_space.selection_preview =
|
||||
set_preview_bit(self.model_space.selection_preview, 1, enabled);
|
||||
self.persist_settings_if_changed();
|
||||
Task::none()
|
||||
}
|
||||
|
||||
Message::SelectionPreviewCommandToggled(enabled) => {
|
||||
self.model_space.selection_preview =
|
||||
set_preview_bit(self.model_space.selection_preview, 2, enabled);
|
||||
self.persist_settings_if_changed();
|
||||
Task::none()
|
||||
}
|
||||
|
||||
// "Use Shift to add" is the inverse of PICKADD.
|
||||
Message::ShiftToAddToggled(shift_to_add) => {
|
||||
self.pick_add = !shift_to_add;
|
||||
self.persist_settings_if_changed();
|
||||
Task::none()
|
||||
}
|
||||
|
||||
Message::PickDragRectToggled(rectangle) => {
|
||||
self.pick_drag_rect = rectangle;
|
||||
self.persist_settings_if_changed();
|
||||
Task::none()
|
||||
}
|
||||
|
||||
Message::RestoreSelectionVisualDefaults => {
|
||||
self.model_space.selection_area = true;
|
||||
self.model_space.selection_opacity = 12;
|
||||
@@ -6160,6 +6265,10 @@ impl OpenCADStudio {
|
||||
self.model_space.grip_color = 0;
|
||||
self.model_space.grip_hot = 0;
|
||||
self.model_space.grip_hover = 0;
|
||||
// The button restores the *visual* defaults, which is why the
|
||||
// grip limit is here and PICKADD / PICKDRAG are not — those are
|
||||
// how selection behaves, not how it looks.
|
||||
self.grip_object_limit = crate::app::settings::DEFAULT_GRIP_OBJECT_LIMIT;
|
||||
self.sync_model_space_theme(false);
|
||||
self.persist_settings_if_changed();
|
||||
Task::none()
|
||||
@@ -8677,3 +8786,12 @@ mod free_text_entry_tests {
|
||||
assert_eq!(app.text_entry_mode(), TextEntryMode::Command);
|
||||
}
|
||||
}
|
||||
|
||||
/// Set or clear one bit of `SELECTIONPREVIEW`.
|
||||
fn set_preview_bit(current: u8, bit: u8, enabled: bool) -> u8 {
|
||||
if enabled {
|
||||
current | bit
|
||||
} else {
|
||||
current & !bit
|
||||
}
|
||||
}
|
||||
+143
-24
@@ -4124,6 +4124,10 @@ impl OpenCADStudio {
|
||||
aabb
|
||||
},
|
||||
);
|
||||
// A 186 k-entity box selection sits at ~800 ms in this
|
||||
// handler and two guesses about which step owns it have
|
||||
// both been wrong. Split it.
|
||||
let t_sel = crate::perf::enabled().then(Instant::now);
|
||||
let area_candidates = self.tabs[i].scene.interaction_candidates_in_aabb(
|
||||
all_wires,
|
||||
world_aabb,
|
||||
@@ -4132,6 +4136,9 @@ impl OpenCADStudio {
|
||||
eye,
|
||||
bounds,
|
||||
);
|
||||
let cand_ms = t_sel
|
||||
.map_or(0.0, |t| t.elapsed().as_secs_f64() * 1000.0);
|
||||
let t_hit = crate::perf::enabled().then(Instant::now);
|
||||
let candidate_handles = self.tabs[i]
|
||||
.scene
|
||||
.interaction_candidate_handles(&area_candidates);
|
||||
@@ -4192,20 +4199,31 @@ impl OpenCADStudio {
|
||||
// selection, Shift+box removes the boxed
|
||||
// entities. Esc / empty-space click still clears.
|
||||
// PICKADD 0 (#226): a plain box REPLACES.
|
||||
let hit_ms = t_hit
|
||||
.map_or(0.0, |t| t.elapsed().as_secs_f64() * 1000.0);
|
||||
let t_apply = crate::perf::enabled().then(Instant::now);
|
||||
if self.shift_down || self.select_remove_mode {
|
||||
for h in &handles {
|
||||
self.tabs[i].scene.deselect_entity(*h);
|
||||
}
|
||||
self.tabs[i].scene.deselect_entities(&handles);
|
||||
} else {
|
||||
if !selection_pick_add && !handles.is_empty() {
|
||||
self.tabs[i].scene.deselect_all();
|
||||
}
|
||||
for h in &handles {
|
||||
self.tabs[i].scene.select_entity(*h, false);
|
||||
}
|
||||
self.tabs[i].scene.select_entities(&handles);
|
||||
self.tabs[i].scene.expand_selection_for_groups(&handles);
|
||||
}
|
||||
let apply_ms = t_apply
|
||||
.map_or(0.0, |t| t.elapsed().as_secs_f64() * 1000.0);
|
||||
let t_props = crate::perf::enabled().then(Instant::now);
|
||||
self.refresh_properties();
|
||||
if crate::perf::enabled() {
|
||||
crate::perf_record!(
|
||||
"[perf] select-commit kind=drag-box crossing={crossing} \
|
||||
candidates={cand_ms:.1}ms hit={hit_ms:.1}ms apply={apply_ms:.1}ms \
|
||||
properties={:.1}ms picked={}",
|
||||
t_props.map_or(0.0, |t| t.elapsed().as_secs_f64() * 1000.0),
|
||||
handles.len(),
|
||||
);
|
||||
}
|
||||
selection_just_completed = true;
|
||||
}
|
||||
} else {
|
||||
@@ -4239,9 +4257,7 @@ impl OpenCADStudio {
|
||||
// leaves the current selection untouched so a stray
|
||||
// drag never discards hard-won picks.
|
||||
if self.shift_down || self.select_remove_mode {
|
||||
for h in &handles {
|
||||
self.tabs[i].scene.deselect_entity(*h);
|
||||
}
|
||||
self.tabs[i].scene.deselect_entities(&handles);
|
||||
} else {
|
||||
// PICKADD 0 (#226): a plain marquee REPLACES
|
||||
// the selection (empty results still leave it
|
||||
@@ -4249,9 +4265,7 @@ impl OpenCADStudio {
|
||||
if !selection_pick_add && !handles.is_empty() {
|
||||
self.tabs[i].scene.deselect_all();
|
||||
}
|
||||
for h in &handles {
|
||||
self.tabs[i].scene.select_entity(*h, false);
|
||||
}
|
||||
self.tabs[i].scene.select_entities(&handles);
|
||||
self.tabs[i].scene.expand_selection_for_groups(&handles);
|
||||
}
|
||||
self.refresh_properties();
|
||||
@@ -4268,6 +4282,8 @@ impl OpenCADStudio {
|
||||
if box_anchor.is_none() {
|
||||
let (view_rot, eye, all_wires) = self.pick_view(i, &edit_cam, bounds);
|
||||
let click_world = self.cursor_model_point(i, &edit_cam, p, bounds);
|
||||
let t_arm = crate::perf::enabled().then(Instant::now);
|
||||
let prior_selection = self.tabs[i].scene.selected.len();
|
||||
let click_candidates = self.tabs[i].scene.interaction_pick_candidates_near(
|
||||
all_wires,
|
||||
click_world,
|
||||
@@ -4402,6 +4418,16 @@ impl OpenCADStudio {
|
||||
// (#234). Computed before the selection
|
||||
// borrow so the &self projection can't clash.
|
||||
let anchor_world = self.cursor_model_point(i, &edit_cam, p, bounds);
|
||||
if let Some(t) = t_arm {
|
||||
let arm_ms = t.elapsed().as_secs_f64() * 1000.0;
|
||||
if arm_ms >= 5.0 {
|
||||
crate::perf_record!(
|
||||
"[perf] select-arm {arm_ms:>7.1}ms pick+clear, \
|
||||
was_selected={}",
|
||||
prior_selection,
|
||||
);
|
||||
}
|
||||
}
|
||||
let mut sel = self.tabs[i].scene.selection.borrow_mut();
|
||||
// Full-canvas space: ViewportMove updates
|
||||
// box_current in canvas coords and the overlay
|
||||
@@ -4485,6 +4511,10 @@ impl OpenCADStudio {
|
||||
aabb
|
||||
},
|
||||
);
|
||||
// This is the path a click-move-click window takes, as
|
||||
// opposed to a press-drag; it is the one a large selection
|
||||
// actually goes through.
|
||||
let t_sel = crate::perf::enabled().then(Instant::now);
|
||||
let area_candidates = self.tabs[i].scene.interaction_candidates_in_aabb(
|
||||
all_wires,
|
||||
world_aabb,
|
||||
@@ -4493,9 +4523,16 @@ impl OpenCADStudio {
|
||||
eye,
|
||||
bounds,
|
||||
);
|
||||
let cand_ms =
|
||||
t_sel.map_or(0.0, |t| t.elapsed().as_secs_f64() * 1000.0);
|
||||
let t_hit = crate::perf::enabled().then(Instant::now);
|
||||
let candidate_handles = self.tabs[i]
|
||||
.scene
|
||||
.interaction_candidate_handles(&area_candidates);
|
||||
// `hit` covers five things, and which of them owns it has
|
||||
// never been separated: resolving candidate handles, the wire
|
||||
// box test, the two hatch tests and the two mesh tests.
|
||||
let m_handles = t_hit.map(|t| t.elapsed().as_secs_f64() * 1000.0);
|
||||
let mut handles: Vec<Handle> = scene::pick::hit_test::box_hit(
|
||||
a,
|
||||
p,
|
||||
@@ -4508,6 +4545,7 @@ impl OpenCADStudio {
|
||||
.into_iter()
|
||||
.filter_map(|s| Scene::handle_from_wire_name(s))
|
||||
.collect();
|
||||
let m_wires = t_hit.map(|t| t.elapsed().as_secs_f64() * 1000.0);
|
||||
handles.extend(scene::pick::hit_test::box_hit_hatch(
|
||||
a,
|
||||
p,
|
||||
@@ -4530,6 +4568,7 @@ impl OpenCADStudio {
|
||||
bounds,
|
||||
candidate_handles.as_ref(),
|
||||
));
|
||||
let m_hatch = t_hit.map(|t| t.elapsed().as_secs_f64() * 1000.0);
|
||||
handles.extend(self.tabs[i].scene.mesh_box_hit(
|
||||
a,
|
||||
p,
|
||||
@@ -4548,27 +4587,45 @@ impl OpenCADStudio {
|
||||
bounds,
|
||||
candidate_handles.as_ref(),
|
||||
));
|
||||
let hit_ms = t_hit.map_or(0.0, |t| t.elapsed().as_secs_f64() * 1000.0);
|
||||
let t_filter = crate::perf::enabled().then(Instant::now);
|
||||
// Selection filter: keep only allowed types.
|
||||
handles.retain(|&h| self.tabs[i].scene.passes_selection_filter(h));
|
||||
let filter_ms =
|
||||
t_filter.map_or(0.0, |t| t.elapsed().as_secs_f64() * 1000.0);
|
||||
let t_apply = crate::perf::enabled().then(Instant::now);
|
||||
// Accumulate (issue #83): a plain box adds to the
|
||||
// current selection, Shift+box removes the boxed
|
||||
// entities. An empty box leaves the selection alone
|
||||
// so an accidental empty drag never discards it.
|
||||
// PICKADD 0 (#226): a plain box REPLACES instead.
|
||||
if self.shift_down || self.select_remove_mode {
|
||||
for h in &handles {
|
||||
self.tabs[i].scene.deselect_entity(*h);
|
||||
}
|
||||
self.tabs[i].scene.deselect_entities(&handles);
|
||||
} else {
|
||||
if !selection_pick_add && !handles.is_empty() {
|
||||
self.tabs[i].scene.deselect_all();
|
||||
}
|
||||
for h in &handles {
|
||||
self.tabs[i].scene.select_entity(*h, false);
|
||||
}
|
||||
self.tabs[i].scene.select_entities(&handles);
|
||||
self.tabs[i].scene.expand_selection_for_groups(&handles);
|
||||
}
|
||||
let apply_ms =
|
||||
t_apply.map_or(0.0, |t| t.elapsed().as_secs_f64() * 1000.0);
|
||||
let t_props = crate::perf::enabled().then(Instant::now);
|
||||
self.refresh_properties();
|
||||
if crate::perf::enabled() {
|
||||
crate::perf_record!(
|
||||
"[perf] select-commit kind=window crossing={crossing} \
|
||||
candidates={cand_ms:.1}ms hit={hit_ms:.1}ms [handles={:.1} wires={:.1} \
|
||||
hatch={:.1} mesh={:.1}] filter={filter_ms:.1}ms apply={apply_ms:.1}ms \
|
||||
properties={:.1}ms picked={}",
|
||||
m_handles.unwrap_or(0.0),
|
||||
m_wires.unwrap_or(0.0) - m_handles.unwrap_or(0.0),
|
||||
m_hatch.unwrap_or(0.0) - m_wires.unwrap_or(0.0),
|
||||
hit_ms - m_hatch.unwrap_or(0.0),
|
||||
t_props.map_or(0.0, |t| t.elapsed().as_secs_f64() * 1000.0),
|
||||
handles.len(),
|
||||
);
|
||||
}
|
||||
let mut sel = self.tabs[i].scene.selection.borrow_mut();
|
||||
sel.box_last = Some((a, p));
|
||||
sel.box_last_crossing = crossing;
|
||||
@@ -4704,15 +4761,17 @@ impl OpenCADStudio {
|
||||
return self.begin_text_edit(handle);
|
||||
}
|
||||
// Double-clicking a block with attributes opens the
|
||||
// attribute editor (edit its values); a block with
|
||||
// no attributes opens the block editor (BEDIT) — a
|
||||
// space tab scoped to the block's own geometry, so
|
||||
// edits reflect in every instance. (#136, #192, #261)
|
||||
// attribute editor (edit its values). For blocks without
|
||||
// attributes, Options > Drawing chooses BEDIT (default)
|
||||
// or in-place REFEDIT. (#136, #192, #261)
|
||||
let insert_has_attrs = matches!(
|
||||
self.tabs[i].scene.document.get_entity(handle),
|
||||
Some(AcadEntityType::Insert(ins)) if !ins.attributes.is_empty()
|
||||
);
|
||||
if insert_has_attrs && self.tabs[i].active_block_edit.is_none() {
|
||||
if insert_has_attrs
|
||||
&& self.double_click_block_attedit
|
||||
&& self.tabs[i].active_block_edit.is_none()
|
||||
{
|
||||
return Task::done(Message::AttrEditorOpen(handle));
|
||||
}
|
||||
let is_insert = matches!(
|
||||
@@ -4720,8 +4779,14 @@ impl OpenCADStudio {
|
||||
Some(AcadEntityType::Insert(_))
|
||||
);
|
||||
if is_insert && self.tabs[i].refedit_session.is_none() {
|
||||
let command = if self.double_click_block_refedit {
|
||||
"REFEDIT_BEGIN"
|
||||
} else {
|
||||
"BEDIT_BEGIN"
|
||||
};
|
||||
return Task::done(Message::Command(format!(
|
||||
"BEDIT_BEGIN:{}",
|
||||
"{}:{}",
|
||||
command,
|
||||
handle.value()
|
||||
)));
|
||||
}
|
||||
@@ -5744,3 +5809,57 @@ impl OpenCADStudio {
|
||||
Task::none()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod selection_preview_tests {
|
||||
use super::*;
|
||||
use crate::app::{HoverDwell, OpenCADStudio, HOVER_DWELL_MS};
|
||||
|
||||
/// Drive one settled rollover pick over a line and report what the scene
|
||||
/// ended up highlighting.
|
||||
fn rollover_hits(preview: u8) -> bool {
|
||||
let mut app = OpenCADStudio::new_for_test();
|
||||
app.automation_op(r#"{"op":"new"}"#);
|
||||
let i = app.active_tab;
|
||||
app.model_space.selection_preview = preview;
|
||||
let _ = app.run_command_line("LINE 0,0 10,10");
|
||||
app.tabs[i].scene.selection.borrow_mut().vp_size = (800.0, 600.0);
|
||||
let _ = app.run_command_line("ZOOM EXTENTS");
|
||||
|
||||
// The pick only runs once the cursor has been still for the dwell
|
||||
// window, so the arming timestamp is backdated past it.
|
||||
app.hover_dwell = Some(HoverDwell {
|
||||
last_move_at: Instant::now()
|
||||
- std::time::Duration::from_millis(HOVER_DWELL_MS as u64 * 2),
|
||||
point: iced::Point::new(400.0, 300.0),
|
||||
tile_size: (800.0, 600.0),
|
||||
tab: i,
|
||||
});
|
||||
let _ = app.on_hover_dwell_tick();
|
||||
app.tabs[i].scene.hover_highlight.is_some()
|
||||
}
|
||||
|
||||
/// Bit 1 of `SELECTIONPREVIEW` is the rollover that runs with no command
|
||||
/// active, and the Options card gives it a checkbox. The config comment
|
||||
/// used to describe the bits the wrong way round, so which bit does what
|
||||
/// is worth asserting rather than reading.
|
||||
#[test]
|
||||
fn the_idle_rollover_follows_bit_one() {
|
||||
assert!(
|
||||
rollover_hits(1),
|
||||
"bit 1 set: the line under the cursor must be highlighted",
|
||||
);
|
||||
assert!(
|
||||
rollover_hits(3),
|
||||
"both bits set: still highlighted",
|
||||
);
|
||||
assert!(
|
||||
!rollover_hits(0),
|
||||
"preview off: nothing may be highlighted",
|
||||
);
|
||||
assert!(
|
||||
!rollover_hits(2),
|
||||
"only the in-command bit: the idle rollover stays off",
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user