mirror of
https://github.com/storytold/designcraft.git
synced 2026-10-14 19:27:07 -04:00
V: Power Zoom with the Hand tool
Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
eec8bd3d9e
commit
0454044d74
6 files changed
+74
-3
No files matched your search
@@ -86,6 +86,7 @@ DesignCraft aims at full Adobe InDesign parity — and to be better: faster, ope
|
||||
- Move table rows and columns (`table.moveRow`, `table.moveColumn`, Table panel buttons; dragging on the canvas isn't there yet).
|
||||
- Object States (`states.create/show/rename/release/list`, Object States panel): multi-state objects show one state on screen and in PDF.
|
||||
- Buttons (`button.set/clear/list`, Buttons and Forms panel): go to page / next / previous / first / last / URL as PDF link annotations. Form fields need AcroForm support the PDF writer lacks.
|
||||
- Power Zoom (Hand tool, Alt-press): zoom out to the spread, aim the red view rectangle, release to zoom back in there. InDesign triggers it by press-and-hold instead.
|
||||
- Drag and drop text editing: drag selected text to move it (Alt copies) with its formatting (`text.release`).
|
||||
- Image Import Options: place any page of a multi-page PDF (`file.place {pdfPage}`; the app asks which page), shown on screen and embedded as that page in PDF export.
|
||||
- Define Lists: named numbered lists that continue across stories in page order (`list.define`, paragraph `listName` / `startAt`), round-tripped through IDML; `null` in `type.para` / `type.char` now removes an override.
|
||||
|
||||
@@ -5,9 +5,12 @@ use serde_json::json;
|
||||
|
||||
use crate::{Action, Cursor, Mods, PointerEvent, PointerKind, Tool, ToolContext};
|
||||
|
||||
/// Hand tool: drag to pan. Alt-press starts Power Zoom: the view zooms out to the spread, the
|
||||
/// pointer aims a rectangle of the previous view, and releasing zooms back in there.
|
||||
#[derive(Default)]
|
||||
pub struct HandTool {
|
||||
last: Option<Point>,
|
||||
power: bool,
|
||||
}
|
||||
|
||||
impl Tool for HandTool {
|
||||
@@ -16,10 +19,21 @@ impl Tool for HandTool {
|
||||
}
|
||||
fn pointer(&mut self, cx: &ToolContext, ev: &PointerEvent) -> Vec<Action> {
|
||||
match ev.kind {
|
||||
PointerKind::Down if ev.mods.alt => {
|
||||
self.power = true;
|
||||
self.last = Some(ev.pos);
|
||||
vec![Action::View(json!({"powerZoom": "start", "at": [ev.pos.x, ev.pos.y]}))]
|
||||
}
|
||||
PointerKind::Down => {
|
||||
self.last = Some(ev.pos);
|
||||
vec![]
|
||||
}
|
||||
PointerKind::Drag if self.power => vec![Action::View(json!({"powerZoom": "move", "at": [ev.pos.x, ev.pos.y]}))],
|
||||
PointerKind::Up if self.power => {
|
||||
self.power = false;
|
||||
self.last = None;
|
||||
vec![Action::View(json!({"powerZoom": "end", "at": [ev.pos.x, ev.pos.y]}))]
|
||||
}
|
||||
PointerKind::Drag => {
|
||||
// Canvas positions shift as we pan, so convert the delta to screen pixels once.
|
||||
let Some(l) = self.last else { return vec![] };
|
||||
|
||||
@@ -204,6 +204,25 @@ fn zoom_tool_click_and_scrub() {
|
||||
assert!(t.pointer(&cx, &PointerEvent::new(PointerKind::Up, 250.0, 100.0)).is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hand_tool_alt_press_power_zooms() {
|
||||
let d = Document::new(&NewDocument::default());
|
||||
let (s, c, l) = (Selection::default(), Cache::new(), CanvasLayout::new(&d, false));
|
||||
let cx = ctx(&d, &s, &c, &l);
|
||||
let mut t = create("hand");
|
||||
let alt = Mods { alt: true, ..Default::default() };
|
||||
let phase = |a: Vec<Action>| match &a[..] {
|
||||
[Action::View(v)] => v["powerZoom"].as_str().unwrap_or("").to_string(),
|
||||
other => panic!("{other:?}"),
|
||||
};
|
||||
assert_eq!(phase(t.pointer(&cx, &PointerEvent::new(PointerKind::Down, 10.0, 10.0).with_mods(alt))), "start");
|
||||
assert_eq!(phase(t.pointer(&cx, &PointerEvent::new(PointerKind::Drag, 50.0, 60.0))), "move");
|
||||
assert_eq!(phase(t.pointer(&cx, &PointerEvent::new(PointerKind::Up, 50.0, 60.0))), "end");
|
||||
// A plain press pans as before.
|
||||
t.pointer(&cx, &PointerEvent::new(PointerKind::Down, 10.0, 10.0));
|
||||
assert!(t.pointer(&cx, &PointerEvent::new(PointerKind::Up, 10.0, 10.0)).is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn page_tool_opens_page_size() {
|
||||
let d = Document::new(&NewDocument::default());
|
||||
|
||||
@@ -145,6 +145,34 @@ pub fn set_zoom(app: &mut DesignApp, z: f64) {
|
||||
}
|
||||
|
||||
pub fn apply_view_request(app: &mut DesignApp, p: &Value) {
|
||||
// Power Zoom (Hand tool, Alt-press): zoom out to the spread, aim a view-sized rectangle, zoom
|
||||
// back in where it was released.
|
||||
if let Some(phase) = p.get("powerZoom").and_then(Value::as_str) {
|
||||
let at = p.get("at").and_then(Value::as_array).map(|a| Point::new(a[0].as_f64().unwrap_or(0.0), a[1].as_f64().unwrap_or(0.0)));
|
||||
match phase {
|
||||
"start" => {
|
||||
if let (Some(v), Some(rect)) = (app.view().copied(), app.canvas_rect) {
|
||||
let centre = Xf::new(rect, &v).to_canvas(rect.center());
|
||||
app.power_zoom = Some((v.zoom, at.unwrap_or(centre)));
|
||||
fit(app, rect, "spread");
|
||||
}
|
||||
}
|
||||
"move" => {
|
||||
if let (Some(pz), Some(a)) = (&mut app.power_zoom, at) {
|
||||
pz.1 = a;
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
if let (Some((z, c)), Some(rect)) = (app.power_zoom.take(), app.canvas_rect)
|
||||
&& let Some(v) = app.view_mut()
|
||||
{
|
||||
let c = at.unwrap_or(c);
|
||||
v.zoom = z;
|
||||
v.origin = Point::new(c.x - rect.width() as f64 / 2.0 / z, c.y - rect.height() as f64 / 2.0 / z);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some(d) = p.get("pan").and_then(Value::as_array) {
|
||||
let (dx, dy) = (d.first().and_then(Value::as_f64).unwrap_or(0.0), d.get(1).and_then(Value::as_f64).unwrap_or(0.0));
|
||||
if let Some(v) = app.view_mut() {
|
||||
@@ -1076,6 +1104,12 @@ fn caret_x(l: &compose::Line, pos: usize) -> f64 {
|
||||
}
|
||||
|
||||
fn draw_tool_overlays(app: &mut DesignApp, painter: &egui::Painter, xf: &Xf) {
|
||||
if let (Some((z, c)), Some(rect)) = (app.power_zoom, app.canvas_rect) {
|
||||
// The view that releasing returns to.
|
||||
let (w, h) = (rect.width() as f64 / z, rect.height() as f64 / z);
|
||||
let r = xf.rect(designcraft_geom::Rect::new(c.x - w / 2.0, c.y - h / 2.0, c.x + w / 2.0, c.y + h / 2.0));
|
||||
painter.rect_stroke(r, 0.0, egui::Stroke::new(1.5, Color32::from_rgb(230, 40, 40)), egui::StrokeKind::Middle);
|
||||
}
|
||||
let ov = app.session.overlays(app.view_info());
|
||||
for o in ov {
|
||||
match o {
|
||||
|
||||
@@ -262,6 +262,8 @@ pub struct DesignApp {
|
||||
pub views: HashMap<u64, View>,
|
||||
pub canvas: CanvasCache,
|
||||
pub canvas_rect: Option<egui::Rect>,
|
||||
/// Power Zoom in progress: the zoom to return to and the canvas point it will centre on.
|
||||
pub power_zoom: Option<(f64, designcraft_geom::Point)>,
|
||||
pub perf: Perf,
|
||||
pub synthetic: Vec<egui::Event>,
|
||||
/// Story open in the Story Editor.
|
||||
@@ -308,6 +310,7 @@ impl DesignApp {
|
||||
patches: 0,
|
||||
},
|
||||
canvas_rect: None,
|
||||
power_zoom: None,
|
||||
perf: Perf::default(),
|
||||
synthetic: vec![],
|
||||
story_editor: None,
|
||||
|
||||
+3
-3
@@ -11,7 +11,7 @@ Each row of the InDesign feature catalogue is scored **D** (done), **P** (partia
|
||||
|---|---|---|---|---|---|
|
||||
| P0 | 102 | 99 | 3 | 0 | 99% |
|
||||
| P1 | 97 | 92 | 4 | 1 | 97% |
|
||||
| P2 | 76 | 35 | 23 | 18 | 61% |
|
||||
| P2 | 76 | 35 | 24 | 17 | 62% |
|
||||
|
||||
| Area | Parity |
|
||||
|---|---|
|
||||
@@ -31,7 +31,7 @@ Each row of the InDesign feature catalogue is scored **D** (done), **P** (partia
|
||||
| Interactivity & digital | 90% |
|
||||
| Output & production | 80% |
|
||||
| XML & automation | 83% |
|
||||
| View & navigation | 88% |
|
||||
| View & navigation | 89% |
|
||||
| Undo, history, saving | 100% |
|
||||
| Accessibility | 70% |
|
||||
|
||||
@@ -297,7 +297,7 @@ Open P0 items: Preferences, Vector/page formats (PDF/AI/SVG/EPS place), Export t
|
||||
| XML & automation | Snippets | P1 | D |
|
||||
| XML & automation | Object Library | P2 | D |
|
||||
| View & navigation | Zoom commands | P0 | D |
|
||||
| View & navigation | Power Zoom | P2 | M |
|
||||
| View & navigation | Power Zoom | P2 | P |
|
||||
| View & navigation | Animated/scrubby zoom | P1 | D |
|
||||
| View & navigation | Screen modes | P0 | D |
|
||||
| View & navigation | Overprint preview | P1 | D |
|
||||
|
||||
Reference in new issue
Block a user