Skip to content

Add origin to each layer #2730

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions editor/src/consts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ pub const SELECTION_DRAG_ANGLE: f64 = 90.;
pub const PIVOT_CROSSHAIR_THICKNESS: f64 = 1.;
pub const PIVOT_CROSSHAIR_LENGTH: f64 = 9.;
pub const PIVOT_DIAMETER: f64 = 5.;
pub const DOWEL_PIN_RADIUS: f64 = 5.;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Diameter or radius?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's actually the radius here, we could change the values later

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the PIVOT_DIAMETER actually a radius on the line above, then? Or do they just happen to be twice/half the size of one another?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They happen to be twice/half of each other, I just it looked larger temporarily, we can later decide the final value


// COMPASS ROSE
pub const COMPASS_ROSE_RING_INNER_DIAMETER: f64 = 13.;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,10 @@ pub enum GraphOperationMessage {
layer: LayerNodeIdentifier,
pivot: DVec2,
},
TransformSetOrigin {
layer: LayerNodeIdentifier,
origin: DVec2,
},
Vector {
layer: LayerNodeIdentifier,
modification_type: VectorModificationType,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,15 @@ impl MessageHandler<GraphOperationMessage, GraphOperationMessageData<'_>> for Gr
modify_inputs.pivot_set(pivot);
}
}
GraphOperationMessage::TransformSetOrigin { layer, origin } => {
if layer == LayerNodeIdentifier::ROOT_PARENT {
log::error!("Cannot run TransformSetOrigin on ROOT_PARENT");
return;
}
if let Some(mut modify_inputs) = ModifyInputsContext::new_with_layer(layer, network_interface, responses) {
modify_inputs.origin_set(origin);
}
}
GraphOperationMessage::Vector { layer, modification_type } => {
if layer == LayerNodeIdentifier::ROOT_PARENT {
log::error!("Cannot run Vector on ROOT_PARENT");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -456,6 +456,12 @@ impl<'a> ModifyInputsContext<'a> {
self.set_input_with_refresh(InputConnector::node(transform_node_id, 5), NodeInput::value(TaggedValue::DVec2(new_pivot), false), false);
}

pub fn origin_set(&mut self, new_origin: DVec2) {
let Some(transform_node_id) = self.existing_node_id("Transform", true) else { return };

self.set_input_with_refresh(InputConnector::node(transform_node_id, 6), NodeInput::value(TaggedValue::DVec2(new_origin), false), false);
}

pub fn vector_modify(&mut self, modification_type: VectorModificationType) {
let Some(path_node_id) = self.existing_node_id("Path", true) else { return };
self.network_interface.vector_modify(&path_node_id, modification_type);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2060,6 +2060,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
NodeInput::value(TaggedValue::DVec2(DVec2::ONE), false),
NodeInput::value(TaggedValue::DVec2(DVec2::ZERO), false),
NodeInput::value(TaggedValue::DVec2(DVec2::splat(0.5)), false),
NodeInput::value(TaggedValue::DVec2(DVec2::ZERO), false),
],
implementation: DocumentNodeImplementation::Network(NodeNetwork {
exports: vec![NodeInput::node(NodeId(1), 0)],
Expand All @@ -2079,6 +2080,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
NodeInput::network(concrete!(DVec2), 3),
NodeInput::network(concrete!(DVec2), 4),
NodeInput::network(concrete!(DVec2), 5),
NodeInput::network(concrete!(DVec2), 6),
],
manual_composition: Some(concrete!(Context)),
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::transform::TransformNode")),
Expand Down Expand Up @@ -2147,6 +2149,16 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
),
PropertiesRow::with_override("Skew", "TODO", WidgetOverride::Custom("transform_skew".to_string())),
PropertiesRow::with_override("Pivot", "TODO", WidgetOverride::Hidden),
PropertiesRow::with_override(
"Origin Offset",
"TODO",
WidgetOverride::Vec2(Vec2InputSettings {
x: "X".to_string(),
y: "Y".to_string(),
unit: " px".to_string(),
..Default::default()
}),
),
],
output_names: vec!["Data".to_string()],
..Default::default()
Expand Down
32 changes: 31 additions & 1 deletion editor/src/messages/portfolio/document/overlays/utility_types.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use super::utility_functions::overlay_canvas_context;
use crate::consts::{
COLOR_OVERLAY_BLUE, 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,
COMPASS_ROSE_RING_INNER_DIAMETER, MANIPULATOR_GROUP_MARKER_SIZE, PIVOT_CROSSHAIR_LENGTH, PIVOT_CROSSHAIR_THICKNESS, PIVOT_DIAMETER, DOWEL_PIN_RADIUS
};
use crate::messages::prelude::Message;
use bezier_rs::{Bezier, Subpath};
Expand Down Expand Up @@ -550,6 +550,36 @@ impl OverlayContext {
self.end_dpi_aware_transform();
}

pub fn draw_origin(&mut self, position: DVec2) {
use std::f64::consts::PI;
let (x, y) = (position.round() - DVec2::splat(0.5)).into();

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_OVERLAY_BLUE);
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, PI).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, TAU).expect("Failed to draw arc");
self.render_context.close_path();
self.render_context.set_fill_style_str(COLOR_OVERLAY_BLUE);
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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -253,12 +253,28 @@ pub fn get_pivot(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInte
}
}

/// Locate the origin of the transform node
pub fn get_origin(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option<DVec2> {
let origin_node_input_index = 6;
if let TaggedValue::DVec2(origin) = NodeGraphLayer::new(layer, network_interface).find_input("Transform", origin_node_input_index)? {
Some(*origin)
} else {
None
}
}

pub fn get_viewport_pivot(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> DVec2 {
let [min, max] = network_interface.document_metadata().nonzero_bounding_box(layer);
let pivot = get_pivot(layer, network_interface).unwrap_or(DVec2::splat(0.5));
network_interface.document_metadata().transform_to_viewport(layer).transform_point2(min + (max - min) * pivot)
}

pub fn get_viewport_origin(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> DVec2 {
let [min, max] = network_interface.document_metadata().nonzero_bounding_box(layer);
let origin = get_origin(layer, network_interface).unwrap_or(DVec2::ZERO);
network_interface.document_metadata().transform_to_viewport(layer).transform_point2(min + (max - min) * origin)
}

/// Get the current gradient of a layer from the closest "Fill" node.
pub fn get_gradient(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option<Gradient> {
let fill_index = 1;
Expand Down
1 change: 1 addition & 0 deletions editor/src/messages/tool/common_functionality/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ pub mod shape_editor;
pub mod snapping;
pub mod transformation_cage;
pub mod utility_functions;
pub mod origin;
154 changes: 154 additions & 0 deletions editor/src/messages/tool/common_functionality/origin.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
//! Handler for the origin overlay visible on the selected layer(s) whilst using the Select tool which controls the center of rotation/scale and origin of the layer.

use super::graph_modification_utils;
use crate::consts::DOWEL_PIN_RADIUS;
use crate::messages::portfolio::document::overlays::utility_types::OverlayContext;
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
use crate::messages::prelude::*;
use glam::{DAffine2, DVec2};
use graphene_std::transform::ReferencePoint;
use std::collections::VecDeque;

#[derive(Clone, Debug)]
pub struct Origin {
/// Origin between (0,0) and (1,1)
normalized_origin: DVec2,
/// Transform to get from normalized origin to viewspace
transform_from_normalized: DAffine2,
/// The viewspace origin position (if applicable)
origin: Option<DVec2>,
/// The old origin position in the GUI, used to reduce refreshes of the document bar
old_origin_position: ReferencePoint,
/// Used to enable and disable the origin
active: bool,
}

impl Default for Origin {
fn default() -> Self {
Self {
normalized_origin: DVec2::ZERO,
transform_from_normalized: Default::default(),
origin: Default::default(),
old_origin_position: ReferencePoint::Center,
active: true,
}
}
}

impl Origin {
/// Calculates the transform that gets from normalized origin to viewspace.
fn get_layer_origin_transform(layer: LayerNodeIdentifier, document: &DocumentMessageHandler) -> DAffine2 {
let [min, max] = document.metadata().nonzero_bounding_box(layer);

let bounds_transform = DAffine2::from_translation(min) * DAffine2::from_scale(max - min);
let layer_transform = document.metadata().transform_to_viewport(layer);
layer_transform * bounds_transform
}

/// Recomputes the origin position and transform.
fn recalculate_origin(&mut self, document: &DocumentMessageHandler) {
if !self.active {
return;
}

let selected_nodes = document.network_interface.selected_nodes();
let mut layers = selected_nodes.selected_visible_and_unlocked_layers(&document.network_interface);
let Some(first) = layers.next() else {
// If no layers are selected then we revert things back to default
self.normalized_origin = DVec2::ZERO;
self.origin = None;
return;
};

// Add one because the first item is consumed above.
let selected_layers_count = layers.count() + 1;

// If just one layer is selected we can use its inner transform (as it accounts for rotation)
if selected_layers_count == 1 {
let normalized_origin = graph_modification_utils::get_origin(first, &document.network_interface).unwrap_or(DVec2::ZERO);
self.normalized_origin = normalized_origin;
self.transform_from_normalized = Self::get_layer_origin_transform(first, document);
self.origin = Some(self.transform_from_normalized.transform_point2(normalized_origin));
} else {
// If more than one layer is selected we use the AABB with the mean of the origins
let xy_summation = document
.network_interface
.selected_nodes()
.selected_visible_and_unlocked_layers(&document.network_interface)
.map(|layer| graph_modification_utils::get_viewport_origin(layer, &document.network_interface))
.reduce(|a, b| a + b)
.unwrap_or_default();

let origin = xy_summation / selected_layers_count as f64;
self.origin = Some(origin);
let [min, max] = document.selected_visible_and_unlock_layers_bounding_box_viewport().unwrap_or([DVec2::ZERO, DVec2::ONE]);
self.normalized_origin = (origin - min) / (max - min);

self.transform_from_normalized = DAffine2::from_translation(min) * DAffine2::from_scale(max - min);
}
}

pub fn update_origin(&mut self, document: &DocumentMessageHandler, overlay_context: &mut OverlayContext) {
if !overlay_context.visibility_settings.pivot() {
self.active = false;
return;
} else {
self.active = true;
}

self.recalculate_origin(document);
if let Some(origin) = self.origin {
overlay_context.draw_origin(origin);
}
}

/// Answers if the origin widget has changed (so we should refresh the tool bar at the top of the canvas).
pub fn should_refresh_origin_position(&mut self) -> bool {
if !self.active {
return false;
}

let new = self.to_origin_position();
let should_refresh = new != self.old_origin_position;
self.old_origin_position = new;
should_refresh
}

pub fn to_origin_position(&self) -> ReferencePoint {
self.normalized_origin.into()
}

/// Sets the viewport position of the origin for all selected layers.
pub fn set_viewport_position(&self, position: DVec2, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>) {
if !self.active {
return;
}

for layer in document.network_interface.selected_nodes().selected_visible_and_unlocked_layers(&document.network_interface) {
let transform = Self::get_layer_origin_transform(layer, document);
// Only update the origin when computed position is finite.
if transform.matrix2.determinant().abs() <= f64::EPSILON {
return;
};
let origin = transform.inverse().transform_point2(position);
responses.add(GraphOperationMessage::TransformSetOrigin { layer, origin });
}
}

/// Set the origin using the normalized transform that is set above.
pub fn set_normalized_position(&self, position: DVec2, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>) {
if !self.active {
return;
}

self.set_viewport_position(self.transform_from_normalized.transform_point2(position), document, responses);
}

/// Answers if the pointer is currently positioned over the origin.
pub fn is_over(&self, mouse: DVec2) -> bool {
if !self.active {
return false;
}
self.origin.filter(|&origin| mouse.distance_squared(origin) < (DOWEL_PIN_RADIUS).powi(2)).is_some()
}
}
Loading
Loading