diff --git a/editor/src/consts.rs b/editor/src/consts.rs index 9d85fee1fa..c9e42a81d7 100644 --- a/editor/src/consts.rs +++ b/editor/src/consts.rs @@ -19,6 +19,11 @@ pub const LINE_ROTATE_SNAP_ANGLE: f64 = 15.; // SELECT TOOL pub const SELECTION_TOLERANCE: f64 = 1.0; +// SCROLLBARS +pub const SCROLLBAR_SPACING: f64 = 0.1; +pub const ASYMPTOTIC_EFFECT: f64 = 0.5; +pub const SCALE_EFFECT: f64 = 0.5; + pub const DEFAULT_DOCUMENT_NAME: &str = "Untitled Document"; pub const FILE_SAVE_SUFFIX: &str = ".graphite"; pub const FILE_EXPORT_SUFFIX: &str = ".svg"; diff --git a/editor/src/document/document_file.rs b/editor/src/document/document_file.rs index 2ef404d4c5..700b21979c 100644 --- a/editor/src/document/document_file.rs +++ b/editor/src/document/document_file.rs @@ -1,6 +1,6 @@ pub use super::layer_panel::*; use crate::{ - consts::{FILE_EXPORT_SUFFIX, FILE_SAVE_SUFFIX}, + consts::{ASYMPTOTIC_EFFECT, FILE_EXPORT_SUFFIX, FILE_SAVE_SUFFIX, SCALE_EFFECT, SCROLLBAR_SPACING}, frontend::layer_panel::*, EditorError, }; @@ -406,15 +406,16 @@ impl MessageHandler for DocumentMessageHand } .into(), ); - let root = self.layerdata(&[]); - let viewport = ipp.viewport_bounds.size(); - let [bounds1, bounds2] = self.document.visible_layers_bounding_box().unwrap_or_default(); - let bounds1 = bounds1.min(DVec2::ZERO) - viewport * (f64::powf(2., root.scale / 3.) * 0.5); - let bounds2 = bounds2.max(viewport) + viewport * (f64::powf(2., root.scale / 3.) * 0.5); - let bounds_length = bounds2 - bounds1; - let scrollbar_multiplier = bounds_length - viewport; - let scrollbar_position = bounds1.abs() / scrollbar_multiplier; - let scrollbar_size = viewport / bounds_length; + let scale = 0.5 + ASYMPTOTIC_EFFECT + self.layerdata(&[]).scale * SCALE_EFFECT; + let viewport_size = ipp.viewport_bounds.size(); + let viewport_mid = ipp.viewport_bounds.center(); + let [bounds1, bounds2] = self.document.visible_layers_bounding_box().unwrap_or([viewport_mid; 2]); + let bounds1 = bounds1.min(viewport_mid) - viewport_size * scale; + let bounds2 = bounds2.max(viewport_mid) + viewport_size * scale; + let bounds_length = (bounds2 - bounds1) * (1. + SCROLLBAR_SPACING); + let scrollbar_position = DVec2::splat(0.5) - (bounds1.lerp(bounds2, 0.5) - viewport_mid) / (bounds_length - viewport_size); + let scrollbar_multiplier = bounds_length - viewport_size; + let scrollbar_size = viewport_size / bounds_length; responses.push_back( FrontendMessage::UpdateScrollbars { position: scrollbar_position.into(), diff --git a/editor/src/document/movement_handler.rs b/editor/src/document/movement_handler.rs index 00fa5c6839..79ac524a1f 100644 --- a/editor/src/document/movement_handler.rs +++ b/editor/src/document/movement_handler.rs @@ -30,7 +30,8 @@ pub enum MovementMessage { DecreaseCanvasZoom, WheelCanvasZoom, ZoomCanvasToFitAll, - TranslateCanvas(glam::DVec2), + TranslateCanvas(DVec2), + TranslateCanvasByViewportFraction(DVec2), } #[derive(Debug, Clone, Default, PartialEq)] @@ -193,6 +194,12 @@ impl MessageHandler { let transformed_delta = document.root.transform.inverse().transform_vector2(delta); + layerdata.translation += transformed_delta; + self.create_document_transform_from_layerdata(layerdata, &ipp.viewport_bounds, responses); + } + TranslateCanvasByViewportFraction(delta) => { + let transformed_delta = document.root.transform.inverse().transform_vector2(delta * ipp.viewport_bounds.size()); + layerdata.translation += transformed_delta; self.create_document_transform_from_layerdata(layerdata, &ipp.viewport_bounds, responses); } @@ -212,6 +219,8 @@ impl MessageHandler DVec2 { self.bottom_right - self.top_left } + + pub fn center(&self) -> DVec2 { + self.bottom_right.lerp(self.top_left, 0.5) + } } #[derive(Debug, Copy, Clone, Default, Eq, PartialEq, Hash)] diff --git a/frontend/src/components/panels/Document.vue b/frontend/src/components/panels/Document.vue index 2fcb4af666..5515d5af8c 100644 --- a/frontend/src/components/panels/Document.vue +++ b/frontend/src/components/panels/Document.vue @@ -128,6 +128,7 @@ :handlePosition="scrollbarPos.y" @update:handlePosition="translateCanvasY" v-model:handleLength="scrollbarSize.y" + @pressTrack="pageY" :class="'right-scrollbar'" /> @@ -138,6 +139,7 @@ :handlePosition="scrollbarPos.x" @update:handlePosition="translateCanvasX" v-model:handleLength="scrollbarSize.x" + @pressTrack="pageX" :class="'bottom-scrollbar'" /> @@ -293,6 +295,14 @@ export default defineComponent({ this.scrollbarPos.y = newValue; (await wasm).translate_canvas(0, -delta * this.scrollbarMultiplier.y); }, + async pageX(delta: number) { + const move = delta < 0 ? 1 : -1; + (await wasm).translate_canvas_by_fraction(move, 0); + }, + async pageY(delta: number) { + const move = delta < 0 ? 1 : -1; + (await wasm).translate_canvas_by_fraction(0, move); + }, async selectTool(toolName: string) { (await wasm).select_tool(toolName); }, diff --git a/frontend/src/components/widgets/scrollbars/PersistentScrollbar.vue b/frontend/src/components/widgets/scrollbars/PersistentScrollbar.vue index e0d1b1293b..b26cca79c9 100644 --- a/frontend/src/components/widgets/scrollbars/PersistentScrollbar.vue +++ b/frontend/src/components/widgets/scrollbars/PersistentScrollbar.vue @@ -186,9 +186,9 @@ export default defineComponent({ }, grabArea(e: MouseEvent) { if (!this.dragging) { - this.dragging = true; - this.mousePos = mousePosition(this.direction, e); - this.clampHandlePosition(((this.mousePos - this.trackOffset()) / this.trackLength() - this.handleLength / 2) / (1 - this.handleLength)); + const mousePos = mousePosition(this.direction, e); + const oldMouse = handleToTrack(this.handleLength, this.handlePosition) * this.trackLength() + this.trackOffset(); + this.$emit("pressTrack", mousePos - oldMouse); } }, mouseUp() { diff --git a/frontend/wasm/src/document.rs b/frontend/wasm/src/document.rs index f85783fee1..eba56529e6 100644 --- a/frontend/wasm/src/document.rs +++ b/frontend/wasm/src/document.rs @@ -312,6 +312,13 @@ pub fn translate_canvas(delta_x: f64, delta_y: f64) -> Result<(), JsValue> { EDITOR_STATE.with(|editor| editor.borrow_mut().handle_message(ev)).map_err(convert_error) } +/// Translates document (in viewport coords) +#[wasm_bindgen] +pub fn translate_canvas_by_fraction(delta_x: f64, delta_y: f64) -> Result<(), JsValue> { + let ev = MovementMessage::TranslateCanvasByViewportFraction((delta_x, delta_y).into()); + EDITOR_STATE.with(|editor| editor.borrow_mut().handle_message(ev)).map_err(convert_error) +} + /// Update the list of selected layers. The layer paths have to be stored in one array and are separated by LayerId::MAX #[wasm_bindgen] pub fn select_layers(paths: Vec) -> Result<(), JsValue> { diff --git a/frontend/wasm/src/wrappers.rs b/frontend/wasm/src/wrappers.rs index 2f8165898b..03a0c3b0c6 100644 --- a/frontend/wasm/src/wrappers.rs +++ b/frontend/wasm/src/wrappers.rs @@ -151,6 +151,8 @@ pub fn translate_key(name: &str) -> Key { "]" => KeyRightBracket, "{" => KeyLeftCurlyBracket, "}" => KeyRightCurlyBracket, + "pageup" => KeyPageUp, + "pagedown" => KeyPageDown, _ => UnknownKey, } }