Skip to content

Commit 8a68683

Browse files
mTvare6Keavon
andauthored
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 <[email protected]>
1 parent a1d93da commit 8a68683

27 files changed

+1848
-1123
lines changed

Cargo.lock

Lines changed: 973 additions & 760 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

editor/src/consts.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ pub const SELECTION_DRAG_ANGLE: f64 = 90.;
6161
pub const PIVOT_CROSSHAIR_THICKNESS: f64 = 1.;
6262
pub const PIVOT_CROSSHAIR_LENGTH: f64 = 9.;
6363
pub const PIVOT_DIAMETER: f64 = 5.;
64+
pub const DOWEL_PIN_RADIUS: f64 = 4.;
6465

6566
// COMPASS ROSE
6667
pub const COMPASS_ROSE_RING_INNER_DIAMETER: f64 = 13.;
@@ -133,8 +134,8 @@ pub const SCALE_EFFECT: f64 = 0.5;
133134

134135
// COLORS
135136
pub const COLOR_OVERLAY_BLUE: &str = "#00a8ff";
136-
pub const COLOR_OVERLAY_BLUE_50: &str = "rgba(0, 168, 255, 0.5)";
137137
pub const COLOR_OVERLAY_YELLOW: &str = "#ffc848";
138+
pub const COLOR_OVERLAY_YELLOW_DULL: &str = "#d7ba8b";
138139
pub const COLOR_OVERLAY_GREEN: &str = "#63ce63";
139140
pub const COLOR_OVERLAY_RED: &str = "#ef5454";
140141
pub const COLOR_OVERLAY_GRAY: &str = "#cccccc";

editor/src/messages/layout/utility_types/widgets/input_widgets.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -471,6 +471,8 @@ pub struct ReferencePointInput {
471471

472472
pub disabled: bool,
473473

474+
pub tooltip: String,
475+
474476
// Callbacks
475477
#[serde(skip)]
476478
#[derivative(Debug = "ignore", PartialEq = "ignore")]

editor/src/messages/portfolio/document/document_message_handler.rs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -750,6 +750,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
750750
// Nudge translation without resizing
751751
if !resize {
752752
let transform = DAffine2::from_translation(DVec2::from_angle(-self.document_ptz.tilt()).rotate(DVec2::new(delta_x, delta_y)));
753+
responses.add(SelectToolMessage::ShiftSelectedNodes { offset: transform.translation });
753754

754755
for layer in self.network_interface.shallowest_unique_layers(&[]).filter(|layer| can_move(*layer)) {
755756
responses.add(GraphOperationMessage::TransformChange {
@@ -1185,6 +1186,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
11851186
OverlaysType::HoverOutline => visibility_settings.hover_outline = visible,
11861187
OverlaysType::SelectionOutline => visibility_settings.selection_outline = visible,
11871188
OverlaysType::Pivot => visibility_settings.pivot = visible,
1189+
OverlaysType::Origin => visibility_settings.origin = visible,
11881190
OverlaysType::Path => visibility_settings.path = visible,
11891191
OverlaysType::Anchors => {
11901192
visibility_settings.anchors = visible;
@@ -1716,6 +1718,14 @@ impl DocumentMessageHandler {
17161718
.reduce(graphene_std::renderer::Quad::combine_bounds)
17171719
}
17181720

1721+
pub fn selected_visible_and_unlock_layers_bounding_box_document(&self) -> Option<[DVec2; 2]> {
1722+
self.network_interface
1723+
.selected_nodes()
1724+
.selected_visible_and_unlocked_layers(&self.network_interface)
1725+
.map(|layer| self.metadata().nonzero_bounding_box(layer))
1726+
.reduce(graphene_std::renderer::Quad::combine_bounds)
1727+
}
1728+
17191729
pub fn document_network(&self) -> &NodeNetwork {
17201730
self.network_interface.document_network()
17211731
}
@@ -2269,6 +2279,24 @@ impl DocumentMessageHandler {
22692279
]
22702280
},
22712281
},
2282+
LayoutGroup::Row {
2283+
widgets: {
2284+
let mut checkbox_id = CheckboxId::default();
2285+
vec![
2286+
CheckboxInput::new(self.overlays_visibility_settings.pivot)
2287+
.on_update(|optional_input: &CheckboxInput| {
2288+
DocumentMessage::SetOverlaysVisibility {
2289+
visible: optional_input.checked,
2290+
overlays_type: Some(OverlaysType::Origin),
2291+
}
2292+
.into()
2293+
})
2294+
.for_label(checkbox_id.clone())
2295+
.widget_holder(),
2296+
TextLabel::new("Transform Origin".to_string()).for_checkbox(&mut checkbox_id).widget_holder(),
2297+
]
2298+
},
2299+
},
22722300
LayoutGroup::Row {
22732301
widgets: {
22742302
let mut checkbox_id = CheckboxId::default();

editor/src/messages/portfolio/document/graph_operation/graph_operation_message.rs

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use crate::messages::portfolio::document::utility_types::document_metadata::Laye
33
use crate::messages::portfolio::document::utility_types::network_interface::NodeTemplate;
44
use crate::messages::prelude::*;
55
use bezier_rs::Subpath;
6-
use glam::{DAffine2, DVec2, IVec2};
6+
use glam::{DAffine2, IVec2};
77
use graph_craft::document::NodeId;
88
use graphene_std::Artboard;
99
use graphene_std::brush::brush_stroke::BrushStroke;
@@ -52,10 +52,6 @@ pub enum GraphOperationMessage {
5252
transform_in: TransformIn,
5353
skip_rerender: bool,
5454
},
55-
TransformSetPivot {
56-
layer: LayerNodeIdentifier,
57-
pivot: DVec2,
58-
},
5955
Vector {
6056
layer: LayerNodeIdentifier,
6157
modification_type: VectorModificationType,

editor/src/messages/portfolio/document/graph_operation/graph_operation_message_handler.rs

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -89,15 +89,6 @@ impl MessageHandler<GraphOperationMessage, GraphOperationMessageData<'_>> for Gr
8989
modify_inputs.transform_set(transform, transform_in, skip_rerender);
9090
}
9191
}
92-
GraphOperationMessage::TransformSetPivot { layer, pivot } => {
93-
if layer == LayerNodeIdentifier::ROOT_PARENT {
94-
log::error!("Cannot run TransformSetPivot on ROOT_PARENT");
95-
return;
96-
}
97-
if let Some(mut modify_inputs) = ModifyInputsContext::new_with_layer(layer, network_interface, responses) {
98-
modify_inputs.pivot_set(pivot);
99-
}
100-
}
10192
GraphOperationMessage::Vector { layer, modification_type } => {
10293
if layer == LayerNodeIdentifier::ROOT_PARENT {
10394
log::error!("Cannot run Vector on ROOT_PARENT");

editor/src/messages/portfolio/document/graph_operation/utility_types.rs

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use crate::messages::portfolio::document::utility_types::document_metadata::Laye
44
use crate::messages::portfolio::document::utility_types::network_interface::{self, InputConnector, NodeNetworkInterface, OutputConnector};
55
use crate::messages::prelude::*;
66
use bezier_rs::Subpath;
7-
use glam::{DAffine2, DVec2, IVec2};
7+
use glam::{DAffine2, IVec2};
88
use graph_craft::concrete;
99
use graph_craft::document::value::TaggedValue;
1010
use graph_craft::document::{NodeId, NodeInput};
@@ -458,12 +458,6 @@ impl<'a> ModifyInputsContext<'a> {
458458
}
459459
}
460460

461-
pub fn pivot_set(&mut self, new_pivot: DVec2) {
462-
let Some(transform_node_id) = self.existing_node_id("Transform", true) else { return };
463-
464-
self.set_input_with_refresh(InputConnector::node(transform_node_id, 5), NodeInput::value(TaggedValue::DVec2(new_pivot), false), false);
465-
}
466-
467461
pub fn vector_modify(&mut self, modification_type: VectorModificationType) {
468462
let Some(path_node_id) = self.existing_node_id("Path", true) else { return };
469463
self.network_interface.vector_modify(&path_node_id, modification_type);

editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1308,7 +1308,6 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
13081308
NodeInput::value(TaggedValue::F64(0.), false),
13091309
NodeInput::value(TaggedValue::DVec2(DVec2::ONE), false),
13101310
NodeInput::value(TaggedValue::DVec2(DVec2::ZERO), false),
1311-
NodeInput::value(TaggedValue::DVec2(DVec2::splat(0.5)), false),
13121311
],
13131312
implementation: DocumentNodeImplementation::Network(NodeNetwork {
13141313
exports: vec![NodeInput::node(NodeId(1), 0)],
@@ -1327,7 +1326,6 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
13271326
NodeInput::network(concrete!(f64), 2),
13281327
NodeInput::network(concrete!(DVec2), 3),
13291328
NodeInput::network(concrete!(DVec2), 4),
1330-
NodeInput::network(concrete!(DVec2), 5),
13311329
],
13321330
manual_composition: Some(concrete!(Context)),
13331331
implementation: DocumentNodeImplementation::ProtoNode(transform_nodes::transform::IDENTIFIER),
@@ -1395,7 +1393,6 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
13951393
}),
13961394
),
13971395
InputMetadata::with_name_description_override("Skew", "TODO", WidgetOverride::Custom("transform_skew".to_string())),
1398-
InputMetadata::with_name_description_override("Pivot", "TODO", WidgetOverride::Hidden),
13991396
],
14001397
output_names: vec!["Data".to_string()],
14011398
..Default::default()

editor/src/messages/portfolio/document/node_graph/utility_types.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,7 @@ pub struct FrontendClickTargets {
191191
pub modify_import_export: Vec<String>,
192192
}
193193

194-
#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
194+
#[derive(Clone, Copy, Debug, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
195195
pub enum Direction {
196196
Up,
197197
Down,

editor/src/messages/portfolio/document/overlays/utility_types.rs

Lines changed: 48 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
use super::utility_functions::overlay_canvas_context;
22
use crate::consts::{
3-
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,
4-
COMPASS_ROSE_MAIN_RING_DIAMETER, COMPASS_ROSE_RING_INNER_DIAMETER, MANIPULATOR_GROUP_MARKER_SIZE, PIVOT_CROSSHAIR_LENGTH, PIVOT_CROSSHAIR_THICKNESS, PIVOT_DIAMETER,
3+
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,
4+
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,
55
};
66
use crate::messages::prelude::Message;
77
use bezier_rs::{Bezier, Subpath};
88
use core::borrow::Borrow;
9-
use core::f64::consts::{FRAC_PI_2, TAU};
9+
use core::f64::consts::{FRAC_PI_2, PI, TAU};
1010
use glam::{DAffine2, DVec2};
1111
use graphene_std::Color;
1212
use graphene_std::math::quad::Quad;
@@ -33,12 +33,14 @@ pub enum OverlaysType {
3333
HoverOutline,
3434
SelectionOutline,
3535
Pivot,
36+
Origin,
3637
Path,
3738
Anchors,
3839
Handles,
3940
}
4041

4142
#[derive(PartialEq, Copy, Clone, Debug, serde::Serialize, serde::Deserialize, specta::Type)]
43+
#[serde(default)]
4244
pub struct OverlaysVisibilitySettings {
4345
pub all: bool,
4446
pub artboard_name: bool,
@@ -49,6 +51,7 @@ pub struct OverlaysVisibilitySettings {
4951
pub hover_outline: bool,
5052
pub selection_outline: bool,
5153
pub pivot: bool,
54+
pub origin: bool,
5255
pub path: bool,
5356
pub anchors: bool,
5457
pub handles: bool,
@@ -66,6 +69,7 @@ impl Default for OverlaysVisibilitySettings {
6669
hover_outline: true,
6770
selection_outline: true,
6871
pivot: true,
72+
origin: true,
6973
path: true,
7074
anchors: true,
7175
handles: true,
@@ -110,6 +114,10 @@ impl OverlaysVisibilitySettings {
110114
self.all && self.pivot
111115
}
112116

117+
pub fn origin(&self) -> bool {
118+
self.all && self.origin
119+
}
120+
113121
pub fn path(&self) -> bool {
114122
self.all && self.path
115123
}
@@ -423,10 +431,7 @@ impl OverlayContext {
423431

424432
pub fn draw_scale(&mut self, start: DVec2, scale: f64, radius: f64, text: &str) {
425433
let sign = scale.signum();
426-
let mut fill_color = graphene_std::Color::from_rgb_str(crate::consts::COLOR_OVERLAY_WHITE.strip_prefix('#').unwrap())
427-
.unwrap()
428-
.with_alpha(0.05)
429-
.to_rgba_hex_srgb();
434+
let mut fill_color = Color::from_rgb_str(COLOR_OVERLAY_WHITE.strip_prefix('#').unwrap()).unwrap().with_alpha(0.05).to_rgba_hex_srgb();
430435
fill_color.insert(0, '#');
431436
let fill_color = Some(fill_color.as_str());
432437
self.line(start + DVec2::X * radius * sign, start + DVec2::X * (radius * scale), None, None);
@@ -463,10 +468,7 @@ impl OverlayContext {
463468

464469
// Hover ring
465470
if show_hover_ring {
466-
let mut fill_color = graphene_std::Color::from_rgb_str(COLOR_OVERLAY_BLUE.strip_prefix('#').unwrap())
467-
.unwrap()
468-
.with_alpha(0.5)
469-
.to_rgba_hex_srgb();
471+
let mut fill_color = Color::from_rgb_str(COLOR_OVERLAY_BLUE.strip_prefix('#').unwrap()).unwrap().with_alpha(0.5).to_rgba_hex_srgb();
470472
fill_color.insert(0, '#');
471473

472474
self.render_context.set_line_width(HOVER_RING_STROKE_WIDTH);
@@ -550,6 +552,36 @@ impl OverlayContext {
550552
self.end_dpi_aware_transform();
551553
}
552554

555+
pub fn dowel_pin(&mut self, position: DVec2, angle: f64, color: Option<&str>) {
556+
let (x, y) = (position.round() - DVec2::splat(0.5)).into();
557+
let color = color.unwrap_or(COLOR_OVERLAY_YELLOW_DULL);
558+
559+
self.start_dpi_aware_transform();
560+
561+
// Draw the background circle with a white fill and blue outline
562+
self.render_context.begin_path();
563+
self.render_context.arc(x, y, DOWEL_PIN_RADIUS, 0., TAU).expect("Failed to draw the circle");
564+
self.render_context.set_fill_style_str(COLOR_OVERLAY_WHITE);
565+
self.render_context.fill();
566+
self.render_context.set_stroke_style_str(color);
567+
self.render_context.stroke();
568+
569+
// Draw the two blue filled sectors
570+
self.render_context.begin_path();
571+
// Top-left sector
572+
self.render_context.move_to(x, y);
573+
self.render_context.arc(x, y, DOWEL_PIN_RADIUS, FRAC_PI_2 + angle, PI + angle).expect("Failed to draw arc");
574+
self.render_context.close_path();
575+
// Bottom-right sector
576+
self.render_context.move_to(x, y);
577+
self.render_context.arc(x, y, DOWEL_PIN_RADIUS, PI + FRAC_PI_2 + angle, TAU + angle).expect("Failed to draw arc");
578+
self.render_context.close_path();
579+
self.render_context.set_fill_style_str(color);
580+
self.render_context.fill();
581+
582+
self.end_dpi_aware_transform();
583+
}
584+
553585
/// Used by the Pen and Path tools to outline the path of the shape.
554586
pub fn outline_vector(&mut self, vector_data: &VectorData, transform: DAffine2) {
555587
self.start_dpi_aware_transform();
@@ -599,9 +631,11 @@ impl OverlayContext {
599631
pub fn outline_overlay_bezier(&mut self, bezier: Bezier, transform: DAffine2) {
600632
self.start_dpi_aware_transform();
601633

634+
let color = Color::from_rgb_str(COLOR_OVERLAY_BLUE.strip_prefix('#').unwrap()).unwrap().with_alpha(0.05).to_rgba_hex_srgb();
635+
602636
self.render_context.begin_path();
603637
self.bezier_command(bezier, transform, true);
604-
self.render_context.set_stroke_style_str(COLOR_OVERLAY_BLUE_50);
638+
self.render_context.set_stroke_style_str(&color);
605639
self.render_context.set_line_width(4.);
606640
self.render_context.stroke();
607641

@@ -731,11 +765,11 @@ impl OverlayContext {
731765
// └──┴──┴──┴──┘
732766
let pixels = [(0, 0), (2, 2)];
733767
for &(x, y) in &pixels {
734-
let index = (x + y * PATTERN_WIDTH as usize) * 4;
768+
let index = (x + y * PATTERN_WIDTH) * 4;
735769
data[index..index + 4].copy_from_slice(&color.to_rgba8_srgb());
736770
}
737771

738-
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();
772+
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();
739773
pattern_context.put_image_data(&image_data, 0., 0.).unwrap();
740774
let pattern = self.render_context.create_pattern_with_offscreen_canvas(&pattern_canvas, "repeat").unwrap().unwrap();
741775

0 commit comments

Comments
 (0)