Viewport & Navigation
Relevant source files
The following files were used as context for generating this wiki page:
The Viewport & Navigation system in Dotorixel manages how the user views and interacts with the pixel canvas. It encompasses coordinate transformations between screen space and canvas space, zoom and pan logic, and the handling of various input methods such as mouse wheels, trackpads, and touch gestures.
This system is built upon a cross-platform core implemented in Rust, exposed via WebAssembly (WASM) for the web interface and UniFFI for Apple native platforms. It ensures that zoom levels, pan positions, and coordinate math remain consistent across different device shells.
Architecture and Components
The navigation architecture is divided into the Rust core logic for mathematical transformations and the Svelte-based UI layer for event handling and state management.
Core Logic (WasmViewport)
The WasmViewport (and its underlying Rust implementation) is the single source of truth for the viewing state. It stores the pixel size, current zoom level, and pan offsets (pan_x, pan_y). It provides methods to calculate effective pixel sizes and transform coordinates.
| Feature | Description |
|---|---|
| Coordinate Transform | Converts screen-relative coordinates to 0-indexed canvas pixel coordinates. |
| Zoom Stepping | Manages discrete zoom levels (e.g., next/previous levels). |
| Pan Clamping | Restricts panning to prevent the canvas from disappearing entirely from the viewport. |
| Fit-to-Viewport | Calculates the optimal zoom and pan to center the canvas within the available screen area. |
Sources: [crates/core/src/viewport.rs], [src/lib/wasm/wasm-viewport.test.ts:4-70], [tasks/records/016-zoom-pan.md]
Input Classification
Dotorixel uses a specialized utility to distinguish between different types of wheel events, which is critical for supporting both traditional mice and modern trackpads.
This diagram shows the decision logic used to classify raw browser wheel events into specific navigation actions.
Sources: [src/lib/canvas/wheel-input.ts:18-42], [src/lib/canvas/PixelCanvasView.svelte:81-111]
Coordinate Transformations
Transforming screen coordinates to canvas coordinates is essential for drawing and interaction. The math accounts for the canvas being centered in the viewport and the current zoom/pan state.
Transformation Flow
When a user interacts with the screen (e.g., PointerEvent), the following flow occurs:
Sources: [src/lib/canvas/PixelCanvasView.svelte:131-145], [src/lib/canvas/canvas-interaction.svelte.ts:114-125]
Navigation Interactions
The project implements a state machine to handle complex interactions like "Space+Drag" panning and two-finger trackpad navigation.
Zoom and Pan Methods
- Mouse Wheel: Discrete zoom steps (in/out) at the current cursor position.
- Trackpad (Two-Finger): Continuous panning across the X and Y axes.
- Pinch-to-Zoom: Continuous zooming centered at the pinch point.
- Space-Bar Pan: Holding the space bar transitions the tool to a "grab" state, allowing the user to click and drag the canvas.
Interaction States
The canvas-interaction.svelte.ts module manages the transition between drawing and navigating.
| State | Trigger | Resulting Cursor |
|---|---|---|
| Idle | Default | crosshair (or tool default) |
| Panning | Middle Mouse or Space + Drag | grabbing |
| Grab-Ready | Space Key Held | grab |
Sources: [src/lib/canvas/canvas-interaction.svelte.ts:45-90], [src/lib/canvas/PixelCanvasView.svelte:121-128], [tasks/records/016-zoom-pan.md]
Technical Implementation Details
Rust Viewport Math
The core math in Rust handles the calculations for "Zoom at Point," which ensures that the pixel under the cursor remains in the same screen position after the zoom level changes.
// crates/core/src/viewport.rs (logical representation)
pub fn zoom_at_point(&self, screen_x: f64, screen_y: f64, new_zoom: f64) -> Self {
let canvas_point = self.screen_to_canvas(screen_x, screen_y);
// ... calculate new pan offsets based on canvas_point and new_zoom
}
Sources: [crates/core/src/viewport.rs], [src/lib/wasm/wasm-viewport.test.ts:51-54]
Viewport State in Svelte
The Svelte implementation uses $state and $derived runes to maintain responsiveness. The PixelCanvasView component reacts to renderViewport changes to re-render the Canvas2D context.
// src/lib/canvas/PixelCanvasView.svelte
$effect(() => {
if (!canvasEl) return;
const ctx = canvasEl.getContext('2d');
// ...
renderPixelCanvas(ctx, pixelCanvas, renderViewport, viewportSize);
});
Sources: [src/lib/canvas/PixelCanvasView.svelte:67-78]
Conclusion
The Viewport & Navigation system provides a robust foundation for the Dotorixel editor by offloading complex geometry and transformation logic to a high-performance Rust core. By abstracting input classification and maintaining independent viewport states, the application supports a wide range of devices and professional workflows, including multi-touch gestures and traditional peripheral inputs. This separation of concerns allows the UI to remain declarative while the underlying math remains precise and testable.