fix(viewport): correct ViewCube flip + activation/border for off-screen viewports

ViewCube "already there → flip to opposite" tested the main (paper) camera,
which always looks straight down, so inside a floating viewport clicking
"top" landed on "bottom". Route the check through active_gaze_dir() — the
gaze of the camera the cube actually reflects.

Off-screen viewports:
- The active-viewport outline clamped only its origin while keeping full
  width/height, shifting the whole border inward. Clip it to the visible
  canvas (origin AND size) so it hugs the real viewport edge.
- Double-click activation tested each viewport's full paper rect, so a
  click on empty space beside a viewport that runs off-screen matched its
  partly-off-canvas rect and switched to it. Activation now keys on the
  visible on-screen rect (viewport_at_screen_point), picks the smallest
  (topmost) on overlap, and accepts the border-wire pick only when the
  click also lands inside that viewport's visible rect — so a far
  viewport's edge passing near the cursor can't steal the activation.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
Hakan SevenandClaude Opus 4.8 committed 2026-06-24 23:14:46 +03:00
1 parent 7f36cf512f
commit bd1c51c682
3 files changed
+117 -37

No files matched your search

+28 -15
View File
@@ -4200,7 +4200,7 @@ impl OpenCADStudio {
};
// 1) Try direct wire hit — works when the border is clicked.
let hit_vp: Option<acadrust::Handle> = {
let wire_hit: Option<acadrust::Handle> = {
let (view_rot, eye) = { let c = self.tabs[i].scene.camera.borrow(); (c.view_proj_rte(bounds), c.eye()) };
let all_wires = self.tabs[i].scene.hit_test_wires();
scene::pick::hit_test::click_hit(p, &all_wires[..], view_rot, eye, bounds)
@@ -4220,19 +4220,35 @@ impl OpenCADStudio {
})
};
// 2) Geometric fallback: check if the cursor is inside any
// viewport's bounding rectangle in paper space. This handles
// double-clicks on model-entity content wires or empty areas.
let hit_vp = hit_vp.or_else(|| {
let paper_pt = self.tabs[i]
.scene
.camera
.borrow()
.pick_on_target_plane(p, bounds);
// 2) Decide which viewport (if any) the click enters,
// keyed on the *visible* on-screen rectangle. Screen-space
// (not the full paper rect) so a click on the empty area
// beside an off-screen viewport doesn't match its
// partly-off-canvas rect and switch to it by mistake.
// The wire hit only refines *which* visible viewport
// (border precision / overlap), and only when the click
// actually lands inside THAT viewport's visible rect —
// otherwise its border-wire pick tolerance could match a
// far viewport whose edge merely passes near the cursor
// (e.g. clicking the overlap of two viewports while a
// third's border runs nearby) and enter the wrong one.
let screen_hit = self
.tabs[i]
.scene
.viewport_at_screen_point(p.x, p.y, (vw, vh));
let wire_in_visible = wire_hit.filter(|&h| {
self.tabs[i]
.scene
.viewport_at_paper_point(paper_pt.x as f32, paper_pt.y as f32)
.viewport_screen_rect(h, (vw, vh))
.is_some_and(|r| {
let x0 = r.x.max(0.0);
let y0 = r.y.max(0.0);
let x1 = (r.x + r.width).min(vw);
let y1 = (r.y + r.height).min(vh);
p.x >= x0 && p.x <= x1 && p.y >= y0 && p.y <= y1
})
});
let hit_vp = wire_in_visible.or(screen_hit);
if let Some(handle) = hit_vp {
return Task::done(Message::EnterViewport(handle));
@@ -4421,10 +4437,7 @@ impl OpenCADStudio {
// "Already there → flip to opposite" check: compare the
// current gaze direction with the region's target gaze.
let target_dir = r_ucs.transform_vector3(region.snap_direction());
let cur_dir = {
let cam = self.tabs[i].scene.camera.borrow();
cam.rotation * glam::Vec3::Z
};
let cur_dir = self.tabs[i].scene.active_gaze_dir();
if cur_dir.dot(target_dir) > 0.9999 {
region = region.opposite();
}
+10 -2
View File
@@ -469,8 +469,16 @@ impl OpenCADStudio {
None
};
if let Some(rect) = active_vp_rect {
// Clip the outline to the visible canvas. Clamping only the origin
// (max(0.0)) while keeping the full width/height shifted the whole
// outline inward when the viewport ran off the top/left edge, so
// its drawn border no longer matched the real viewport — clicks
// that looked outside landed in (and activated) another viewport.
let (cw, ch) = tab.scene.selection.borrow().vp_size;
let x = rect.x.max(0.0);
let y = rect.y.max(0.0);
let vw = ((rect.x + rect.width).min(cw) - x).max(1.0);
let vh = ((rect.y + rect.height).min(ch) - y).max(1.0);
// Highlight the active viewport with a 2-px border so its
// boundary is always visible over the GPU shader.
const VP_BORDER: Color = Color {
@@ -481,8 +489,8 @@ impl OpenCADStudio {
};
let border_frame = container(
Space::new()
.width(iced::Length::Fixed(rect.width.max(1.0)))
.height(iced::Length::Fixed(rect.height.max(1.0))),
.width(iced::Length::Fixed(vw))
.height(iced::Length::Fixed(vh)),
)
.style(move |_: &Theme| container::Style {
border: iced::Border {