navigation: pan and orbit keep going past the viewport edge

A middle-button drag stopped when the pointer left the viewport: the
mouse area reports moves only inside, and leaving reset the drag, so
the release outside never arrived (#1400). The viewport and each model
pane now carry an input layer that keeps reporting a drag they started,
with the pointer anywhere in the window, and the release outside ends
it; leaving no longer cancels a middle-button drag.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Hakan SevenandClaude Opus 5.5 committed 2026-10-15 09:17:06 +03:00
1 parent 39c3e9af58
commit cc78899363
2 files changed
+91 -14

No files matched your search

+7 -3
View File
@@ -3187,9 +3187,13 @@ impl OpenCADStudio {
sel.input.right_last_pos = None;
sel.input.right_dragging = false;
sel.input.right_click_entered = false;
sel.input.middle_down = false;
sel.input.middle_last_pos = None;
sel.orbit.pivot = None;
// A pan or orbit keeps following the pointer outside the viewport;
// its input layer reports the moves and the release there.
if !sel.input.middle_down {
sel.input.middle_down = false;
sel.input.middle_last_pos = None;
sel.orbit.pivot = None;
}
sel.gesture.box_anchor = None;
sel.gesture.box_anchor_world = None;
sel.gesture.box_current = None;
+84 -11
View File
@@ -23,24 +23,93 @@ use iced_aw::ContextMenu;
struct ViewportPickSurface;
impl iced::widget::canvas::Program<Message> for ViewportPickSurface {
type State = ();
/// Whether a middle-button drag began inside the viewport.
type State = bool;
fn update(
&self,
_state: &mut Self::State,
middle_drag: &mut Self::State,
event: &iced::Event,
bounds: iced::Rectangle,
cursor: iced::mouse::Cursor,
) -> Option<iced::widget::canvas::Action<Message>> {
if let iced::Event::Mouse(iced::mouse::Event::ButtonPressed(iced::mouse::Button::Left)) =
event
{
return cursor.position_in(bounds).map(|position| {
iced::widget::canvas::Action::publish(Message::ViewportLeftPressAt(position))
.and_capture()
});
use iced::mouse::{Button, Event as Mouse};
use iced::widget::canvas::Action;
let iced::Event::Mouse(event) = event else {
return None;
};
match event {
Mouse::ButtonPressed(Button::Left) => cursor.position_in(bounds).map(|position| {
Action::publish(Message::ViewportLeftPressAt(position)).and_capture()
}),
Mouse::ButtonPressed(Button::Middle) => {
*middle_drag = cursor.is_over(bounds);
None
}
// A pan or orbit keeps following the pointer past the viewport
// edge; the mouse area only reports it inside.
Mouse::CursorMoved { position } if *middle_drag && !cursor.is_over(bounds) => Some(
Action::publish(Message::ViewportMove(iced::Point::new(
position.x - bounds.x,
position.y - bounds.y,
))),
),
Mouse::ButtonReleased(Button::Middle) if *middle_drag => {
*middle_drag = false;
(!cursor.is_over(bounds)).then(|| Action::publish(Message::ViewportMiddleRelease))
}
_ => None,
}
}
fn draw(
&self,
_state: &Self::State,
_renderer: &iced::Renderer,
_theme: &iced::Theme,
_bounds: iced::Rectangle,
_cursor: iced::mouse::Cursor,
) -> Vec<iced::widget::canvas::Geometry> {
Vec::new()
}
}
/// Input layer of one Model pane: keeps a pan or orbit that began in the pane
/// following the pointer past its edge, and hears the release there.
struct PaneDragSurface(usize);
impl iced::widget::canvas::Program<Message> for PaneDragSurface {
/// Whether a middle-button drag began inside this pane.
type State = bool;
fn update(
&self,
middle_drag: &mut Self::State,
event: &iced::Event,
bounds: iced::Rectangle,
cursor: iced::mouse::Cursor,
) -> Option<iced::widget::canvas::Action<Message>> {
use iced::mouse::{Button, Event as Mouse};
use iced::widget::canvas::Action;
let iced::Event::Mouse(event) = event else {
return None;
};
match event {
Mouse::ButtonPressed(Button::Middle) => {
*middle_drag = cursor.is_over(bounds);
None
}
Mouse::CursorMoved { position } if *middle_drag && !cursor.is_over(bounds) => {
let local = iced::Point::new(position.x - bounds.x, position.y - bounds.y);
Some(Action::publish(Message::PaneMove(self.0, local)))
}
Mouse::ButtonReleased(Button::Middle) if *middle_drag => {
*middle_drag = false;
(!cursor.is_over(bounds))
.then(|| Action::publish(Message::PaneMiddleRelease(self.0)))
}
_ => None,
}
None
}
fn draw(
@@ -3180,7 +3249,11 @@ fn start_primary_style(theme: &Theme) -> iced::widget::text::Style {
/// that emits pane-tagged viewport events (`idx` = the pane's tile index). The
/// handlers offset the pane-local point to canvas coords and focus the pane.
fn pane_mouse_area<'a>(idx: usize) -> Element<'a, Message> {
mouse_area(container(Space::new().width(Fill).height(Fill)))
mouse_area(container(
iced::widget::canvas(PaneDragSurface(idx))
.width(Fill)
.height(Fill),
))
.on_move(move |p| Message::PaneMove(idx, p))
.on_press(Message::PanePress(idx))
.on_release(Message::PaneRelease(idx))