Add pivot type selection with Custom Pivot, Origin (Average Point), and Origin (Active Object) to the Select tool (#2730)

* add origin

* cleanup pivot

* a lot of stuff

* reset pivot

* fix transform with pivot issues

* fixes

* some more cleanup

* fixes

* finally works

* origin fixes

* fix spaces

* fix using dragged_layers

* simplify pivot logic

* fix bugs

* fix the final bug

* fix in select_tool

* fix updates

* some more refactors to fix misunderstanding and refactor

* add checkboxes

* fix labels

* fix stuff which broke at merge

* update

* cargo fmt

* fix serde crash

* fix pivot not updating on move

* fix pivot not becoming last active refernce

* fix redraw issues

* add: active pivot

* cargo fmt

* fix pivot showing up in default mode

* add: pivot pin

* fix: use pin icons

* cargo: cargo lock update?

* fix: use checkbox instead of Overlays

* refactor: add dot to path_tool

* add: active origins

* UI tweaks

* add: add all of the stuff for path tool

* remove: unused layer

* fix: pivot pinning and origin angle

* fix: pin only if moved in first place

* cargo: fmt

* fix: pivot use disabled method

* fix: remove redudant NoOp

* fix: 3 stuff

* fix: select from elsewhere

* fix: compass rose wobbling around

* add: move pivot on grab

* add: move pivot on nudge

* add: move pivot on Grab

* Code review and tooltips

* fixes

* fixes

* fixes

* fix: skipping artboard on bounds calculation

* fix: by default have origin

* Fix prior fix

---------

Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
mTvare
2025-07-08 09:24:54 +00:00
committed by GitHub
co-authored by Keavon Chambers
parent a1d93da532
commit 8a68683a93
27 changed files with 1864 additions and 1139 deletions
@@ -1,12 +1,12 @@
use super::utility_functions::overlay_canvas_context;
use crate::consts::{
COLOR_OVERLAY_BLUE, COLOR_OVERLAY_BLUE_50, COLOR_OVERLAY_GREEN, COLOR_OVERLAY_RED, COLOR_OVERLAY_WHITE, COLOR_OVERLAY_YELLOW, COMPASS_ROSE_ARROW_SIZE, COMPASS_ROSE_HOVER_RING_DIAMETER,
COMPASS_ROSE_MAIN_RING_DIAMETER, COMPASS_ROSE_RING_INNER_DIAMETER, MANIPULATOR_GROUP_MARKER_SIZE, PIVOT_CROSSHAIR_LENGTH, PIVOT_CROSSHAIR_THICKNESS, PIVOT_DIAMETER,
COLOR_OVERLAY_BLUE, 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,
};
use crate::messages::prelude::Message;
use bezier_rs::{Bezier, Subpath};
use core::borrow::Borrow;
use core::f64::consts::{FRAC_PI_2, TAU};
use core::f64::consts::{FRAC_PI_2, PI, TAU};
use glam::{DAffine2, DVec2};
use graphene_std::Color;
use graphene_std::math::quad::Quad;
@@ -33,12 +33,14 @@ pub enum OverlaysType {
HoverOutline,
SelectionOutline,
Pivot,
Origin,
Path,
Anchors,
Handles,
}
#[derive(PartialEq, Copy, Clone, Debug, serde::Serialize, serde::Deserialize, specta::Type)]
#[serde(default)]
pub struct OverlaysVisibilitySettings {
pub all: bool,
pub artboard_name: bool,
@@ -49,6 +51,7 @@ pub struct OverlaysVisibilitySettings {
pub hover_outline: bool,
pub selection_outline: bool,
pub pivot: bool,
pub origin: bool,
pub path: bool,
pub anchors: bool,
pub handles: bool,
@@ -66,6 +69,7 @@ impl Default for OverlaysVisibilitySettings {
hover_outline: true,
selection_outline: true,
pivot: true,
origin: true,
path: true,
anchors: true,
handles: true,
@@ -110,6 +114,10 @@ impl OverlaysVisibilitySettings {
self.all && self.pivot
}
pub fn origin(&self) -> bool {
self.all && self.origin
}
pub fn path(&self) -> bool {
self.all && self.path
}
@@ -423,10 +431,7 @@ impl OverlayContext {
pub fn draw_scale(&mut self, start: DVec2, scale: f64, radius: f64, text: &str) {
let sign = scale.signum();
let mut fill_color = graphene_std::Color::from_rgb_str(crate::consts::COLOR_OVERLAY_WHITE.strip_prefix('#').unwrap())
.unwrap()
.with_alpha(0.05)
.to_rgba_hex_srgb();
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);
@@ -463,10 +468,7 @@ impl OverlayContext {
// Hover ring
if show_hover_ring {
let mut fill_color = graphene_std::Color::from_rgb_str(COLOR_OVERLAY_BLUE.strip_prefix('#').unwrap())
.unwrap()
.with_alpha(0.5)
.to_rgba_hex_srgb();
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, '#');
self.render_context.set_line_width(HOVER_RING_STROKE_WIDTH);
@@ -550,6 +552,36 @@ impl OverlayContext {
self.end_dpi_aware_transform();
}
pub fn dowel_pin(&mut self, position: DVec2, angle: f64, color: Option<&str>) {
let (x, y) = (position.round() - DVec2::splat(0.5)).into();
let color = color.unwrap_or(COLOR_OVERLAY_YELLOW_DULL);
self.start_dpi_aware_transform();
// Draw the background circle with a white fill and blue outline
self.render_context.begin_path();
self.render_context.arc(x, y, DOWEL_PIN_RADIUS, 0., TAU).expect("Failed to draw the circle");
self.render_context.set_fill_style_str(COLOR_OVERLAY_WHITE);
self.render_context.fill();
self.render_context.set_stroke_style_str(color);
self.render_context.stroke();
// Draw the two blue filled sectors
self.render_context.begin_path();
// Top-left sector
self.render_context.move_to(x, y);
self.render_context.arc(x, y, DOWEL_PIN_RADIUS, FRAC_PI_2 + angle, PI + angle).expect("Failed to draw arc");
self.render_context.close_path();
// Bottom-right sector
self.render_context.move_to(x, y);
self.render_context.arc(x, y, DOWEL_PIN_RADIUS, PI + FRAC_PI_2 + angle, TAU + angle).expect("Failed to draw arc");
self.render_context.close_path();
self.render_context.set_fill_style_str(color);
self.render_context.fill();
self.end_dpi_aware_transform();
}
/// Used by the Pen and Path tools to outline the path of the shape.
pub fn outline_vector(&mut self, vector_data: &VectorData, transform: DAffine2) {
self.start_dpi_aware_transform();
@@ -599,9 +631,11 @@ impl OverlayContext {
pub fn outline_overlay_bezier(&mut self, bezier: Bezier, transform: DAffine2) {
self.start_dpi_aware_transform();
let color = Color::from_rgb_str(COLOR_OVERLAY_BLUE.strip_prefix('#').unwrap()).unwrap().with_alpha(0.05).to_rgba_hex_srgb();
self.render_context.begin_path();
self.bezier_command(bezier, transform, true);
self.render_context.set_stroke_style_str(COLOR_OVERLAY_BLUE_50);
self.render_context.set_stroke_style_str(&color);
self.render_context.set_line_width(4.);
self.render_context.stroke();
@@ -731,11 +765,11 @@ impl OverlayContext {
// └──┴──┴──┴──┘
let pixels = [(0, 0), (2, 2)];
for &(x, y) in &pixels {
let index = (x + y * PATTERN_WIDTH as usize) * 4;
let index = (x + y * PATTERN_WIDTH) * 4;
data[index..index + 4].copy_from_slice(&color.to_rgba8_srgb());
}
let image_data = web_sys::ImageData::new_with_u8_clamped_array_and_sh(wasm_bindgen::Clamped(&mut data), PATTERN_WIDTH as u32, PATTERN_HEIGHT as u32).unwrap();
let image_data = web_sys::ImageData::new_with_u8_clamped_array_and_sh(wasm_bindgen::Clamped(&data), PATTERN_WIDTH as u32, PATTERN_HEIGHT as u32).unwrap();
pattern_context.put_image_data(&image_data, 0., 0.).unwrap();
let pattern = self.render_context.create_pattern_with_offscreen_canvas(&pattern_canvas, "repeat").unwrap().unwrap();