Canvas: middle-mouse drag pans the view
Dragging with the middle mouse button pans the canvas with any tool, like Space-drag, and shows the grabbing hand. Tools still only see the primary button. ui.click / ui.drag accept "button": "middle". Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
e8d6cedfea
commit
9bbaa2940a
|
|
@ -328,7 +328,14 @@ pub fn show(app: &mut DesignApp, ui: &mut egui::Ui) {
|
|||
// Cursor.
|
||||
if let Some(p) = resp.hover_pos().filter(|p| screen.contains(*p)) {
|
||||
let space = ui.input(|i| i.key_down(egui::Key::Space)) && !app.session.wants_text();
|
||||
let c = if space { Cursor::Hand } else { app.session.cursor(xf.to_canvas(p), ui.input(|i| mods(i, false)), app.view_info()) };
|
||||
let middle = ui.input(|i| i.pointer.middle_down());
|
||||
let c = if middle {
|
||||
Cursor::HandGrab
|
||||
} else if space {
|
||||
Cursor::Hand
|
||||
} else {
|
||||
app.session.cursor(xf.to_canvas(p), ui.input(|i| mods(i, false)), app.view_info())
|
||||
};
|
||||
ui.ctx().set_cursor_icon(cursor_icon(c));
|
||||
}
|
||||
}
|
||||
|
|
@ -1345,9 +1352,10 @@ fn handle_input(app: &mut DesignApp, ui: &mut egui::Ui, resp: &egui::Response, r
|
|||
}
|
||||
}
|
||||
// Space-drag = hand (unless a tool drag is under way: then Space is a modifier, e.g. Live
|
||||
// Distribute while resizing).
|
||||
// Distribute while resizing). Middle-drag pans too, with any tool; tools only
|
||||
// see the primary button.
|
||||
let tool_drag: bool = ui.data(|d| d.get_temp(egui::Id::new(("canvas_pointer_down", app.pane)))).unwrap_or(false);
|
||||
if space && !tool_drag && resp.dragged() {
|
||||
if !tool_drag && ((space && resp.dragged()) || resp.dragged_by(egui::PointerButton::Middle)) {
|
||||
let d = xf.unrotate_delta(resp.drag_delta());
|
||||
if let Some(v) = app.view_mut() {
|
||||
v.origin = Point::new(v.origin.x - d.x as f64 / v.zoom, v.origin.y - d.y as f64 / v.zoom);
|
||||
|
|
|
|||
|
|
@ -183,6 +183,7 @@ pub fn handle(app: &mut DesignApp, ctx: &egui::Context, req: &ControlRequest) ->
|
|||
let f = |k: &str| p.get(k).and_then(Value::as_f64).unwrap_or(0.0) as f32;
|
||||
let button = match s("button") {
|
||||
Some("right") | Some("secondary") => egui::PointerButton::Secondary,
|
||||
Some("middle") => egui::PointerButton::Middle,
|
||||
_ => egui::PointerButton::Primary,
|
||||
};
|
||||
let b = |n: &str| p.get(n).and_then(Value::as_bool).unwrap_or(false);
|
||||
|
|
|
|||
|
|
@ -2173,6 +2173,43 @@ mod tests {
|
|||
assert!((app.view().unwrap().zoom - z0).abs() < 1e-9, "back at the zoom it started from");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn middle_drag_pans_with_any_tool() {
|
||||
let mut app = crate::DesignApp::new(designcraft_engine::Session::new(), crate::Services::default());
|
||||
app.session.execute("file.new", &json!({})).unwrap();
|
||||
let ctx = egui::Context::default();
|
||||
let frame = |app: &mut crate::DesignApp, events: Vec<egui::Event>| {
|
||||
let input = egui::RawInput {
|
||||
screen_rect: Some(egui::Rect::from_min_size(egui::Pos2::ZERO, egui::vec2(1400.0, 900.0))),
|
||||
events,
|
||||
..Default::default()
|
||||
};
|
||||
let mut out = ctx.run_ui(input, |ui| {
|
||||
app.logic(&ui.ctx().clone());
|
||||
app.ui(ui);
|
||||
});
|
||||
out.textures_delta.clear();
|
||||
};
|
||||
frame(&mut app, vec![]);
|
||||
frame(&mut app, vec![]);
|
||||
// A drawing tool: a primary drag would make a frame, a middle drag must not.
|
||||
app.select_tool("rectangleFrame");
|
||||
let p = app.canvas_rect.unwrap().center();
|
||||
let v0 = *app.view().unwrap();
|
||||
let items = |app: &crate::DesignApp| app.session.active().unwrap().doc.spreads.iter().map(|s| s.items.len()).sum::<usize>();
|
||||
let items0 = items(&app);
|
||||
let button = |pos, pressed| egui::Event::PointerButton { pos, button: egui::PointerButton::Middle, pressed, modifiers: Default::default() };
|
||||
frame(&mut app, vec![egui::Event::PointerMoved(p), button(p, true)]);
|
||||
for i in 1..=8 {
|
||||
frame(&mut app, vec![egui::Event::PointerMoved(p + egui::vec2(10.0 * i as f32, 5.0 * i as f32))]);
|
||||
}
|
||||
frame(&mut app, vec![button(p + egui::vec2(80.0, 40.0), false)]);
|
||||
let v = *app.view().unwrap();
|
||||
assert!((v.zoom - v0.zoom).abs() < 1e-9, "panning keeps the zoom");
|
||||
assert!(v.origin.x < v0.origin.x && v.origin.y < v0.origin.y, "dragging right/down moves the view: {v0:?} -> {v:?}");
|
||||
assert_eq!(items(&app), items0, "the tool saw nothing");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tool_shortcuts_work_after_drawing_on_the_canvas() {
|
||||
// Clicking the canvas gives it keyboard focus; single-key tool shortcuts must still switch
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ Start the app with `--control <port>` (or `DESIGNCRAFT_CONTROL_PORT`). The serve
|
|||
| `ui.tool.select` | `{tool}` | Select a tool (`selection`, `type`, `rectangleFrame`, …) |
|
||||
| `ui.pointer` | `{events:[{kind: down\|drag\|up\|move\|doubleclick, x, y, space?: "screen"\|"canvas"}], mods?}` | Drive the active tool through the same code path as the mouse |
|
||||
| `ui.key` / `ui.text` | `{key, shift?, alt?, cmd?}` / `{text}` | Synthetic keyboard input (typing into a text frame) |
|
||||
| `ui.move` / `ui.click` / `ui.drag` | screen points | Real egui pointer input — reaches every widget, menu and panel |
|
||||
| `ui.move` / `ui.click` / `ui.drag` | screen points, `button?: left\|right\|middle` | Real egui pointer input — reaches every widget, menu and panel |
|
||||
| `ui.set` | `{brightness?, panel?, rulers?, guides?, frameEdges?, baselineGrid?, textThreads?, screenMode?, zoom?, page?, fit?}` | UI state |
|
||||
| `ui.dialog.open` | `{id, fields?}` | Open a dialog by id (e.g. `paragraphStyleOptions` with `{name, section}`) |
|
||||
| `ui.dialog.set` / `ui.dialog.confirm` / `ui.dialog.cancel` | `{field, value}` | Fill and confirm the open dialog |
|
||||
|
|
|
|||
Loading…
Reference in New Issue