From fdbac3ca037cf477ea3a3c9724aa2da94d3f7091 Mon Sep 17 00:00:00 2001 From: Korg1ch <57859048+Korg1ch@users.noreply.github.com> Date: Wed, 21 Oct 2026 00:58:07 +0300 Subject: [PATCH] fix(ui-egui): Page Up/Down, Home and End scroll the canvas, as in Photoshop (#8057) --- crates/ui-egui/src/lib.rs | 1 + crates/ui-egui/src/page_keys.rs | 107 ++++++++++++++++++++++++++++++++ crates/ui-egui/src/shortcuts.rs | 4 ++ 3 files changed, 112 insertions(+) create mode 100644 crates/ui-egui/src/page_keys.rs diff --git a/crates/ui-egui/src/lib.rs b/crates/ui-egui/src/lib.rs index 3e3b1d2e5b..660568edcc 100644 --- a/crates/ui-egui/src/lib.rs +++ b/crates/ui-egui/src/lib.rs @@ -152,6 +152,7 @@ pub mod object_finder; mod opacity_keys; pub mod open_prompt; pub mod outline; +mod page_keys; pub mod paint_mouse; pub mod palette; mod panel_docking; diff --git a/crates/ui-egui/src/page_keys.rs b/crates/ui-egui/src/page_keys.rs new file mode 100644 index 0000000000..caef5dc7eb --- /dev/null +++ b/crates/ui-egui/src/page_keys.rs @@ -0,0 +1,107 @@ +//! Photoshop's page keys scroll the canvas (#8057): Page Up and Page Down a screen up or down, +//! ⌘/Ctrl turns them sideways (⌘ Page Up left, ⌘ Page Down right), and ⇧ makes the step ten +//! screen points. Home and End show the document's top-left and bottom-right corners. The zoom +//! stays as it is. A binding of the user's on the same keys has already run and taken them. + +use egui::{Event, Key, Modifiers, Vec2, vec2}; + +use crate::PhotocraftApp; + +/// Screen points a ⇧ page key scrolls. +pub const SMALL_STEP: f32 = 10.0; + +/// What a page key does to the view. +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum PageKey { + /// Scroll by this many screen points (positive: show what is below or to the right). + Scroll(Vec2), + /// Show the document's top-left corner (Home) or its bottom-right one (End). + Corner { end: bool }, +} + +/// The page key `key` with `mods` held, in a canvas of `size` points. `None` for other keys, ⌥, +/// or ⇧ on Home and End. +pub fn page_key(key: Key, mods: Modifiers, size: Vec2) -> Option { + if mods.alt { + return None; + } + let sign = match key { + Key::PageUp => -1.0, + Key::PageDown => 1.0, + Key::Home | Key::End if !mods.shift && !mods.command => return Some(PageKey::Corner { end: key == Key::End }), + _ => return None, + }; + let sideways = mods.command; + let step = match (mods.shift, sideways) { + (true, _) => SMALL_STEP, + (false, true) => size.x, + (false, false) => size.y, + }; + Some(PageKey::Scroll(if sideways { vec2(sign * step, 0.0) } else { vec2(0.0, sign * step) })) +} + +/// Consumes this frame's page keys and moves the active document's view. True when one ran. +pub fn handle(app: &mut PhotocraftApp, ctx: &egui::Context) -> bool { + let Some(i) = app.session.active_index() else { return false }; + let rect = crate::rulers::content_rect(app, app.last_canvas_rect); + let size = rect.size(); + let presses: Vec = ctx.input_mut(|input| { + let mut out = Vec::new(); + input.events.retain(|e| match e { + Event::Key { key, pressed: true, modifiers, .. } => match page_key(*key, *modifiers, size) { + Some(p) => { + out.push(p); + false + } + None => true, + }, + _ => true, + }); + out + }); + if presses.is_empty() { + return false; + } + app.sync_views(); + let Some(doc_size) = app.session.active().map(|st| [st.doc.size.width as f32, st.doc.size.height as f32]) else { return false }; + let (ppp, aspect, flip, overscroll) = (app.ppp, app.ui.view.display_aspect(), app.ui.view.flip_horizontal, app.session.prefs().tools.overscroll); + let ppp = if ppp.is_finite() && ppp > 0.0 { ppp } else { 1.0 }; + let Some(view) = app.ui.views.get_mut(i) else { return false }; + let zoom = view.zoom / ppp; + if !zoom.is_finite() || zoom <= 0.0 { + return false; + } + view.fit_pending = false; + view.fill_pending = false; + for p in presses { + match p { + PageKey::Scroll(d) => { + let xf = crate::canvas::ViewXform { rect, zoom, center: view.center, flip, rotation: view.rotation, aspect }; + let d = xf.unmap_vec(d) / zoom; + if d.is_finite() { + view.center[0] += d.x; + view.center[1] += d.y; + } + } + PageKey::Corner { end } => { + // The document corner goes to the canvas corner the document runs on from: the + // top-left one in an upright view, another when it is rotated or flipped. + let xf = crate::canvas::ViewXform { rect, zoom, center: view.center, flip, rotation: view.rotation, aspect }; + let [w, h] = doc_size; + let (corner, inward) = if end { ([w, h], vec2(-1.0, -1.0)) } else { ([0.0, 0.0], vec2(1.0, 1.0)) }; + let dir = xf.map_vec(inward); + let at = vec2(-dir.x.signum() * size.x / 2.0, -dir.y.signum() * size.y / 2.0); + let off = xf.unmap_vec(at) / zoom; + if off.is_finite() { + view.center = [corner[0] - off.x, corner[1] - off.y]; + } + } + } + } + // Overscroll off: the view stops at the edge, as the scrollbars do. + if !overscroll { + crate::scrollbars::clamp_view_with_aspect(view, size, ppp, aspect); + } + ctx.request_repaint(); + true +} diff --git a/crates/ui-egui/src/shortcuts.rs b/crates/ui-egui/src/shortcuts.rs index 1ad8d6d92a..c842ee6011 100644 --- a/crates/ui-egui/src/shortcuts.rs +++ b/crates/ui-egui/src/shortcuts.rs @@ -704,6 +704,10 @@ pub fn handle(app: &mut PhotocraftApp, ctx: &egui::Context) { return; } } + // Page Up / Page Down (⌘ sideways, ⇧ a small step), Home and End scroll the canvas (#8057). + if crate::page_keys::handle(app, ctx) { + return; + } // 1–0 set opacity, ⇧1–0 flow or fill (`opacity_keys`). crate::opacity_keys::handle(app, ctx); // ⇧+ / ⇧− step the blend mode (`blend_keys`).