forked from 1b-agent/opencadstudio-upstream
feat(viewport): tile-relative input + overlays in unified model
Route every per-frame interaction through the active model tile's screen rectangle so a tiled VPORTS layout behaves like independent panes while still sharing one shader widget: - pick / draw / snap / box+lasso: cursor mapped to tile-local space; the hover-activates-tile rule on ViewportMove keeps the active tile under the cursor, so the picked camera matches what the user sees. - pan: switched to Camera::pan_screen(dx, dy, viewport_height) so the world-per-pixel rate matches the active tile's height instead of the full canvas. - grid: clipped via Frame::with_clip to the active tile rect; world→screen adds the tile origin so the lines align with the panel. - UCS icon: origin offset by the tile rect so the gizmo sits in the active tile's bottom-left corner. - render-mode picker (+ split buttons) and ViewCube hit-area: positioned via active_model_tile_bounds with leading Spaces, mirroring the floating-viewport pattern. - ViewCube hover: driven by the CursorMoved/ViewportMove handlers into a new Scene::viewcube_hover cell, so the highlight survives the cube hit-area overlay masking events from shader::Program::update. Co-Authored-By: Claude Opus 4.7 <[email protected]>
This commit is contained in:
1 parent
6fe25e4b5f
commit
417a708275
7 files changed
+330
-86
No files matched your search
+45
-38
@@ -275,12 +275,11 @@ impl OpenCADStudio {
|
||||
let grips: Vec<overlay::GripMarker> =
|
||||
if tab.active_cmd.is_none() && !tab.selected_grips.is_empty() {
|
||||
let (vw, vh) = tab.scene.selection.borrow().vp_size;
|
||||
let bounds = iced::Rectangle {
|
||||
x: 0.0,
|
||||
y: 0.0,
|
||||
width: vw,
|
||||
height: vh,
|
||||
};
|
||||
// Overlays project through the active tile's camera, so
|
||||
// they must use the active tile's screen rectangle (with
|
||||
// its canvas offset) — not the whole canvas — or they
|
||||
// land in the wrong place in a tiled layout.
|
||||
let bounds = tab.scene.active_model_tile_bounds(vw, vh);
|
||||
let sel_h = tab.selected_handle;
|
||||
let screen_grips = if is_paper {
|
||||
let cam = tab.scene.camera.borrow();
|
||||
@@ -322,12 +321,10 @@ impl OpenCADStudio {
|
||||
};
|
||||
|
||||
let (vw, vh) = tab.scene.selection.borrow().vp_size;
|
||||
let vp_bounds = iced::Rectangle {
|
||||
x: 0.0,
|
||||
y: 0.0,
|
||||
width: vw,
|
||||
height: vh,
|
||||
};
|
||||
// Active tile rectangle (canvas-offset included) so grid / UCS
|
||||
// icon / crosshair project through the active pane's camera at
|
||||
// the correct place and scale.
|
||||
let vp_bounds = tab.scene.active_model_tile_bounds(vw, vh);
|
||||
|
||||
let grid = if self.show_grid {
|
||||
let cam = tab.scene.camera.borrow();
|
||||
@@ -529,13 +526,21 @@ impl OpenCADStudio {
|
||||
})
|
||||
};
|
||||
let bar = row![info, split_btn("▤", true), split_btn("▥", false)].spacing(4);
|
||||
viewport_stack = viewport_stack.push(
|
||||
container(iced::widget::opaque(bar))
|
||||
.width(Fill)
|
||||
.height(Fill)
|
||||
.align_x(iced::alignment::Horizontal::Left)
|
||||
.align_y(iced::alignment::Vertical::Top),
|
||||
);
|
||||
// Position the bar at the active model tile's top-left corner so
|
||||
// it follows the active panel in a tiled layout (full canvas when
|
||||
// a single tile fills the window). Leading Spaces offset it.
|
||||
let (vw, vh) = tab.scene.selection.borrow().vp_size;
|
||||
let rect = tab.scene.active_model_tile_bounds(vw, vh);
|
||||
let bar_layer = column![
|
||||
Space::new().height(iced::Length::Fixed(rect.y.max(0.0))),
|
||||
row![
|
||||
Space::new().width(iced::Length::Fixed(rect.x.max(0.0))),
|
||||
iced::widget::opaque(bar),
|
||||
],
|
||||
]
|
||||
.width(Fill)
|
||||
.height(Fill);
|
||||
viewport_stack = viewport_stack.push(bar_layer);
|
||||
}
|
||||
|
||||
// Active paper-space viewport overlays: a render-mode picker in
|
||||
@@ -592,26 +597,28 @@ impl OpenCADStudio {
|
||||
}
|
||||
|
||||
if self.show_viewcube && !is_paper && !tab.is_start {
|
||||
let cube_click: Element<'_, Message> = container(
|
||||
mouse_area(container(
|
||||
iced::widget::Space::new()
|
||||
.width(iced::Length::Fixed(VIEWCUBE_HIT_SIZE))
|
||||
.height(iced::Length::Fixed(VIEWCUBE_HIT_SIZE)),
|
||||
))
|
||||
.on_move(Message::CursorMoved)
|
||||
.on_press(Message::ViewportClick),
|
||||
)
|
||||
.align_right(Fill)
|
||||
.align_top(Fill)
|
||||
.padding(iced::Padding {
|
||||
top: VIEWCUBE_PAD,
|
||||
right: VIEWCUBE_PAD,
|
||||
bottom: 0.0,
|
||||
left: 0.0,
|
||||
})
|
||||
// Place the ViewCube hit area in the active model tile's top-right
|
||||
// corner so it tracks the active panel in a tiled layout. The hit
|
||||
// test in update.rs already maps clicks through the active tile.
|
||||
let (vw, vh) = tab.scene.selection.borrow().vp_size;
|
||||
let rect = tab.scene.active_model_tile_bounds(vw, vh);
|
||||
let cube_x = (rect.x + rect.width - VIEWCUBE_HIT_SIZE - VIEWCUBE_PAD).max(0.0);
|
||||
let cube_y = (rect.y + VIEWCUBE_PAD).max(0.0);
|
||||
let cube_click = column![
|
||||
Space::new().height(iced::Length::Fixed(cube_y)),
|
||||
row![
|
||||
Space::new().width(iced::Length::Fixed(cube_x)),
|
||||
mouse_area(
|
||||
iced::widget::Space::new()
|
||||
.width(iced::Length::Fixed(VIEWCUBE_HIT_SIZE))
|
||||
.height(iced::Length::Fixed(VIEWCUBE_HIT_SIZE)),
|
||||
)
|
||||
.on_move(Message::CursorMoved)
|
||||
.on_press(Message::ViewportClick),
|
||||
],
|
||||
]
|
||||
.width(Fill)
|
||||
.height(Fill)
|
||||
.into();
|
||||
.height(Fill);
|
||||
viewport_stack = viewport_stack.push(cube_click);
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user