forked from 1b-agent/opencadstudio-upstream
feat(pan): interactive PAN mode with left-drag (no middle button needed)
The PAN command/tool dispatched a command nobody handled, so it did nothing. Combined with no middle mouse button on a trackpad/web client, there was no way to pan at all (issue 164). PAN (and the "P" alias) now enters a pan mode: a left-button drag pans the view, reusing the existing middle-button pan path. Exit with Esc or by starting another command. While active the crosshair is hidden and the cursor becomes a hand (open when hovering, closed while dragging). PAN/P are also added to command-line autocomplete. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
1 parent
efade01896
commit
69ea050124
5 files changed
+67
-2
No files matched your search
+13
-1
@@ -29,6 +29,9 @@ impl OpenCADStudio {
|
||||
self.tabs[i].scene.clear_preview_wire();
|
||||
self.tabs[i].active_cmd = None;
|
||||
}
|
||||
// Starting any command leaves interactive PAN mode (the PAN arm below
|
||||
// re-enables it).
|
||||
self.tabs[i].pan_mode = false;
|
||||
// Reset the last committed point so the first click of the new command
|
||||
// is not constrained by ortho/polar relative to a previous command's endpoint.
|
||||
self.last_point = None;
|
||||
@@ -5148,6 +5151,14 @@ impl OpenCADStudio {
|
||||
self.command_line.push_output("Display regenerated.");
|
||||
}
|
||||
|
||||
// Interactive pan: left-drag pans the view until Esc. The only pan
|
||||
// path when there is no middle mouse button (trackpad / web).
|
||||
"PAN" | "P" => {
|
||||
self.tabs[i].pan_mode = true;
|
||||
self.command_line
|
||||
.push_output("PAN: drag with the left mouse button. Press Esc to exit.");
|
||||
}
|
||||
|
||||
// ── TABLE cell editing ─────────────────────────────────────────────
|
||||
// TABLE CELL <row> <col> <text> — set text for a cell in the selected Table
|
||||
cmd if cmd.starts_with("TABLE ") => {
|
||||
@@ -5972,7 +5983,8 @@ inventory::submit!(crate::command::CommandRegistration {
|
||||
"LAYER", "LAYERS", "LAYISO", "LAYON", "LAYOUTMANAGER", "LAYOUTPANEL", "LAYOUTTAB", "LAYTHW",
|
||||
"LAYUNISO", "LI", "LINETYPE", "LIST", "LTSCALE", "LWDISPLAY", "MASSPROP", "MLEADERSTYLE",
|
||||
"MLSTYLE", "MS", "MSPACE", "NAVVCUBE", "NEW", "OBJIMPORT", "OPEN", "ORTHO",
|
||||
"PAGESETUP", "PERF", "PERSP", "PLOT", "PLOTSTYLE", "PLOTSTYLEEDITOR", "PLOTSTYLEPANEL", "PR",
|
||||
"P", "PAN", "PAGESETUP", "PERF", "PERSP", "PLOT", "PLOTSTYLE", "PLOTSTYLEEDITOR",
|
||||
"PLOTSTYLEPANEL", "PR",
|
||||
"PRINT", "PROPERTIES", "PROPS", "PSPACE", "PURGE", "QS", "QSAVE", "QSELECT",
|
||||
"QUIT", "REDO", "REDRAW", "REDRWALL", "REGEN", "REGENALL", "RENAME", "REPORT",
|
||||
"SA", "SAVE", "SAVEAS", "SCALETEXT", "SELECTALL", "SELECTSIMILAR", "SELSIM", "SHEETSET",
|
||||
|
||||
@@ -158,6 +158,11 @@ pub(super) struct DocumentTab {
|
||||
/// of the model-space shader. The scene is still constructed so the
|
||||
/// rest of the code can treat it as a normal tab when reading.
|
||||
pub(super) is_start: bool,
|
||||
/// Interactive PAN mode (the PAN command / tool). While active, a left-
|
||||
/// button drag pans the view instead of selecting — the only pan path on a
|
||||
/// device with no middle mouse button (a trackpad / web client). Exited
|
||||
/// with Esc or by starting another command.
|
||||
pub(super) pan_mode: bool,
|
||||
/// Per-plugin document state (`plugin::BuiltinPlugin` manifest id → state).
|
||||
#[cfg_attr(target_arch = "wasm32", allow(dead_code))]
|
||||
pub(super) plugin_state: HashMap<&'static str, Box<dyn Any + Send + Sync>>,
|
||||
@@ -377,6 +382,7 @@ impl DocumentTab {
|
||||
active_mleader_style: "Standard".to_string(),
|
||||
last_synced_camera_gen: 0,
|
||||
is_start: false,
|
||||
pan_mode: false,
|
||||
plugin_state: HashMap::new(),
|
||||
suspended_cmd: None,
|
||||
}
|
||||
|
||||
@@ -1835,6 +1835,18 @@ impl OpenCADStudio {
|
||||
self.text_inline_cancel();
|
||||
return self.post_editor_closed(false);
|
||||
}
|
||||
// Leave interactive PAN mode (and end any in-flight pan drag).
|
||||
if self.tabs[self.active_tab].pan_mode {
|
||||
let i = self.active_tab;
|
||||
self.tabs[i].pan_mode = false;
|
||||
{
|
||||
let mut sel = self.tabs[i].scene.selection.borrow_mut();
|
||||
sel.middle_down = false;
|
||||
sel.middle_last_pos = None;
|
||||
}
|
||||
self.command_line.push_output("PAN ended.");
|
||||
return Task::none();
|
||||
}
|
||||
// Grip popup intercepts Escape — dismisses the menu
|
||||
// without doing anything else.
|
||||
if self.grip_popup.take().is_some() {
|
||||
@@ -3217,6 +3229,16 @@ impl OpenCADStudio {
|
||||
};
|
||||
let (vw, vh) = vp_size;
|
||||
|
||||
// PAN mode: a left press begins a pan drag. Reuse the middle-
|
||||
// button pan path (the move handler pans whenever `middle_down`),
|
||||
// so no selection/pick logic runs while panning.
|
||||
if self.tabs[i].pan_mode {
|
||||
let mut sel = self.tabs[i].scene.selection.borrow_mut();
|
||||
sel.middle_down = true;
|
||||
sel.middle_last_pos = Some(p);
|
||||
return Task::none();
|
||||
}
|
||||
|
||||
if vw > 1.0 && vh > 1.0 {
|
||||
let rot = self.tabs[i].scene.active_view_rotation_mat();
|
||||
// Map the cursor into whichever area owns the cube (active
|
||||
@@ -3382,6 +3404,15 @@ impl OpenCADStudio {
|
||||
Message::ViewportLeftRelease => {
|
||||
let i = self.active_tab;
|
||||
|
||||
// PAN mode: end the pan drag but stay in pan mode for the next
|
||||
// drag (exit is Esc / another command). Mirror of the press.
|
||||
if self.tabs[i].pan_mode {
|
||||
let mut sel = self.tabs[i].scene.selection.borrow_mut();
|
||||
sel.middle_down = false;
|
||||
sel.middle_last_pos = None;
|
||||
return Task::none();
|
||||
}
|
||||
|
||||
// End an in-flight tile-divider drag. Any tile that fell
|
||||
// below the minimum (viewcube fits comfortably) gets
|
||||
// absorbed into its longest-contact neighbour, so the
|
||||
|
||||
@@ -263,6 +263,7 @@ impl OpenCADStudio {
|
||||
tab.last_cursor_screen,
|
||||
!is_paper && self.show_viewcube,
|
||||
tile_edges,
|
||||
tab.pan_mode,
|
||||
)
|
||||
};
|
||||
|
||||
|
||||
+16
-1
@@ -129,6 +129,7 @@ pub fn selection_overlay<'a>(
|
||||
cursor_screen: Point,
|
||||
show_viewcube: bool,
|
||||
tile_edges: Vec<crate::scene::TileEdge>,
|
||||
pan_mode: bool,
|
||||
) -> Element<'a, Message> {
|
||||
canvas(SelectionCanvas {
|
||||
selection,
|
||||
@@ -140,6 +141,7 @@ pub fn selection_overlay<'a>(
|
||||
cursor_screen,
|
||||
show_viewcube,
|
||||
tile_edges,
|
||||
pan_mode,
|
||||
})
|
||||
.width(Length::Fill)
|
||||
.height(Length::Fill)
|
||||
@@ -156,6 +158,9 @@ struct SelectionCanvas {
|
||||
cursor_screen: Point,
|
||||
show_viewcube: bool,
|
||||
tile_edges: Vec<crate::scene::TileEdge>,
|
||||
/// Interactive PAN mode: the crosshair is hidden and the cursor becomes a
|
||||
/// hand so the viewport reads as a draggable surface.
|
||||
pan_mode: bool,
|
||||
}
|
||||
|
||||
impl SelectionCanvas {
|
||||
@@ -207,6 +212,15 @@ impl canvas::Program<Message> for SelectionCanvas {
|
||||
bounds: iced::Rectangle,
|
||||
cursor: mouse::Cursor,
|
||||
) -> mouse::Interaction {
|
||||
// PAN mode owns the whole viewport: an open hand when hovering, a
|
||||
// closed hand while dragging.
|
||||
if self.pan_mode && cursor.is_over(bounds) {
|
||||
return if self.selection.middle_down {
|
||||
mouse::Interaction::Grabbing
|
||||
} else {
|
||||
mouse::Interaction::Grab
|
||||
};
|
||||
}
|
||||
if self.show_viewcube {
|
||||
if let Some(pos) = cursor.position_in(bounds) {
|
||||
use crate::scene::{VIEWCUBE_DRAW_PX, VIEWCUBE_PAD};
|
||||
@@ -757,7 +771,8 @@ impl canvas::Program<Message> for SelectionCanvas {
|
||||
// arrow (see `mouse_interaction`); drawing the CAD crosshair on
|
||||
// top of it would double up the visual feedback.
|
||||
let over_divider = self.tile_edge_under(cursor, bounds).is_some();
|
||||
if !over_viewcube && !over_divider {
|
||||
// PAN mode replaces the crosshair with a hand cursor.
|
||||
if !over_viewcube && !over_divider && !self.pan_mode {
|
||||
if let Some(cp) = self.selection.last_move_pos {
|
||||
let color = Color {
|
||||
r: 0.85,
|
||||
|
||||
Reference in new issue
Block a user