fret-canvas
Policy-light canvas substrate helpers for Fret ecosystem widgets.
This crate exists to reduce duplication across "canvas-like" retained widgets (node graphs, plots,
charts, editors) without pushing interaction policy into crates/fret-ui.
Design reference: docs/adr/0128-canvas-widgets-and-interactive-surfaces.md.
Upstream references (non-normative)
This crate aims to stay policy-light, but some geometry and interaction conventions are informed by common editor UIs:
- XyFlow (React Flow): https://github.com/xyflow/xyflow
- Dear ImGui (editor interaction vocabulary): https://github.com/ocornut/imgui
Goals
- Provide reusable building blocks for canvas widgets:
- pan/zoom view transforms and coordinate mapping,
- pixel/scale policies (constant-pixel strokes, effective scale factors),
- drag phase/value helpers (begin/update/commit/cancel, thresholds),
- small, reusable caches (e.g. prepared text blobs).
- Keep this crate portable and mechanism-oriented:
- no platform/backend deps,
- no domain models,
- no gesture maps / tool rules / snapping policies.
Unit conventions
Fret uses window-local logical pixels as its “screen px” unit (similar to CSS px).
In code, this is typically represented as fret_core::Px (or plain f32 values documented as
screen-space pixels).
When a canvas widget uses Widget::render_transform for pan/zoom (ADR 0082), pointer event
positions are delivered in the widget's local (untransformed) coordinate space. In that case,
any UX tuning expressed in screen pixels (hit slop, click distance, drag threshold, handle radius)
should be converted to canvas units before comparison:
fret_canvas::scale::canvas_units_from_screen_px(screen_px, zoom)(typicallyscreen_px / zoom)
Modules
fret_canvas::viewPanZoom2D: a small pan/zoom view model that can generate aTransform2Dcompatible withWidget::render_transformand providesscreen_to_canvas/canvas_to_screenmapping.
fret_canvas::scaleeffective_scale_factor: DPI scale factor multiplied by view zoom (for resource preparation).constant_pixel_stroke_width: helper for constant-screen-pixel strokes under zoom.
fret_canvas::dragDragPhase: minimal drag lifecycle vocabulary.DragThreshold: screen-px threshold converted to canvas units under zoom.
fret_canvas::textTextCache: a keyed cache for preparedTextBlobId+TextMetricsthat releases resources viaUiServices.
fret_canvas::cachePathCache: a keyed cache for preparedPathId+PathMetricsthat releases resources viaUiServices.SvgCache: a keyed cache for registeredSvgIds that unregisters SVGs viaUiServices.
Replay caches and resource liveness
SceneOpCache / SceneOpTileCache intentionally store only recorded SceneOps and do not
manage the lifetimes of renderer-owned resources referenced by ops (TextBlobId, PathId, SvgId).
When you replay cached ops on a cache hit, make sure the corresponding retained caches are also "touched" so entries are not pruned while still referenced by replay caches:
TextCache::touch_blobs_in_scene_ops(&ops)PathCache::touch_paths_in_scene_ops(&ops)SvgCache::touch_svgs_in_scene_ops(&ops)
To reduce foot-guns, prefer the on-hit hook variants when replaying cached ops:
SceneOpCache::try_replay_with(..., |ops| { ...touch... })SceneOpTileCache::try_replay_with(..., |ops| { ...touch... })warm_scene_op_tiles_u64_with(..., |ops| { ...touch... }, ...)
If you have multiple retained caches, you can aggregate touch behavior into one call:
(&mut text_cache, &mut path_cache, &mut svg_cache).touch_hosted_resources_in_scene_ops(&ops)
Future: declarative surface
Fret now provides a declarative canvas element in crates/fret-ui (ADR 0141).
- Declarative: use
fret_ui::ElementContext::canvas(...)+fret_ui::canvas::CanvasPainterto emitSceneOps with hosted resources (TextBlobId/PathId/SvgId) and scoped helpers for clip/transform/effect stacks. - Retained: keep using
ecosystem/fret-canvasfor retained widgets (node graphs, plots, editors) that own their own interaction policy and internal caches.
This crate intentionally stays focused on reusable retained-canvas helpers that remain useful even when the high-level authoring style is declarative.
Optional: UI integration (fret-canvas/ui)
For convenience, fret-canvas can also provide canvas-specific declarative wiring and recipes
behind an opt-in feature:
- Enable:
fret-canvas/ui - Provides:
fret_canvas::ui::{canvas_surface_panel, pan_zoom_canvas_surface_panel, canvas_tool_router_panel, ...} - Includes a convenience preset:
fret_canvas::ui::editor_pan_zoom_canvas_surface_panel(wheel pan + Ctrl/Cmd+wheel zoom).
This keeps the default crate portable and lightweight while allowing users to depend on a single canvas crate when building editor-grade interactive surfaces.
Optional: rstar backend
fret-canvas ships with a uniform-grid spatial index by default.
For experiments and large graphs with highly non-uniform distributions, an opt-in R-tree backend is
available via the rstar feature:
- Enable:
fret-canvas/rstar - Call sites should prefer
fret_canvas::spatial::DefaultIndexWithBackrefs<T>so the backend can be swapped without refactors.
Optional: kurbo backend
For robust 2D geometry operations (Bezier refinement, path hit-testing, intersection helpers),
fret-canvas can optionally use kurbo (ADR 0152).
- Enable:
fret-canvas/kurbo - Current scope: Bezier wire refinement (
bezier_wire_distance2,closest_point_on_bezier_wire).