Split out viewport handling into its own message handler (#3331)

* extract viewport handeling

* fix web overlays

* some cleanup

* remove some physical conversions

* fix resize snapping

* fixup

* apply some review feedback

* make viewport api more ergonomic

* fix

* fix web overlay canvas clear size

* clear workaround for canvas

* rename trigger message

---------

Co-authored-by: Dennis Kobert <dennis@kobert.dev>
This commit is contained in:
Timon
2025-11-08 08:59:38 +00:00
committed by GitHub
co-authored by Dennis Kobert
parent 3490111b96
commit 881ec0b193
60 changed files with 1326 additions and 607 deletions
@@ -13,9 +13,11 @@ fn grid_overlay_rectangular(document: &DocumentMessageHandler, overlay_context:
let Some(spacing) = GridSnapping::compute_rectangle_spacing(spacing, &document.document_ptz) else {
return;
};
let document_to_viewport = document.navigation_handler.calculate_offset_transform(overlay_context.size / 2., &document.document_ptz);
let document_to_viewport = document
.navigation_handler
.calculate_offset_transform(overlay_context.viewport.center_in_viewport_space().into(), &document.document_ptz);
let bounds = document_to_viewport.inverse() * Quad::from_box([DVec2::ZERO, overlay_context.size]);
let bounds = document_to_viewport.inverse() * Quad::from_box([DVec2::ZERO, overlay_context.viewport.size().into()]);
for primary in 0..2 {
let secondary = 1 - primary;
@@ -52,9 +54,11 @@ fn grid_overlay_rectangular_dot(document: &DocumentMessageHandler, overlay_conte
let Some(spacing) = GridSnapping::compute_rectangle_spacing(spacing, &document.document_ptz) else {
return;
};
let document_to_viewport = document.navigation_handler.calculate_offset_transform(overlay_context.size / 2., &document.document_ptz);
let document_to_viewport = document
.navigation_handler
.calculate_offset_transform(overlay_context.viewport.center_in_viewport_space().into(), &document.document_ptz);
let bounds = document_to_viewport.inverse() * Quad::from_box([DVec2::ZERO, overlay_context.size]);
let bounds = document_to_viewport.inverse() * Quad::from_box([DVec2::ZERO, overlay_context.viewport.size().into()]);
let min = bounds.0.iter().map(|corner| corner.y).min_by(|a, b| a.partial_cmp(b).unwrap()).unwrap_or_default();
let max = bounds.0.iter().map(|corner| corner.y).max_by(|a, b| a.partial_cmp(b).unwrap()).unwrap_or_default();
@@ -85,9 +89,11 @@ fn grid_overlay_isometric(document: &DocumentMessageHandler, overlay_context: &m
let grid_color = "#".to_string() + &document.snapping_state.grid.grid_color.to_rgba_hex_srgb();
let cmp = |a: &f64, b: &f64| a.partial_cmp(b).unwrap();
let origin = document.snapping_state.grid.origin;
let document_to_viewport = document.navigation_handler.calculate_offset_transform(overlay_context.size / 2., &document.document_ptz);
let document_to_viewport = document
.navigation_handler
.calculate_offset_transform(overlay_context.viewport.center_in_viewport_space().into(), &document.document_ptz);
let bounds = document_to_viewport.inverse() * Quad::from_box([DVec2::ZERO, overlay_context.size]);
let bounds = document_to_viewport.inverse() * Quad::from_box([DVec2::ZERO, overlay_context.viewport.size().into()]);
let tan_a = angle_a.to_radians().tan();
let tan_b = angle_b.to_radians().tan();
let spacing = DVec2::new(y_axis_spacing / (tan_a + tan_b), y_axis_spacing);
@@ -128,9 +134,11 @@ fn grid_overlay_isometric_dot(document: &DocumentMessageHandler, overlay_context
let grid_color = "#".to_string() + &document.snapping_state.grid.grid_color.to_rgba_hex_srgb();
let cmp = |a: &f64, b: &f64| a.partial_cmp(b).unwrap();
let origin = document.snapping_state.grid.origin;
let document_to_viewport = document.navigation_handler.calculate_offset_transform(overlay_context.size / 2., &document.document_ptz);
let document_to_viewport = document
.navigation_handler
.calculate_offset_transform(overlay_context.viewport.center_in_viewport_space().into(), &document.document_ptz);
let bounds = document_to_viewport.inverse() * Quad::from_box([DVec2::ZERO, overlay_context.size]);
let bounds = document_to_viewport.inverse() * Quad::from_box([DVec2::ZERO, overlay_context.viewport.size().into()]);
let tan_a = angle_a.to_radians().tan();
let tan_b = angle_b.to_radians().tan();
let spacing = DVec2::new(y_axis_spacing / (tan_a + tan_b), y_axis_spacing);
@@ -4,8 +4,7 @@ use crate::messages::prelude::*;
#[derive(ExtractField)]
pub struct OverlaysMessageContext<'a> {
pub visibility_settings: OverlaysVisibilitySettings,
pub ipp: &'a InputPreprocessorMessageHandler,
pub device_pixel_ratio: f64,
pub viewport: &'a ViewportMessageHandler,
}
#[derive(Debug, Clone, Default, ExtractField)]
@@ -20,19 +19,14 @@ pub struct OverlaysMessageHandler {
#[message_handler_data]
impl MessageHandler<OverlaysMessage, OverlaysMessageContext<'_>> for OverlaysMessageHandler {
fn process_message(&mut self, message: OverlaysMessage, responses: &mut VecDeque<Message>, context: OverlaysMessageContext) {
let OverlaysMessageContext {
visibility_settings,
ipp,
device_pixel_ratio,
..
} = context;
let OverlaysMessageContext { visibility_settings, viewport, .. } = context;
match message {
#[cfg(target_family = "wasm")]
OverlaysMessage::Draw => {
use super::utility_functions::overlay_canvas_element;
use super::utility_types::OverlayContext;
use glam::{DAffine2, DVec2};
use crate::messages::viewport::{Position, ToPhysical};
use wasm_bindgen::JsCast;
let canvas = match &self.canvas {
@@ -48,28 +42,26 @@ impl MessageHandler<OverlaysMessage, OverlaysMessageContext<'_>> for OverlaysMes
canvas_context.dyn_into().expect("Context should be a canvas 2d context")
});
let size = ipp.viewport_bounds.size().as_uvec2();
let size_logical = viewport.size();
let size_physical = size_logical.to_physical();
let width = size_logical.x().max(size_physical.x());
let height = size_logical.y().max(size_physical.y());
let [a, b, c, d, e, f] = DAffine2::from_scale(DVec2::splat(device_pixel_ratio)).to_cols_array();
let _ = canvas_context.set_transform(a, b, c, d, e, f);
canvas_context.clear_rect(0., 0., ipp.viewport_bounds.size().x, ipp.viewport_bounds.size().y);
let _ = canvas_context.reset_transform();
canvas_context.clear_rect(0., 0., width, height);
if visibility_settings.all() {
responses.add(DocumentMessage::GridOverlays {
context: OverlayContext {
render_context: canvas_context.clone(),
size: size.as_dvec2(),
device_pixel_ratio,
visibility_settings: visibility_settings.clone(),
viewport: *viewport,
},
});
for provider in &self.overlay_providers {
responses.add(provider(OverlayContext {
render_context: canvas_context.clone(),
size: size.as_dvec2(),
device_pixel_ratio,
visibility_settings: visibility_settings.clone(),
viewport: *viewport,
}));
}
}
@@ -78,9 +70,7 @@ impl MessageHandler<OverlaysMessage, OverlaysMessageContext<'_>> for OverlaysMes
OverlaysMessage::Draw => {
use super::utility_types::OverlayContext;
let size = ipp.viewport_bounds.size();
let overlay_context = OverlayContext::new(size, device_pixel_ratio, visibility_settings);
let overlay_context = OverlayContext::new(*viewport, visibility_settings);
if visibility_settings.all() {
responses.add(DocumentMessage::GridOverlays { context: overlay_context.clone() });
@@ -93,7 +83,7 @@ impl MessageHandler<OverlaysMessage, OverlaysMessageContext<'_>> for OverlaysMes
}
#[cfg(all(not(target_family = "wasm"), test))]
OverlaysMessage::Draw => {
let _ = (responses, visibility_settings, ipp, device_pixel_ratio);
let _ = (responses, visibility_settings, viewport);
}
OverlaysMessage::AddProvider { provider: message } => {
self.overlay_providers.insert(message);
@@ -207,7 +207,6 @@ pub fn path_endpoint_overlays(document: &DocumentMessageHandler, shape_editor: &
let Some(vector) = document.network_interface.compute_modified_vector(layer) else {
continue;
};
//let document_to_viewport = document.navigation_handler.calculate_offset_transform(overlay_context.size / 2., &document.document_ptz);
let transform = document.metadata().transform_to_viewport_if_feeds(layer, &document.network_interface);
let selected = shape_editor.selected_shape_state.get(&layer);
let is_selected = |selected: Option<&SelectedLayerState>, point: ManipulatorPointId| selected.is_some_and(|selected| selected.is_point_selected(point));
@@ -2,10 +2,11 @@ use super::utility_functions::overlay_canvas_context;
use crate::consts::{
ARC_SWEEP_GIZMO_RADIUS, COLOR_OVERLAY_BLUE, COLOR_OVERLAY_BLUE_50, COLOR_OVERLAY_GREEN, COLOR_OVERLAY_RED, COLOR_OVERLAY_WHITE, COLOR_OVERLAY_YELLOW, COLOR_OVERLAY_YELLOW_DULL,
COMPASS_ROSE_ARROW_SIZE, COMPASS_ROSE_HOVER_RING_DIAMETER, COMPASS_ROSE_MAIN_RING_DIAMETER, COMPASS_ROSE_RING_INNER_DIAMETER, DOWEL_PIN_RADIUS, MANIPULATOR_GROUP_MARKER_SIZE,
PIVOT_CROSSHAIR_LENGTH, PIVOT_CROSSHAIR_THICKNESS, PIVOT_DIAMETER, SEGMENT_SELECTED_THICKNESS,
PIVOT_CROSSHAIR_LENGTH, PIVOT_CROSSHAIR_THICKNESS, PIVOT_DIAMETER, RESIZE_HANDLE_SIZE, SEGMENT_SELECTED_THICKNESS, SKEW_TRIANGLE_OFFSET, SKEW_TRIANGLE_SIZE,
};
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
use crate::messages::prelude::Message;
use crate::messages::viewport::ViewportMessageHandler;
use core::borrow::Borrow;
use core::f64::consts::{FRAC_PI_2, PI, TAU};
use glam::{DAffine2, DVec2};
@@ -141,10 +142,7 @@ pub struct OverlayContext {
#[serde(skip, default = "overlay_canvas_context")]
#[specta(skip)]
pub render_context: web_sys::CanvasRenderingContext2d,
pub size: DVec2,
// The device pixel ratio is a property provided by the browser window and is the CSS pixel size divided by the physical monitor's pixel size.
// It allows better pixel density of visualizations on high-DPI displays where the OS display scaling is not 100%, or where the browser is zoomed.
pub device_pixel_ratio: f64,
pub viewport: ViewportMessageHandler,
pub visibility_settings: OverlaysVisibilitySettings,
}
// Message hashing isn't used but is required by the message system macros
@@ -499,11 +497,25 @@ impl OverlayContext {
self.square(position, None, Some(color_fill), Some(COLOR_OVERLAY_BLUE));
}
pub fn resize_handle(&mut self, position: DVec2, rotation: f64) {
let quad = DAffine2::from_angle_translation(rotation, position) * Quad::from_box([DVec2::splat(-RESIZE_HANDLE_SIZE / 2.), DVec2::splat(RESIZE_HANDLE_SIZE / 2.)]);
self.quad(quad, None, Some(COLOR_OVERLAY_WHITE));
}
pub fn skew_handles(&mut self, edge_start: DVec2, edge_end: DVec2) {
let edge_dir = (edge_end - edge_start).normalize();
let mid = edge_end.midpoint(edge_start);
for edge in [edge_dir, -edge_dir] {
self.draw_triangle(mid + edge * (3. + SKEW_TRIANGLE_OFFSET), edge, SKEW_TRIANGLE_SIZE, None, None);
}
}
/// Transforms the canvas context to adjust for DPI scaling
///
/// Overwrites all existing tranforms. This operation can be reversed with [`Self::reset_transform`].
fn start_dpi_aware_transform(&self) {
let [a, b, c, d, e, f] = DAffine2::from_scale(DVec2::splat(self.device_pixel_ratio)).to_cols_array();
let [a, b, c, d, e, f] = DAffine2::from_scale(DVec2::splat(self.viewport.scale())).to_cols_array();
self.render_context
.set_transform(a, b, c, d, e, f)
.expect("transform should be able to be set to be able to account for DPI");
@@ -967,7 +979,7 @@ impl OverlayContext {
Pivot::End => -padding,
};
let [a, b, c, d, e, f] = (DAffine2::from_scale(DVec2::splat(self.device_pixel_ratio)) * transform * DAffine2::from_translation(DVec2::new(x, y))).to_cols_array();
let [a, b, c, d, e, f] = (DAffine2::from_scale(DVec2::splat(self.viewport.scale())) * transform * DAffine2::from_translation(DVec2::new(x, y))).to_cols_array();
self.render_context.set_transform(a, b, c, d, e, f).expect("Failed to rotate the render context to the specified angle");
if let Some(background) = background_color {
@@ -1,10 +1,11 @@
use crate::consts::{
ARC_SWEEP_GIZMO_RADIUS, COLOR_OVERLAY_BLUE, COLOR_OVERLAY_BLUE_50, COLOR_OVERLAY_GREEN, COLOR_OVERLAY_RED, COLOR_OVERLAY_WHITE, COLOR_OVERLAY_YELLOW, COLOR_OVERLAY_YELLOW_DULL,
COMPASS_ROSE_ARROW_SIZE, COMPASS_ROSE_HOVER_RING_DIAMETER, COMPASS_ROSE_MAIN_RING_DIAMETER, COMPASS_ROSE_RING_INNER_DIAMETER, DOWEL_PIN_RADIUS, MANIPULATOR_GROUP_MARKER_SIZE,
PIVOT_CROSSHAIR_LENGTH, PIVOT_CROSSHAIR_THICKNESS, PIVOT_DIAMETER,
PIVOT_CROSSHAIR_LENGTH, PIVOT_CROSSHAIR_THICKNESS, PIVOT_DIAMETER, RESIZE_HANDLE_SIZE, SKEW_TRIANGLE_OFFSET, SKEW_TRIANGLE_SIZE,
};
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
use crate::messages::prelude::Message;
use crate::messages::prelude::ViewportMessageHandler;
use core::borrow::Borrow;
use core::f64::consts::{FRAC_PI_2, PI, TAU};
use glam::{DAffine2, DVec2};
@@ -157,24 +158,18 @@ pub struct OverlayContext {
#[serde(skip)]
#[specta(skip)]
internal: Arc<Mutex<OverlayContextInternal>>,
pub size: DVec2,
// The device pixel ratio is a property provided by the browser window and is the CSS pixel size divided by the physical monitor's pixel size.
// It allows better pixel density of visualizations on high-DPI displays where the OS display scaling is not 100%, or where the browser is zoomed.
pub device_pixel_ratio: f64,
pub viewport: ViewportMessageHandler,
pub visibility_settings: OverlaysVisibilitySettings,
}
impl Clone for OverlayContext {
fn clone(&self) -> Self {
let internal = self.internal.lock().expect("Failed to lock internal overlay context");
let size = internal.size;
let device_pixel_ratio = internal.device_pixel_ratio;
let visibility_settings = internal.visibility_settings;
drop(internal); // Explicitly release the lock before cloning the Arc<Mutex<_>>
Self {
internal: self.internal.clone(),
size,
device_pixel_ratio,
viewport: self.viewport,
visibility_settings,
}
}
@@ -183,7 +178,7 @@ impl Clone for OverlayContext {
// Manual implementations since Scene doesn't implement PartialEq or Debug
impl PartialEq for OverlayContext {
fn eq(&self, other: &Self) -> bool {
self.size == other.size && self.device_pixel_ratio == other.device_pixel_ratio && self.visibility_settings == other.visibility_settings
self.viewport == other.viewport && self.visibility_settings == other.visibility_settings
}
}
@@ -191,8 +186,7 @@ impl std::fmt::Debug for OverlayContext {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("OverlayContext")
.field("scene", &"Scene { ... }")
.field("size", &self.size)
.field("device_pixel_ratio", &self.device_pixel_ratio)
.field("viewport", &self.viewport)
.field("visibility_settings", &self.visibility_settings)
.finish()
}
@@ -203,8 +197,7 @@ impl Default for OverlayContext {
fn default() -> Self {
Self {
internal: Mutex::new(OverlayContextInternal::default()).into(),
size: DVec2::ZERO,
device_pixel_ratio: 1.0,
viewport: ViewportMessageHandler::default(),
visibility_settings: OverlaysVisibilitySettings::default(),
}
}
@@ -217,11 +210,10 @@ impl core::hash::Hash for OverlayContext {
impl OverlayContext {
#[allow(dead_code)]
pub(super) fn new(size: DVec2, device_pixel_ratio: f64, visibility_settings: OverlaysVisibilitySettings) -> Self {
pub(super) fn new(viewport: ViewportMessageHandler, visibility_settings: OverlaysVisibilitySettings) -> Self {
Self {
internal: Arc::new(Mutex::new(OverlayContextInternal::new(size, device_pixel_ratio, visibility_settings))),
size,
device_pixel_ratio,
internal: Arc::new(Mutex::new(OverlayContextInternal::new(viewport, visibility_settings))),
viewport,
visibility_settings,
}
}
@@ -280,6 +272,14 @@ impl OverlayContext {
self.internal().manipulator_anchor(position, selected, color);
}
pub fn resize_handle(&mut self, position: DVec2, rotation: f64) {
self.internal().resize_handle(position, rotation);
}
pub fn skew_handles(&mut self, edge_start: DVec2, edge_end: DVec2) {
self.internal().skew_handles(edge_start, edge_end);
}
pub fn square(&mut self, position: DVec2, size: Option<f64>, color_fill: Option<&str>, color_stroke: Option<&str>) {
self.internal().square(position, size, color_fill, color_stroke);
}
@@ -292,6 +292,7 @@ impl OverlayContext {
self.internal().circle(position, radius, color_fill, color_stroke);
}
#[allow(clippy::too_many_arguments)]
pub fn dashed_ellipse(
&mut self,
center: DVec2,
@@ -421,23 +422,21 @@ pub enum DrawHandles {
pub(super) struct OverlayContextInternal {
scene: Scene,
size: DVec2,
device_pixel_ratio: f64,
viewport: ViewportMessageHandler,
visibility_settings: OverlaysVisibilitySettings,
}
impl Default for OverlayContextInternal {
fn default() -> Self {
Self::new(DVec2::new(100., 100.), 1., OverlaysVisibilitySettings::default())
Self::new(ViewportMessageHandler::default(), OverlaysVisibilitySettings::default())
}
}
impl OverlayContextInternal {
pub(super) fn new(size: DVec2, device_pixel_ratio: f64, visibility_settings: OverlaysVisibilitySettings) -> Self {
pub(super) fn new(viewport: ViewportMessageHandler, visibility_settings: OverlaysVisibilitySettings) -> Self {
Self {
scene: Scene::new(),
size,
device_pixel_ratio,
viewport,
visibility_settings,
}
}
@@ -473,7 +472,7 @@ impl OverlayContextInternal {
self.scene.fill(peniko::Fill::NonZero, transform, Self::parse_color(color_fill), None, &path);
self.scene.stroke(&kurbo::Stroke::new(1.0), transform, Self::parse_color(color_stroke), None, &path);
self.scene.stroke(&kurbo::Stroke::new(1.), transform, Self::parse_color(color_stroke), None, &path);
}
fn dashed_quad(&mut self, quad: Quad, stroke_color: Option<&str>, color_fill: Option<&str>, dash_width: Option<f64>, dash_gap_width: Option<f64>, dash_offset: Option<f64>) {
@@ -506,7 +505,7 @@ impl OverlayContextInternal {
}
let stroke_color = stroke_color.unwrap_or(COLOR_OVERLAY_BLUE);
let mut stroke = kurbo::Stroke::new(1.0);
let mut stroke = kurbo::Stroke::new(1.);
if let Some(dash_width) = dash_width {
let dash_gap = dash_gap_width.unwrap_or(1.);
@@ -551,7 +550,7 @@ impl OverlayContextInternal {
self.scene.fill(peniko::Fill::NonZero, transform, Self::parse_color(fill), None, &circle);
self.scene
.stroke(&kurbo::Stroke::new(1.0), transform, Self::parse_color(color.unwrap_or(COLOR_OVERLAY_BLUE)), None, &circle);
.stroke(&kurbo::Stroke::new(1.), transform, Self::parse_color(color.unwrap_or(COLOR_OVERLAY_BLUE)), None, &circle);
}
fn hover_manipulator_handle(&mut self, position: DVec2, selected: bool) {
@@ -563,13 +562,13 @@ impl OverlayContextInternal {
let fill = COLOR_OVERLAY_BLUE_50;
self.scene.fill(peniko::Fill::NonZero, transform, Self::parse_color(fill), None, &circle);
self.scene.stroke(&kurbo::Stroke::new(1.0), transform, Self::parse_color(COLOR_OVERLAY_BLUE_50), None, &circle);
self.scene.stroke(&kurbo::Stroke::new(1.), transform, Self::parse_color(COLOR_OVERLAY_BLUE_50), None, &circle);
let inner_circle = kurbo::Circle::new((position.x, position.y), MANIPULATOR_GROUP_MARKER_SIZE / 2.);
let color_fill = if selected { COLOR_OVERLAY_BLUE } else { COLOR_OVERLAY_WHITE };
self.scene.fill(peniko::Fill::NonZero, transform, Self::parse_color(color_fill), None, &circle);
self.scene.stroke(&kurbo::Stroke::new(1.0), transform, Self::parse_color(COLOR_OVERLAY_BLUE), None, &inner_circle);
self.scene.stroke(&kurbo::Stroke::new(1.), transform, Self::parse_color(COLOR_OVERLAY_BLUE), None, &inner_circle);
}
fn manipulator_anchor(&mut self, position: DVec2, selected: bool, color: Option<&str>) {
@@ -584,8 +583,22 @@ impl OverlayContextInternal {
self.square(position, None, Some(color_fill), Some(COLOR_OVERLAY_BLUE));
}
fn resize_handle(&mut self, position: DVec2, rotation: f64) {
let quad = DAffine2::from_angle_translation(rotation, position) * Quad::from_box([DVec2::splat(-RESIZE_HANDLE_SIZE / 2.), DVec2::splat(RESIZE_HANDLE_SIZE / 2.)]);
self.quad(quad, None, Some(COLOR_OVERLAY_WHITE));
}
fn skew_handles(&mut self, edge_start: DVec2, edge_end: DVec2) {
let edge_dir = (edge_end - edge_start).normalize();
let mid = edge_end.midpoint(edge_start);
for edge in [edge_dir, -edge_dir] {
self.draw_triangle(mid + edge * 3. + SKEW_TRIANGLE_OFFSET, edge, SKEW_TRIANGLE_SIZE, None, None);
}
}
fn get_transform(&self) -> kurbo::Affine {
kurbo::Affine::scale(self.device_pixel_ratio)
kurbo::Affine::scale(self.viewport.scale())
}
fn square(&mut self, position: DVec2, size: Option<f64>, color_fill: Option<&str>, color_stroke: Option<&str>) {
@@ -601,7 +614,7 @@ impl OverlayContextInternal {
self.scene.fill(peniko::Fill::NonZero, transform, Self::parse_color(color_fill), None, &rect);
self.scene.stroke(&kurbo::Stroke::new(1.0), transform, Self::parse_color(color_stroke), None, &rect);
self.scene.stroke(&kurbo::Stroke::new(1.), transform, Self::parse_color(color_stroke), None, &rect);
}
fn pixel(&mut self, position: DVec2, color: Option<&str>) {
@@ -627,9 +640,10 @@ impl OverlayContextInternal {
self.scene.fill(peniko::Fill::NonZero, transform, Self::parse_color(color_fill), None, &circle);
self.scene.stroke(&kurbo::Stroke::new(1.0), transform, Self::parse_color(color_stroke), None, &circle);
self.scene.stroke(&kurbo::Stroke::new(1.), transform, Self::parse_color(color_stroke), None, &circle);
}
#[allow(clippy::too_many_arguments)]
fn dashed_ellipse(
&mut self,
_center: DVec2,
@@ -678,7 +692,7 @@ impl OverlayContextInternal {
);
}
self.scene.stroke(&kurbo::Stroke::new(1.0), self.get_transform(), Self::parse_color(COLOR_OVERLAY_BLUE), None, &path);
self.scene.stroke(&kurbo::Stroke::new(1.), self.get_transform(), Self::parse_color(COLOR_OVERLAY_BLUE), None, &path);
}
fn draw_arc_gizmo_angle(&mut self, pivot: DVec2, bold_radius: f64, arc_radius: f64, offset_angle: f64, angle: f64) {
@@ -700,7 +714,7 @@ impl OverlayContextInternal {
let mut fill_color = Color::from_rgb_str(COLOR_OVERLAY_WHITE.strip_prefix('#').unwrap()).unwrap().with_alpha(0.05).to_rgba_hex_srgb();
fill_color.insert(0, '#');
let fill_color = Some(fill_color.as_str());
self.line(start + DVec2::X * radius * sign, start + DVec2::X * (radius * scale), None, None);
self.line(start + DVec2::X * radius * sign, start + DVec2::X * radius * scale.abs(), None, None);
self.circle(start, radius, fill_color, None);
self.circle(start, radius * scale.abs(), fill_color, None);
self.text(
@@ -714,14 +728,14 @@ impl OverlayContextInternal {
}
fn compass_rose(&mut self, compass_center: DVec2, angle: f64, show_compass_with_hover_ring: Option<bool>) {
const HOVER_RING_OUTER_RADIUS: f64 = COMPASS_ROSE_HOVER_RING_DIAMETER / 2.;
const MAIN_RING_OUTER_RADIUS: f64 = COMPASS_ROSE_MAIN_RING_DIAMETER / 2.;
const MAIN_RING_INNER_RADIUS: f64 = COMPASS_ROSE_RING_INNER_DIAMETER / 2.;
const ARROW_RADIUS: f64 = COMPASS_ROSE_ARROW_SIZE / 2.;
const HOVER_RING_STROKE_WIDTH: f64 = HOVER_RING_OUTER_RADIUS - MAIN_RING_INNER_RADIUS;
const HOVER_RING_CENTERLINE_RADIUS: f64 = (HOVER_RING_OUTER_RADIUS + MAIN_RING_INNER_RADIUS) / 2.;
const MAIN_RING_STROKE_WIDTH: f64 = MAIN_RING_OUTER_RADIUS - MAIN_RING_INNER_RADIUS;
const MAIN_RING_CENTERLINE_RADIUS: f64 = (MAIN_RING_OUTER_RADIUS + MAIN_RING_INNER_RADIUS) / 2.;
let hover_ring_outer_radius: f64 = COMPASS_ROSE_HOVER_RING_DIAMETER / 2.;
let main_ring_outer_radius: f64 = COMPASS_ROSE_MAIN_RING_DIAMETER / 2.;
let main_ring_inner_radius: f64 = COMPASS_ROSE_RING_INNER_DIAMETER / 2.;
let arrow_radius: f64 = COMPASS_ROSE_ARROW_SIZE / 2.;
let hover_ring_stroke_width: f64 = hover_ring_outer_radius - main_ring_inner_radius;
let hover_ring_centerline_radius: f64 = (hover_ring_outer_radius + main_ring_inner_radius) / 2.;
let main_ring_stroke_width: f64 = main_ring_outer_radius - main_ring_inner_radius;
let main_ring_centerline_radius: f64 = (main_ring_outer_radius + main_ring_inner_radius) / 2.;
let Some(show_hover_ring) = show_compass_with_hover_ring else { return };
@@ -733,9 +747,9 @@ impl OverlayContextInternal {
let mut fill_color = Color::from_rgb_str(COLOR_OVERLAY_BLUE.strip_prefix('#').unwrap()).unwrap().with_alpha(0.5).to_rgba_hex_srgb();
fill_color.insert(0, '#');
let circle = kurbo::Circle::new((center.x, center.y), HOVER_RING_CENTERLINE_RADIUS);
let circle = kurbo::Circle::new((center.x, center.y), hover_ring_centerline_radius);
self.scene
.stroke(&kurbo::Stroke::new(HOVER_RING_STROKE_WIDTH), transform, Self::parse_color(&fill_color), None, &circle);
.stroke(&kurbo::Stroke::new(hover_ring_stroke_width), transform, Self::parse_color(&fill_color), None, &circle);
}
// Arrows
@@ -743,11 +757,11 @@ impl OverlayContextInternal {
let direction = DVec2::from_angle(i as f64 * FRAC_PI_2 + angle);
let color = if i % 2 == 0 { COLOR_OVERLAY_RED } else { COLOR_OVERLAY_GREEN };
let tip = center + direction * HOVER_RING_OUTER_RADIUS;
let base = center + direction * (MAIN_RING_INNER_RADIUS + MAIN_RING_OUTER_RADIUS) / 2.;
let tip = center + direction * hover_ring_outer_radius;
let base = center + direction * (main_ring_inner_radius + main_ring_outer_radius) / 2.;
let r = (ARROW_RADIUS.powi(2) + MAIN_RING_INNER_RADIUS.powi(2)).sqrt();
let (cos, sin) = (MAIN_RING_INNER_RADIUS / r, ARROW_RADIUS / r);
let r = (arrow_radius.powi(2) + main_ring_inner_radius.powi(2)).sqrt();
let (cos, sin) = (main_ring_inner_radius / r, arrow_radius / r);
let side1 = center + r * DVec2::new(cos * direction.x - sin * direction.y, sin * direction.x + direction.y * cos);
let side2 = center + r * DVec2::new(cos * direction.x + sin * direction.y, -sin * direction.x + direction.y * cos);
@@ -764,9 +778,9 @@ impl OverlayContextInternal {
}
// Main ring
let circle = kurbo::Circle::new((center.x, center.y), MAIN_RING_CENTERLINE_RADIUS);
let circle = kurbo::Circle::new((center.x, center.y), main_ring_centerline_radius);
self.scene
.stroke(&kurbo::Stroke::new(MAIN_RING_STROKE_WIDTH), transform, Self::parse_color(COLOR_OVERLAY_BLUE), None, &circle);
.stroke(&kurbo::Stroke::new(main_ring_stroke_width), transform, Self::parse_color(COLOR_OVERLAY_BLUE), None, &circle);
}
fn pivot(&mut self, position: DVec2, angle: f64) {
@@ -780,22 +794,22 @@ impl OverlayContextInternal {
self.scene.fill(peniko::Fill::NonZero, transform, Self::parse_color(COLOR_OVERLAY_YELLOW), None, &circle);
// Crosshair
const CROSSHAIR_RADIUS: f64 = (PIVOT_CROSSHAIR_LENGTH - PIVOT_CROSSHAIR_THICKNESS) / 2.;
let crosshair_radius: f64 = (PIVOT_CROSSHAIR_LENGTH - PIVOT_CROSSHAIR_THICKNESS) / 2.;
let mut stroke = kurbo::Stroke::new(PIVOT_CROSSHAIR_THICKNESS);
stroke = stroke.with_caps(kurbo::Cap::Round);
// Horizontal line
let mut path = BezPath::new();
path.move_to(kurbo::Point::new(x + CROSSHAIR_RADIUS * uv.x, y + CROSSHAIR_RADIUS * uv.y));
path.line_to(kurbo::Point::new(x - CROSSHAIR_RADIUS * uv.x, y - CROSSHAIR_RADIUS * uv.y));
path.move_to(kurbo::Point::new(x + crosshair_radius * uv.x, y + crosshair_radius * uv.y));
path.line_to(kurbo::Point::new(x - crosshair_radius * uv.x, y - crosshair_radius * uv.y));
self.scene.stroke(&stroke, transform, Self::parse_color(COLOR_OVERLAY_YELLOW), None, &path);
// Vertical line
let mut path = BezPath::new();
path.move_to(kurbo::Point::new(x - CROSSHAIR_RADIUS * uv.y, y + CROSSHAIR_RADIUS * uv.x));
path.line_to(kurbo::Point::new(x + CROSSHAIR_RADIUS * uv.y, y - CROSSHAIR_RADIUS * uv.x));
path.move_to(kurbo::Point::new(x - crosshair_radius * uv.y, y + crosshair_radius * uv.x));
path.line_to(kurbo::Point::new(x + crosshair_radius * uv.y, y - crosshair_radius * uv.x));
self.scene.stroke(&stroke, transform, Self::parse_color(COLOR_OVERLAY_YELLOW), None, &path);
}
@@ -809,15 +823,15 @@ impl OverlayContextInternal {
// Draw the background circle with a white fill and colored outline
let circle = kurbo::Circle::new((x, y), DOWEL_PIN_RADIUS);
self.scene.fill(peniko::Fill::NonZero, transform, Self::parse_color(COLOR_OVERLAY_WHITE), None, &circle);
self.scene.stroke(&kurbo::Stroke::new(1.0), transform, Self::parse_color(color), None, &circle);
self.scene.stroke(&kurbo::Stroke::new(1.), transform, Self::parse_color(color), None, &circle);
// Draw the two filled sectors using paths
let mut path = BezPath::new();
// Top-left sector
path.move_to(kurbo::Point::new(x, y));
let end_x = x + DOWEL_PIN_RADIUS * (FRAC_PI_2 + angle).cos();
let end_y = y + DOWEL_PIN_RADIUS * (FRAC_PI_2 + angle).sin();
let end_x = x + DOWEL_PIN_RADIUS * (FRAC_PI_2 + angle.cos());
let end_y = y + DOWEL_PIN_RADIUS * (FRAC_PI_2 + angle.sin());
path.line_to(kurbo::Point::new(end_x, end_y));
// Draw arc manually
let arc = kurbo::Arc::new((x, y), (DOWEL_PIN_RADIUS, DOWEL_PIN_RADIUS), FRAC_PI_2 + angle, FRAC_PI_2, 0.0);
@@ -828,8 +842,8 @@ impl OverlayContextInternal {
// Bottom-right sector
path.move_to(kurbo::Point::new(x, y));
let end_x = x + DOWEL_PIN_RADIUS * (PI + FRAC_PI_2 + angle).cos();
let end_y = y + DOWEL_PIN_RADIUS * (PI + FRAC_PI_2 + angle).sin();
let end_x = x + DOWEL_PIN_RADIUS * (PI + FRAC_PI_2 + angle.cos());
let end_y = y + DOWEL_PIN_RADIUS * (PI + FRAC_PI_2 + angle.sin());
path.line_to(kurbo::Point::new(end_x, end_y));
// Draw arc manually
let arc = kurbo::Arc::new((x, y), (DOWEL_PIN_RADIUS, DOWEL_PIN_RADIUS), PI + FRAC_PI_2 + angle, FRAC_PI_2, 0.0);
@@ -861,7 +875,7 @@ impl OverlayContextInternal {
self.bezier_to_path(bezier, transform, move_to, &mut path);
}
self.scene.stroke(&kurbo::Stroke::new(1.0), vello_transform, Self::parse_color(COLOR_OVERLAY_BLUE), None, &path);
self.scene.stroke(&kurbo::Stroke::new(1.), vello_transform, Self::parse_color(COLOR_OVERLAY_BLUE), None, &path);
}
/// Used by the Pen tool in order to show how the bezier curve would look like.
@@ -870,7 +884,7 @@ impl OverlayContextInternal {
let mut path = BezPath::new();
self.bezier_to_path(bezier, transform, true, &mut path);
self.scene.stroke(&kurbo::Stroke::new(1.0), vello_transform, Self::parse_color(COLOR_OVERLAY_BLUE), None, &path);
self.scene.stroke(&kurbo::Stroke::new(1.), vello_transform, Self::parse_color(COLOR_OVERLAY_BLUE), None, &path);
}
/// Used by the path tool segment mode in order to show the selected segments.
@@ -963,7 +977,7 @@ impl OverlayContextInternal {
let path = self.push_path(subpaths.iter(), transform);
let color = color.unwrap_or(COLOR_OVERLAY_BLUE);
self.scene.stroke(&kurbo::Stroke::new(1.0), self.get_transform(), Self::parse_color(color), None, &path);
self.scene.stroke(&kurbo::Stroke::new(1.), self.get_transform(), Self::parse_color(color), None, &path);
}
}
@@ -1010,7 +1024,7 @@ impl OverlayContextInternal {
x_extend: peniko::Extend::Repeat,
y_extend: peniko::Extend::Repeat,
quality: peniko::ImageQuality::default(),
alpha: 1.0,
alpha: 1.,
},
};