mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-10-02 03:18:12 +08:00
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:
co-authored by
Keavon Chambers
parent
a1d93da532
commit
8a68683a93
@@ -11,6 +11,7 @@ use crate::messages::portfolio::document::utility_types::network_interface::Node
|
||||
use crate::messages::portfolio::document::utility_types::transformation::Axis;
|
||||
use crate::messages::preferences::SelectionMode;
|
||||
use crate::messages::tool::common_functionality::auto_panning::AutoPanning;
|
||||
use crate::messages::tool::common_functionality::pivot::{PivotGizmo, PivotGizmoType, PivotToolSource, pin_pivot_widget, pivot_gizmo_type_widget, pivot_reference_point_widget};
|
||||
use crate::messages::tool::common_functionality::shape_editor::{
|
||||
ClosestSegment, ManipulatorAngle, OpposingHandleLengths, SelectedLayerState, SelectedPointsInfo, SelectionChange, SelectionShape, SelectionShapeType, ShapeState,
|
||||
};
|
||||
@@ -18,6 +19,7 @@ use crate::messages::tool::common_functionality::snapping::{SnapCache, SnapCandi
|
||||
use crate::messages::tool::common_functionality::utility_functions::{calculate_segment_angle, find_two_param_best_approximate};
|
||||
use bezier_rs::{Bezier, BezierHandles, TValue};
|
||||
use graphene_std::renderer::Quad;
|
||||
use graphene_std::transform::ReferencePoint;
|
||||
use graphene_std::vector::{HandleExt, HandleId, NoHashBuilder, SegmentId, VectorData};
|
||||
use graphene_std::vector::{ManipulatorPointId, PointId, VectorModificationType};
|
||||
use std::vec;
|
||||
@@ -106,6 +108,9 @@ pub enum PathToolMessage {
|
||||
SelectedPointYChanged {
|
||||
new_y: f64,
|
||||
},
|
||||
SetPivot {
|
||||
position: ReferencePoint,
|
||||
},
|
||||
SwapSelectedHandles,
|
||||
UpdateOptions(PathOptionsUpdate),
|
||||
UpdateSelectedPointsStatus {
|
||||
@@ -141,6 +146,9 @@ pub enum PathOptionsUpdate {
|
||||
OverlayModeType(PathOverlayMode),
|
||||
PointEditingMode { enabled: bool },
|
||||
SegmentEditingMode { enabled: bool },
|
||||
PivotGizmoType(PivotGizmoType),
|
||||
TogglePivotGizmoType(bool),
|
||||
TogglePivotPinned,
|
||||
}
|
||||
|
||||
impl ToolMetadata for PathTool {
|
||||
@@ -255,6 +263,20 @@ impl LayoutHolder for PathTool {
|
||||
.selected_index(Some(self.options.path_overlay_mode as u32))
|
||||
.widget_holder();
|
||||
|
||||
let [_checkbox, _dropdown] = {
|
||||
let pivot_gizmo_type_widget = pivot_gizmo_type_widget(self.tool_data.pivot_gizmo.state, PivotToolSource::Path);
|
||||
[pivot_gizmo_type_widget[0].clone(), pivot_gizmo_type_widget[2].clone()]
|
||||
};
|
||||
|
||||
let has_something = !self.tool_data.saved_points_before_anchor_convert_smooth_sharp.is_empty();
|
||||
let _pivot_reference = pivot_reference_point_widget(
|
||||
has_something || !self.tool_data.pivot_gizmo.state.is_pivot(),
|
||||
self.tool_data.pivot_gizmo.pivot.to_pivot_position(),
|
||||
PivotToolSource::Path,
|
||||
);
|
||||
|
||||
let _pin_pivot = pin_pivot_widget(self.tool_data.pivot_gizmo.pin_active(), false, PivotToolSource::Path);
|
||||
|
||||
Layout::WidgetLayout(WidgetLayout::new(vec![LayoutGroup::Row {
|
||||
widgets: vec![
|
||||
x_location,
|
||||
@@ -268,8 +290,16 @@ impl LayoutHolder for PathTool {
|
||||
point_editing_mode,
|
||||
related_seperator.clone(),
|
||||
segment_editing_mode,
|
||||
unrelated_seperator,
|
||||
unrelated_seperator.clone(),
|
||||
path_overlay_mode_widget,
|
||||
unrelated_seperator.clone(),
|
||||
// checkbox.clone(),
|
||||
// related_seperator.clone(),
|
||||
// dropdown.clone(),
|
||||
// unrelated_seperator,
|
||||
// pivot_reference,
|
||||
// related_seperator.clone(),
|
||||
// pin_pivot,
|
||||
],
|
||||
}]))
|
||||
}
|
||||
@@ -293,6 +323,29 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for PathToo
|
||||
self.options.path_editing_mode.segment_editing_mode = enabled;
|
||||
responses.add(OverlaysMessage::Draw);
|
||||
}
|
||||
PathOptionsUpdate::PivotGizmoType(gizmo_type) => {
|
||||
if !self.tool_data.pivot_gizmo.state.disabled {
|
||||
self.tool_data.pivot_gizmo.state.gizmo_type = gizmo_type;
|
||||
responses.add(ToolMessage::UpdateHints);
|
||||
let pivot_gizmo = self.tool_data.pivot_gizmo();
|
||||
responses.add(TransformLayerMessage::SetPivotGizmo { pivot_gizmo });
|
||||
responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||
self.send_layout(responses, LayoutTarget::ToolOptions);
|
||||
}
|
||||
}
|
||||
PathOptionsUpdate::TogglePivotGizmoType(state) => {
|
||||
self.tool_data.pivot_gizmo.state.disabled = !state;
|
||||
responses.add(ToolMessage::UpdateHints);
|
||||
responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||
self.send_layout(responses, LayoutTarget::ToolOptions);
|
||||
}
|
||||
|
||||
PathOptionsUpdate::TogglePivotPinned => {
|
||||
self.tool_data.pivot_gizmo.pivot.pinned = !self.tool_data.pivot_gizmo.pivot.pinned;
|
||||
responses.add(ToolMessage::UpdateHints);
|
||||
responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||
self.send_layout(responses, LayoutTarget::ToolOptions);
|
||||
}
|
||||
},
|
||||
ToolMessage::Path(PathToolMessage::ClosePath) => {
|
||||
responses.add(DocumentMessage::AddTransaction);
|
||||
@@ -443,6 +496,8 @@ struct PathToolData {
|
||||
last_click_time: u64,
|
||||
dragging_state: DraggingState,
|
||||
angle: f64,
|
||||
pivot_gizmo: PivotGizmo,
|
||||
ordered_points: Vec<ManipulatorPointId>,
|
||||
opposite_handle_position: Option<DVec2>,
|
||||
last_clicked_point_was_selected: bool,
|
||||
last_clicked_segment_was_selected: bool,
|
||||
@@ -1315,6 +1370,16 @@ impl PathToolData {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn pivot_gizmo(&self) -> PivotGizmo {
|
||||
self.pivot_gizmo.clone()
|
||||
}
|
||||
|
||||
fn sync_history(&mut self, points: &[ManipulatorPointId]) {
|
||||
self.ordered_points.retain(|layer| points.contains(layer));
|
||||
self.ordered_points.extend(points.iter().find(|&layer| !self.ordered_points.contains(layer)));
|
||||
self.pivot_gizmo.point = self.ordered_points.last().copied()
|
||||
}
|
||||
}
|
||||
|
||||
impl Fsm for PathToolFsmState {
|
||||
@@ -1327,6 +1392,10 @@ impl Fsm for PathToolFsmState {
|
||||
update_dynamic_hints(self, responses, shape_editor, document, tool_data, tool_options);
|
||||
|
||||
let ToolMessage::Path(event) = event else { return self };
|
||||
|
||||
// TODO(mTvare6): Remove once gizmos are implemented for path_tool
|
||||
tool_data.pivot_gizmo.state.disabled = true;
|
||||
|
||||
match (self, event) {
|
||||
(_, PathToolMessage::SelectionChanged) => {
|
||||
// Set the newly targeted layers to visible
|
||||
@@ -1343,6 +1412,9 @@ impl Fsm for PathToolFsmState {
|
||||
shape_editor.update_selected_anchors_status(display_anchors);
|
||||
shape_editor.update_selected_handles_status(display_handles);
|
||||
|
||||
let new_points = shape_editor.selected_points().copied().collect::<Vec<_>>();
|
||||
tool_data.sync_history(&new_points);
|
||||
|
||||
self
|
||||
}
|
||||
(_, PathToolMessage::Overlays(mut overlay_context)) => {
|
||||
@@ -1710,14 +1782,8 @@ impl Fsm for PathToolFsmState {
|
||||
}
|
||||
(PathToolFsmState::Ready, PathToolMessage::PointerMove { delete_segment, .. }) => {
|
||||
tool_data.delete_segment_pressed = input.keyboard.get(delete_segment as usize);
|
||||
|
||||
if !tool_data.saved_points_before_anchor_convert_smooth_sharp.is_empty() {
|
||||
tool_data.saved_points_before_anchor_convert_smooth_sharp.clear();
|
||||
}
|
||||
|
||||
if tool_data.adjacent_anchor_offset.is_some() {
|
||||
tool_data.adjacent_anchor_offset = None;
|
||||
}
|
||||
tool_data.saved_points_before_anchor_convert_smooth_sharp.clear();
|
||||
tool_data.adjacent_anchor_offset = None;
|
||||
tool_data.stored_selection = None;
|
||||
|
||||
responses.add(OverlaysMessage::Draw);
|
||||
@@ -2208,6 +2274,18 @@ impl Fsm for PathToolFsmState {
|
||||
responses.add(DocumentMessage::EndTransaction);
|
||||
PathToolFsmState::Ready
|
||||
}
|
||||
(_, PathToolMessage::SetPivot { position }) => {
|
||||
responses.add(DocumentMessage::StartTransaction);
|
||||
|
||||
tool_data.pivot_gizmo.pivot.last_non_none_reference_point = position;
|
||||
let position: Option<DVec2> = position.into();
|
||||
tool_data.pivot_gizmo.pivot.set_normalized_position(position.unwrap());
|
||||
let pivot_gizmo = tool_data.pivot_gizmo();
|
||||
responses.add(TransformLayerMessage::SetPivotGizmo { pivot_gizmo });
|
||||
responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||
|
||||
self
|
||||
}
|
||||
(_, _) => PathToolFsmState::Ready,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,9 +12,10 @@ use crate::messages::portfolio::document::utility_types::nodes::SelectedNodes;
|
||||
use crate::messages::preferences::SelectionMode;
|
||||
use crate::messages::tool::common_functionality::auto_panning::AutoPanning;
|
||||
use crate::messages::tool::common_functionality::compass_rose::{Axis, CompassRose};
|
||||
use crate::messages::tool::common_functionality::graph_modification_utils;
|
||||
use crate::messages::tool::common_functionality::graph_modification_utils::is_layer_fed_by_node_of_name;
|
||||
use crate::messages::tool::common_functionality::measure;
|
||||
use crate::messages::tool::common_functionality::pivot::Pivot;
|
||||
use crate::messages::tool::common_functionality::pivot::{PivotGizmo, PivotGizmoType, PivotToolSource, pin_pivot_widget, pivot_gizmo_type_widget, pivot_reference_point_widget};
|
||||
use crate::messages::tool::common_functionality::shape_editor::SelectionShapeType;
|
||||
use crate::messages::tool::common_functionality::snapping::{self, SnapCandidatePoint, SnapData, SnapManager};
|
||||
use crate::messages::tool::common_functionality::transformation_cage::*;
|
||||
@@ -43,6 +44,9 @@ pub struct SelectOptions {
|
||||
#[derive(PartialEq, Eq, Clone, Debug, Hash, serde::Serialize, serde::Deserialize, specta::Type)]
|
||||
pub enum SelectOptionsUpdate {
|
||||
NestedSelectionBehavior(NestedSelectionBehavior),
|
||||
PivotGizmoType(PivotGizmoType),
|
||||
TogglePivotGizmoType(bool),
|
||||
TogglePivotPinned,
|
||||
}
|
||||
|
||||
#[derive(Default, PartialEq, Eq, Clone, Copy, Debug, Hash, serde::Serialize, serde::Deserialize, specta::Type)]
|
||||
@@ -95,6 +99,14 @@ pub enum SelectToolMessage {
|
||||
SetPivot {
|
||||
position: ReferencePoint,
|
||||
},
|
||||
SyncHistory,
|
||||
ShiftSelectedNodes {
|
||||
offset: DVec2,
|
||||
},
|
||||
PivotShift {
|
||||
offset: Option<DVec2>,
|
||||
flush: bool,
|
||||
},
|
||||
}
|
||||
|
||||
impl ToolMetadata for SelectTool {
|
||||
@@ -122,14 +134,12 @@ impl SelectTool {
|
||||
|
||||
DropdownInput::new(vec![layer_selection_behavior_entries])
|
||||
.selected_index(Some((self.tool_data.nested_selection_behavior == NestedSelectionBehavior::Deepest) as u32))
|
||||
.tooltip("Choose if clicking nested layers directly selects the deepest, or selects the shallowest and deepens by double clicking")
|
||||
.widget_holder()
|
||||
}
|
||||
|
||||
fn pivot_reference_point_widget(&self, disabled: bool) -> WidgetHolder {
|
||||
ReferencePointInput::new(self.tool_data.pivot.to_pivot_position())
|
||||
.on_update(|pivot_input: &ReferencePointInput| SelectToolMessage::SetPivot { position: pivot_input.value }.into())
|
||||
.disabled(disabled)
|
||||
.tooltip(
|
||||
"Selection Mode\n\
|
||||
\n\
|
||||
Shallow Select: clicks initially select the least-nested layers and double clicks drill deeper into the folder hierarchy.\n\
|
||||
Deep Select: clicks directly select the most-nested layers in the folder hierarchy.",
|
||||
)
|
||||
.widget_holder()
|
||||
}
|
||||
|
||||
@@ -178,7 +188,7 @@ impl SelectTool {
|
||||
|
||||
fn boolean_widgets(&self, selected_count: usize) -> impl Iterator<Item = WidgetHolder> + use<> {
|
||||
let list = <BooleanOperation as graphene_std::registry::ChoiceTypeStatic>::list();
|
||||
list.into_iter().map(|i| i.into_iter()).flatten().map(move |(operation, info)| {
|
||||
list.iter().flat_map(|i| i.iter()).map(move |(operation, info)| {
|
||||
let mut tooltip = info.label.to_string();
|
||||
if let Some(doc) = info.docstring.as_deref() {
|
||||
tooltip.push_str("\n\n");
|
||||
@@ -203,9 +213,29 @@ impl LayoutHolder for SelectTool {
|
||||
// Select mode (Deep/Shallow)
|
||||
widgets.push(self.deep_selection_widget());
|
||||
|
||||
// Pivot
|
||||
// Pivot gizmo type (checkbox + dropdown for pivot/origin)
|
||||
widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder());
|
||||
widgets.push(self.pivot_reference_point_widget(self.tool_data.selected_layers_count == 0));
|
||||
widgets.extend(pivot_gizmo_type_widget(self.tool_data.pivot_gizmo.state, PivotToolSource::Select));
|
||||
|
||||
if self.tool_data.pivot_gizmo.state.is_pivot_type() {
|
||||
// Nine-position reference point widget
|
||||
widgets.push(Separator::new(SeparatorType::Related).widget_holder());
|
||||
widgets.push(pivot_reference_point_widget(
|
||||
self.tool_data.selected_layers_count == 0 || !self.tool_data.pivot_gizmo.state.is_pivot(),
|
||||
self.tool_data.pivot_gizmo.pivot.to_pivot_position(),
|
||||
PivotToolSource::Select,
|
||||
));
|
||||
|
||||
// Pivot pin button
|
||||
widgets.push(Separator::new(SeparatorType::Related).widget_holder());
|
||||
|
||||
let pin_active = self.tool_data.pivot_gizmo.pin_active();
|
||||
let pin_enabled = self.tool_data.pivot_gizmo.pivot.old_pivot_position == ReferencePoint::None && !self.tool_data.pivot_gizmo.state.disabled;
|
||||
|
||||
if pin_active || pin_enabled {
|
||||
widgets.push(pin_pivot_widget(pin_active, pin_enabled, PivotToolSource::Select));
|
||||
}
|
||||
}
|
||||
|
||||
// Align
|
||||
let disabled = self.tool_data.selected_layers_count < 2;
|
||||
@@ -244,14 +274,43 @@ impl LayoutHolder for SelectTool {
|
||||
|
||||
impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for SelectTool {
|
||||
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) {
|
||||
if let ToolMessage::Select(SelectToolMessage::SelectOptions(SelectOptionsUpdate::NestedSelectionBehavior(nested_selection_behavior))) = message {
|
||||
self.tool_data.nested_selection_behavior = nested_selection_behavior;
|
||||
responses.add(ToolMessage::UpdateHints);
|
||||
let mut redraw_reference_pivot = false;
|
||||
|
||||
if let ToolMessage::Select(SelectToolMessage::SelectOptions(ref option_update)) = message {
|
||||
match option_update {
|
||||
SelectOptionsUpdate::NestedSelectionBehavior(nested_selection_behavior) => {
|
||||
self.tool_data.nested_selection_behavior = *nested_selection_behavior;
|
||||
responses.add(ToolMessage::UpdateHints);
|
||||
}
|
||||
SelectOptionsUpdate::PivotGizmoType(gizmo_type) => {
|
||||
if !self.tool_data.pivot_gizmo.state.disabled {
|
||||
self.tool_data.pivot_gizmo.state.gizmo_type = *gizmo_type;
|
||||
responses.add(ToolMessage::UpdateHints);
|
||||
let pivot_gizmo = self.tool_data.pivot_gizmo();
|
||||
responses.add(TransformLayerMessage::SetPivotGizmo { pivot_gizmo });
|
||||
responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||
redraw_reference_pivot = true;
|
||||
}
|
||||
}
|
||||
SelectOptionsUpdate::TogglePivotGizmoType(state) => {
|
||||
self.tool_data.pivot_gizmo.state.disabled = !state;
|
||||
responses.add(ToolMessage::UpdateHints);
|
||||
responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||
redraw_reference_pivot = true;
|
||||
}
|
||||
|
||||
SelectOptionsUpdate::TogglePivotPinned => {
|
||||
self.tool_data.pivot_gizmo.pivot.pinned = !self.tool_data.pivot_gizmo.pivot.pinned;
|
||||
responses.add(ToolMessage::UpdateHints);
|
||||
responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||
redraw_reference_pivot = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.fsm_state.process_event(message, &mut self.tool_data, tool_data, &(), responses, false);
|
||||
|
||||
if self.tool_data.pivot.should_refresh_pivot_position() || self.tool_data.selected_layers_changed {
|
||||
if self.tool_data.pivot_gizmo.pivot.should_refresh_pivot_position() || self.tool_data.selected_layers_changed || redraw_reference_pivot {
|
||||
// Send the layout containing the updated pivot position (a bit ugly to do it here not in the fsm but that doesn't have SelectTool)
|
||||
self.send_layout(responses, LayoutTarget::ToolOptions);
|
||||
self.tool_data.selected_layers_changed = false;
|
||||
@@ -323,7 +382,8 @@ struct SelectToolData {
|
||||
drag_current: ViewportPosition,
|
||||
lasso_polygon: Vec<ViewportPosition>,
|
||||
selection_mode: Option<SelectionMode>,
|
||||
layers_dragging: Vec<LayerNodeIdentifier>,
|
||||
layers_dragging: Vec<LayerNodeIdentifier>, // Unordered, often used as temporary buffer
|
||||
ordered_layers: Vec<LayerNodeIdentifier>, // Ordered list of layers
|
||||
layer_selected_on_start: Option<LayerNodeIdentifier>,
|
||||
select_single_layer: Option<LayerNodeIdentifier>,
|
||||
axis_align: bool,
|
||||
@@ -331,7 +391,9 @@ struct SelectToolData {
|
||||
bounding_box_manager: Option<BoundingBoxManager>,
|
||||
snap_manager: SnapManager,
|
||||
cursor: MouseCursorIcon,
|
||||
pivot: Pivot,
|
||||
pivot_gizmo: PivotGizmo,
|
||||
pivot_gizmo_start: Option<DVec2>,
|
||||
pivot_gizmo_shift: Option<DVec2>,
|
||||
compass_rose: CompassRose,
|
||||
line_center: DVec2,
|
||||
skew_edge: EdgeBool,
|
||||
@@ -497,6 +559,24 @@ impl SelectToolData {
|
||||
responses.add(NodeGraphMessage::SendGraph);
|
||||
self.layers_dragging = original;
|
||||
}
|
||||
|
||||
fn state_from_pivot_gizmo(&self, mouse: DVec2) -> Option<SelectToolFsmState> {
|
||||
match self.pivot_gizmo.state.gizmo_type {
|
||||
PivotGizmoType::Pivot if self.pivot_gizmo.state.is_pivot() => self.pivot_gizmo.pivot.is_over(mouse).then_some(SelectToolFsmState::DraggingPivot),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn pivot_gizmo(&self) -> PivotGizmo {
|
||||
self.pivot_gizmo.clone()
|
||||
}
|
||||
|
||||
fn sync_history(&mut self, document: &DocumentMessageHandler) {
|
||||
let layers: Vec<_> = document.network_interface.selected_nodes().selected_visible_and_unlocked_layers(&document.network_interface).collect();
|
||||
self.ordered_layers.retain(|layer| layers.contains(layer));
|
||||
self.ordered_layers.extend(layers.iter().find(|&layer| !self.ordered_layers.contains(layer)));
|
||||
self.pivot_gizmo.layer = self.ordered_layers.last().copied()
|
||||
}
|
||||
}
|
||||
|
||||
impl Fsm for SelectToolFsmState {
|
||||
@@ -710,8 +790,63 @@ impl Fsm for SelectToolFsmState {
|
||||
.flatten()
|
||||
});
|
||||
|
||||
// Update pivot
|
||||
tool_data.pivot.update_pivot(document, &mut overlay_context, Some((angle,)));
|
||||
let mut active_origin = None;
|
||||
let mut origin_angle = 0.;
|
||||
if overlay_context.visibility_settings.origin() && !tool_data.pivot_gizmo.state.is_pivot_type() {
|
||||
let get_angle = |layer: LayerNodeIdentifier| -> f64 {
|
||||
let quad = Quad::from_box([DVec2::ZERO, DVec2::ONE]);
|
||||
let bounds = document.metadata().transform_to_viewport_with_first_transform_node_if_group(layer, &document.network_interface) * quad;
|
||||
(bounds.top_left() - bounds.top_right()).to_angle()
|
||||
};
|
||||
if tool_data.pivot_gizmo.state.gizmo_type == PivotGizmoType::Average {
|
||||
let mut count = 0_usize;
|
||||
|
||||
let sum: f64 = document
|
||||
.network_interface
|
||||
.selected_nodes()
|
||||
.selected_visible_and_unlocked_layers(&document.network_interface)
|
||||
.map(get_angle)
|
||||
.inspect(|_| count += 1)
|
||||
.sum();
|
||||
if count > 0 {
|
||||
origin_angle = sum / count as f64;
|
||||
}
|
||||
} else if tool_data.pivot_gizmo.state.gizmo_type == PivotGizmoType::Active {
|
||||
origin_angle = document
|
||||
.network_interface
|
||||
.selected_nodes()
|
||||
.selected_visible_and_unlocked_layers(&document.network_interface)
|
||||
.find(|&layer| Some(layer) == tool_data.pivot_gizmo.layer)
|
||||
.iter()
|
||||
.map(|&layer| get_angle(layer))
|
||||
.sum();
|
||||
}
|
||||
|
||||
for layer in document.network_interface.selected_nodes().selected_visible_and_unlocked_layers(&document.network_interface) {
|
||||
let origin = graph_modification_utils::get_viewport_origin(layer, &document.network_interface);
|
||||
if Some(layer) == tool_data.pivot_gizmo.layer {
|
||||
active_origin = Some(origin);
|
||||
continue;
|
||||
}
|
||||
overlay_context.dowel_pin(origin, origin_angle, None);
|
||||
}
|
||||
}
|
||||
if let Some(origin) = active_origin {
|
||||
overlay_context.dowel_pin(origin, origin_angle, Some(COLOR_OVERLAY_YELLOW));
|
||||
}
|
||||
|
||||
let has_layers = document.network_interface.selected_nodes().has_selected_nodes();
|
||||
let draw_pivot = tool_data.pivot_gizmo.state.is_pivot() && overlay_context.visibility_settings.pivot() && has_layers;
|
||||
tool_data.pivot_gizmo.pivot.recalculate_pivot(document);
|
||||
let pivot = draw_pivot.then_some(tool_data.pivot_gizmo.pivot.pivot).flatten();
|
||||
if let Some(pivot) = pivot {
|
||||
let offset = tool_data
|
||||
.pivot_gizmo_start
|
||||
.map(|offset| tool_data.pivot_gizmo.pivot_disconnected().then_some(tool_data.drag_current - offset).unwrap_or_default())
|
||||
.unwrap_or_default();
|
||||
let shift = tool_data.pivot_gizmo_shift.unwrap_or_default();
|
||||
overlay_context.pivot(pivot + offset + shift, angle);
|
||||
}
|
||||
|
||||
// Update compass rose
|
||||
if overlay_context.visibility_settings.compass_rose() {
|
||||
@@ -837,6 +972,7 @@ impl Fsm for SelectToolFsmState {
|
||||
(SelectionShapeType::Lasso, _) => overlay_context.polygon(polygon, None, fill_color),
|
||||
}
|
||||
}
|
||||
|
||||
self
|
||||
}
|
||||
(_, SelectToolMessage::EditLayer) => {
|
||||
@@ -868,7 +1004,8 @@ impl Fsm for SelectToolFsmState {
|
||||
let intersection_list = document.click_list(input).collect::<Vec<_>>();
|
||||
let intersection = document.find_deepest(&intersection_list);
|
||||
|
||||
let (resize, rotate, skew) = transforming_transform_cage(document, &mut tool_data.bounding_box_manager, input, responses, &mut tool_data.layers_dragging);
|
||||
let position = tool_data.pivot_gizmo().position(document);
|
||||
let (resize, rotate, skew) = transforming_transform_cage(document, &mut tool_data.bounding_box_manager, input, responses, &mut tool_data.layers_dragging, Some(position));
|
||||
|
||||
// If the user is dragging the bounding box bounds, go into ResizingBounds mode.
|
||||
// If the user is dragging the rotate trigger, go into RotatingBounds mode.
|
||||
@@ -883,20 +1020,17 @@ impl Fsm for SelectToolFsmState {
|
||||
let angle = bounds.map_or(0., |quad| (quad.top_left() - quad.top_right()).to_angle());
|
||||
let mouse_position = input.mouse.position;
|
||||
let compass_rose_state = tool_data.compass_rose.compass_rose_state(mouse_position, angle);
|
||||
let is_over_pivot = tool_data.pivot.is_over(mouse_position);
|
||||
|
||||
let show_compass = bounds.is_some_and(|quad| quad.all_sides_at_least_width(COMPASS_ROSE_HOVER_RING_DIAMETER) && quad.contains(mouse_position));
|
||||
let can_grab_compass_rose = compass_rose_state.can_grab() && (show_compass || bounds.is_none());
|
||||
|
||||
let state = if is_over_pivot
|
||||
// Dragging the pivot
|
||||
{
|
||||
let state = if let Some(state) = tool_data.state_from_pivot_gizmo(input.mouse.position) {
|
||||
responses.add(DocumentMessage::StartTransaction);
|
||||
|
||||
// tool_data.snap_manager.start_snap(document, input, document.bounding_boxes(), true, true);
|
||||
// tool_data.snap_manager.add_all_document_handles(document, input, &[], &[], &[]);
|
||||
|
||||
SelectToolFsmState::DraggingPivot
|
||||
state
|
||||
}
|
||||
// Dragging one (or two, forming a corner) of the transform cage bounding box edges
|
||||
else if resize {
|
||||
@@ -917,12 +1051,14 @@ impl Fsm for SelectToolFsmState {
|
||||
}
|
||||
|
||||
tool_data.layers_dragging = selected;
|
||||
|
||||
tool_data.get_snap_candidates(document, input);
|
||||
let (axis, using_compass) = {
|
||||
let axis_state = compass_rose_state.axis_type().filter(|_| can_grab_compass_rose);
|
||||
(axis_state.unwrap_or_default(), axis_state.is_some())
|
||||
};
|
||||
|
||||
tool_data.pivot_gizmo_start = Some(tool_data.drag_current);
|
||||
|
||||
SelectToolFsmState::Dragging {
|
||||
axis,
|
||||
using_compass,
|
||||
@@ -941,6 +1077,12 @@ impl Fsm for SelectToolFsmState {
|
||||
let extend = input.keyboard.key(extend_selection);
|
||||
if !extend && !input.keyboard.key(remove_from_selection) {
|
||||
responses.add(DocumentMessage::DeselectAllLayers);
|
||||
|
||||
if !tool_data.pivot_gizmo.pivot.pinned {
|
||||
let position = tool_data.pivot_gizmo.pivot.last_non_none_reference_point;
|
||||
responses.add(SelectToolMessage::SetPivot { position });
|
||||
}
|
||||
|
||||
tool_data.layers_dragging.clear();
|
||||
}
|
||||
|
||||
@@ -955,6 +1097,9 @@ impl Fsm for SelectToolFsmState {
|
||||
tool_data.get_snap_candidates(document, input);
|
||||
|
||||
responses.add(DocumentMessage::StartTransaction);
|
||||
|
||||
tool_data.pivot_gizmo_start = Some(tool_data.drag_current);
|
||||
|
||||
SelectToolFsmState::Dragging {
|
||||
axis: Axis::None,
|
||||
using_compass: false,
|
||||
@@ -1098,7 +1243,10 @@ impl Fsm for SelectToolFsmState {
|
||||
(SelectToolFsmState::DraggingPivot, SelectToolMessage::PointerMove(modifier_keys)) => {
|
||||
let mouse_position = input.mouse.position;
|
||||
let snapped_mouse_position = mouse_position;
|
||||
tool_data.pivot.set_viewport_position(snapped_mouse_position, document, responses);
|
||||
|
||||
tool_data.pivot_gizmo.pivot.set_viewport_position(snapped_mouse_position);
|
||||
|
||||
responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||
|
||||
// Auto-panning
|
||||
let messages = [
|
||||
@@ -1143,7 +1291,7 @@ impl Fsm for SelectToolFsmState {
|
||||
.map_or(MouseCursorIcon::Default, |bounds| bounds.get_cursor(input, true, dragging_bounds, Some(tool_data.skew_edge)));
|
||||
|
||||
// Dragging the pivot overrules the other operations
|
||||
if tool_data.pivot.is_over(input.mouse.position) {
|
||||
if tool_data.state_from_pivot_gizmo(input.mouse.position).is_some() {
|
||||
cursor = MouseCursorIcon::Move;
|
||||
}
|
||||
|
||||
@@ -1283,20 +1431,32 @@ impl Fsm for SelectToolFsmState {
|
||||
tool_data.snap_manager.cleanup(responses);
|
||||
tool_data.select_single_layer = None;
|
||||
|
||||
if let Some(start) = tool_data.pivot_gizmo_start {
|
||||
let offset = tool_data.pivot_gizmo.pivot_disconnected().then_some(tool_data.drag_current - start).unwrap_or_default();
|
||||
if let Some(v) = tool_data.pivot_gizmo.pivot.pivot.as_mut() {
|
||||
*v += offset;
|
||||
}
|
||||
}
|
||||
tool_data.pivot_gizmo_start = None;
|
||||
|
||||
let pivot_gizmo = tool_data.pivot_gizmo();
|
||||
responses.add(TransformLayerMessage::SetPivotGizmo { pivot_gizmo });
|
||||
|
||||
let selection = tool_data.nested_selection_behavior;
|
||||
SelectToolFsmState::Ready { selection }
|
||||
}
|
||||
(
|
||||
SelectToolFsmState::ResizingBounds
|
||||
| SelectToolFsmState::SkewingBounds { .. }
|
||||
| SelectToolFsmState::RotatingBounds
|
||||
| SelectToolFsmState::Dragging { .. }
|
||||
| SelectToolFsmState::DraggingPivot,
|
||||
SelectToolFsmState::ResizingBounds | SelectToolFsmState::SkewingBounds { .. } | SelectToolFsmState::RotatingBounds | SelectToolFsmState::DraggingPivot,
|
||||
SelectToolMessage::DragStop { .. } | SelectToolMessage::Enter,
|
||||
) => {
|
||||
let drag_too_small = input.mouse.position.distance(tool_data.drag_start) < 10. * f64::EPSILON;
|
||||
let response = if drag_too_small { DocumentMessage::AbortTransaction } else { DocumentMessage::EndTransaction };
|
||||
|
||||
let pivot_gizmo = tool_data.pivot_gizmo();
|
||||
responses.add(TransformLayerMessage::SetPivotGizmo { pivot_gizmo });
|
||||
|
||||
responses.add(response);
|
||||
|
||||
tool_data.axis_align = false;
|
||||
tool_data.snap_manager.cleanup(responses);
|
||||
|
||||
@@ -1432,8 +1592,48 @@ impl Fsm for SelectToolFsmState {
|
||||
(_, SelectToolMessage::SetPivot { position }) => {
|
||||
responses.add(DocumentMessage::StartTransaction);
|
||||
|
||||
tool_data.pivot_gizmo.pivot.last_non_none_reference_point = position;
|
||||
tool_data.pivot_gizmo.pivot.pinned = false;
|
||||
|
||||
let pos: Option<DVec2> = position.into();
|
||||
tool_data.pivot.set_normalized_position(pos.unwrap(), document, responses);
|
||||
|
||||
tool_data.pivot_gizmo.pivot.set_normalized_position(pos.unwrap());
|
||||
|
||||
let pivot_gizmo = tool_data.pivot_gizmo();
|
||||
responses.add(TransformLayerMessage::SetPivotGizmo { pivot_gizmo });
|
||||
|
||||
responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||
|
||||
self
|
||||
}
|
||||
(_, SelectToolMessage::SyncHistory) => {
|
||||
tool_data.sync_history(document);
|
||||
|
||||
self
|
||||
}
|
||||
(_, SelectToolMessage::ShiftSelectedNodes { offset }) => {
|
||||
let offset = document.metadata().document_to_viewport.transform_vector2(offset);
|
||||
if tool_data.pivot_gizmo.pivot_disconnected() {
|
||||
if let Some(v) = tool_data.pivot_gizmo.pivot.pivot.as_mut() {
|
||||
*v += offset;
|
||||
}
|
||||
|
||||
let pivot_gizmo = tool_data.pivot_gizmo();
|
||||
responses.add(TransformLayerMessage::SetPivotGizmo { pivot_gizmo });
|
||||
}
|
||||
|
||||
self
|
||||
}
|
||||
(_, SelectToolMessage::PivotShift { offset, flush }) => {
|
||||
if flush {
|
||||
tool_data.pivot_gizmo.pivot.pivot.as_mut().map(|v| *v += tool_data.pivot_gizmo_shift.take().unwrap_or_default());
|
||||
let pivot_gizmo = tool_data.pivot_gizmo();
|
||||
responses.add(TransformLayerMessage::SetPivotGizmo { pivot_gizmo });
|
||||
return self;
|
||||
}
|
||||
if tool_data.pivot_gizmo.pivot_disconnected() {
|
||||
tool_data.pivot_gizmo_shift = offset;
|
||||
}
|
||||
|
||||
self
|
||||
}
|
||||
@@ -1658,6 +1858,7 @@ fn drag_deepest_manipulation(responses: &mut VecDeque<Message>, selected: Vec<La
|
||||
.next()
|
||||
.expect("ROOT_PARENT should have a layer child when clicking"),
|
||||
);
|
||||
|
||||
if !remove {
|
||||
tool_data.layers_dragging.extend(vec![layer]);
|
||||
} else {
|
||||
|
||||
@@ -557,7 +557,7 @@ impl Fsm for ShapeToolFsmState {
|
||||
}
|
||||
}
|
||||
|
||||
let (resize, rotate, skew) = transforming_transform_cage(document, &mut tool_data.bounding_box_manager, input, responses, &mut tool_data.layers_dragging);
|
||||
let (resize, rotate, skew) = transforming_transform_cage(document, &mut tool_data.bounding_box_manager, input, responses, &mut tool_data.layers_dragging, None);
|
||||
|
||||
if !input.keyboard.key(Key::Control) {
|
||||
match (resize, rotate, skew) {
|
||||
|
||||
@@ -9,7 +9,6 @@ use crate::messages::portfolio::document::utility_types::network_interface::Inpu
|
||||
use crate::messages::tool::common_functionality::auto_panning::AutoPanning;
|
||||
use crate::messages::tool::common_functionality::color_selector::{ToolColorOptions, ToolColorType};
|
||||
use crate::messages::tool::common_functionality::graph_modification_utils::{self, is_layer_fed_by_node_of_name};
|
||||
use crate::messages::tool::common_functionality::pivot::Pivot;
|
||||
use crate::messages::tool::common_functionality::resize::Resize;
|
||||
use crate::messages::tool::common_functionality::snapping::{self, SnapCandidatePoint, SnapData};
|
||||
use crate::messages::tool::common_functionality::transformation_cage::*;
|
||||
@@ -283,7 +282,6 @@ struct TextToolData {
|
||||
// Since the overlays must be drawn without knowledge of the inputs
|
||||
cached_resize_bounds: [DVec2; 2],
|
||||
bounding_box_manager: Option<BoundingBoxManager>,
|
||||
pivot: Pivot,
|
||||
snap_candidates: Vec<SnapCandidatePoint>,
|
||||
// TODO: Handle multiple layers in the future
|
||||
layer_dragging: Option<ResizingLayer>,
|
||||
@@ -526,7 +524,6 @@ impl Fsm for TextToolFsmState {
|
||||
}
|
||||
|
||||
bounding_box_manager.render_overlays(&mut overlay_context, false);
|
||||
tool_data.pivot.update_pivot(document, &mut overlay_context, None);
|
||||
}
|
||||
} else {
|
||||
tool_data.bounding_box_manager.take();
|
||||
|
||||
Reference in New Issue
Block a user