Extract drag state to new file

This commit is contained in:
hypercube
2025-08-07 21:53:00 +01:00
parent eb255073e6
commit ce969b172b
5 changed files with 120 additions and 94 deletions

View File

@@ -451,6 +451,19 @@ impl LayerNodeIdentifier {
}
}
impl PartialEq<NodeId> for LayerNodeIdentifier {
fn eq(&self, other: &NodeId) -> bool {
self.to_node() == *other
}
}
// Implement <BookFormat> == <Book> comparisons
impl PartialEq<LayerNodeIdentifier> for NodeId {
fn eq(&self, other: &LayerNodeIdentifier) -> bool {
other.to_node() == *self
}
}
// ========
// AxisIter
// ========

View File

@@ -1317,15 +1317,11 @@ impl NodeNetworkInterface {
/// Layers excluding ones that are children of other layers in the list in layer tree order.
// TODO: Cache this
// Now allocation free!
pub fn shallowest_unique_layers(&self, network_path: &[NodeId]) -> ShallowestSelectionIter<'_> {
pub fn shallowest_unique_layers(&self, network_path: &[NodeId]) -> ShallowestSelectionIter<'_, NodeId> {
// Avoids the clone and filtering from from the selected_nodes_in_nested_network.
let metadata = self.network_metadata(network_path);
let selection = metadata.and_then(|metadata| metadata.persistent_metadata.selection_undo_history.back());
ShallowestSelectionIter {
selection: selection.map_or([].as_slice(), |selection| selection.0.as_slice()),
next: Some(LayerNodeIdentifier::ROOT_PARENT),
metadata: self.document_metadata(),
}
ShallowestSelectionIter::new(self.document_metadata(), selection.map_or([].as_slice(), |selection| selection.0.as_slice()))
}
/// Ancestor that is shared by all layers and that is deepest (more nested). Default may be the root. Skips selected non-folder, non-artboard layers
@@ -6997,13 +6993,23 @@ pub enum TransactionStatus {
/// Iterate through the shallowest selected layers without allocating
#[derive(Clone)]
pub struct ShallowestSelectionIter<'a> {
pub struct ShallowestSelectionIter<'a, T: PartialEq<LayerNodeIdentifier>> {
next: Option<LayerNodeIdentifier>,
selection: &'a [NodeId], // TODO: should be HashSet to avoid duplicates.
selection: &'a [T], // TODO: should be HashSet to avoid duplicates.
metadata: &'a DocumentMetadata,
}
impl Iterator for ShallowestSelectionIter<'_> {
impl<'a, T: PartialEq<LayerNodeIdentifier>> ShallowestSelectionIter<'a, T> {
pub fn new(metadata: &'a DocumentMetadata, selection: &'a [T]) -> Self {
ShallowestSelectionIter {
selection,
next: Some(LayerNodeIdentifier::ROOT_PARENT),
metadata,
}
}
}
impl<T: PartialEq<LayerNodeIdentifier>> Iterator for ShallowestSelectionIter<'_, T> {
type Item = LayerNodeIdentifier;
fn next(&mut self) -> Option<Self::Item> {
@@ -7012,7 +7018,7 @@ impl Iterator for ShallowestSelectionIter<'_> {
let below_in_tree = || layer_node.ancestors(self.metadata).find_map(|ancestor| ancestor.next_sibling(self.metadata));
// If the current layer is selected, return it.
if layer_node != LayerNodeIdentifier::ROOT_PARENT && self.selection.contains(&layer_node.to_node()) {
if layer_node != LayerNodeIdentifier::ROOT_PARENT && self.selection.iter().any(|selection| *selection == layer_node) {
self.next = below_in_tree(); // Go straight to below and don't look at children
return Some(layer_node);
}

View File

@@ -6,7 +6,7 @@ use crate::messages::input_mapper::utility_types::input_mouse::ViewportPosition;
use crate::messages::portfolio::document::graph_operation::utility_types::TransformIn;
use crate::messages::portfolio::document::overlays::utility_types::OverlayContext;
use crate::messages::portfolio::document::utility_types::document_metadata::{DocumentMetadata, LayerNodeIdentifier};
use crate::messages::portfolio::document::utility_types::network_interface::NodeNetworkInterface;
use crate::messages::portfolio::document::utility_types::network_interface::{NodeNetworkInterface, ShallowestSelectionIter};
use crate::messages::preferences::SelectionMode;
use crate::messages::tool::common_functionality::auto_panning::AutoPanning;
use crate::messages::tool::common_functionality::compass_rose::{Axis, CompassRose};
@@ -25,8 +25,10 @@ use graphene_std::renderer::Quad;
use graphene_std::renderer::Rect;
use graphene_std::transform::ReferencePoint;
mod drag_state;
mod duplicate;
pub mod options;
use drag_state::*;
#[derive(Default, ExtractField)]
pub struct SelectTool {
@@ -162,85 +164,6 @@ impl Default for SelectToolFsmState {
}
}
/// Represents the current drag in progress
#[derive(Clone, Debug, Default)]
struct DragState {
pub start_document: DVec2,
pub current_document: DVec2,
/// Selection mode is set when the drag exceeds a certain distance. Once resolved, the selection mode cannot change.
resolved_selection_mode: Option<SelectionMode>,
}
impl DragState {
pub fn new(input: &InputPreprocessorMessageHandler, metadata: &DocumentMetadata) -> Self {
let document_mouse = metadata.document_to_viewport.inverse().transform_point2(input.mouse.position);
Self {
start_document: document_mouse,
current_document: document_mouse,
resolved_selection_mode: None,
}
}
pub fn set_current(&mut self, input: &InputPreprocessorMessageHandler, metadata: &DocumentMetadata) {
self.current_document = metadata.document_to_viewport.inverse().transform_point2(input.mouse.position);
}
pub fn start_viewport(&self, metadata: &DocumentMetadata) -> DVec2 {
metadata.document_to_viewport.transform_point2(self.start_document)
}
pub fn current_viewport(&self, metadata: &DocumentMetadata) -> DVec2 {
metadata.document_to_viewport.transform_point2(self.current_document)
}
pub fn start_current_viewport(&self, metadata: &DocumentMetadata) -> [DVec2; 2] {
[self.start_viewport(metadata), self.current_viewport(metadata)]
}
pub fn total_drag_delta_document(&self) -> DVec2 {
self.current_document - self.start_document
}
pub fn total_drag_delta_viewport(&self, metadata: &DocumentMetadata) -> DVec2 {
metadata.document_to_viewport.transform_vector2(self.total_drag_delta_document())
}
pub fn inverse_drag_delta_viewport(&self, metadata: &DocumentMetadata) -> DVec2 {
-self.total_drag_delta_viewport(metadata)
}
fn update_selection_mode(&mut self, metadata: &DocumentMetadata, preferences: &PreferencesMessageHandler) -> SelectionMode {
if let Some(resolved_selection_mode) = self.resolved_selection_mode {
return resolved_selection_mode;
}
if preferences.get_selection_mode() != SelectionMode::Directional {
self.resolved_selection_mode = Some(preferences.get_selection_mode());
return preferences.get_selection_mode();
}
let [start, current] = self.start_current_viewport(metadata);
// Drag direction cannot be resolved TODO: why not consider only X distance?
if start.distance_squared(current) >= DRAG_DIRECTION_MODE_DETERMINATION_THRESHOLD.powi(2) {
let selection_mode = if current.x < start.x { SelectionMode::Touched } else { SelectionMode::Enclosed };
self.resolved_selection_mode = Some(selection_mode);
return selection_mode;
}
SelectionMode::default()
}
/// A viewport quad representing the drag bounds. Expanded if the start == end
pub fn expanded_selection_box_viewport(&self, metadata: &DocumentMetadata) -> [DVec2; 2] {
let [start, current] = self.start_current_viewport(metadata);
if start == current {
let tolerance = DVec2::splat(SELECTION_TOLERANCE);
[current - tolerance, current + tolerance]
} else {
[start, current]
}
}
}
#[derive(Clone, Debug, Default)]
struct SelectToolData {
drag: DragState,
@@ -879,6 +802,7 @@ impl Fsm for SelectToolFsmState {
let snap_data = SnapData::ignore(document, input, ignore);
let [start, current] = tool_data.drag.start_current_viewport(document.metadata());
let e0 = tool_data
.bounding_box_manager
.as_ref()
@@ -893,7 +817,7 @@ impl Fsm for SelectToolFsmState {
};
// TODO: Cache the result of `shallowest_unique_layers` to avoid this heavy computation every frame of movement, see https://github.com/GraphiteEditor/Graphite/pull/481
for layer in document.network_interface.shallowest_unique_layers(&[]) {
for layer in ShallowestSelectionIter::new(document.metadata(), &tool_data.layers_dragging) {
responses.add_front(GraphOperationMessage::TransformChange {
layer,
transform: DAffine2::from_translation(mouse_delta),
@@ -901,9 +825,8 @@ impl Fsm for SelectToolFsmState {
skip_rerender: false,
});
}
tool_data.drag.current_document += document.metadata().document_to_viewport.inverse().transform_vector2(mouse_delta);
info!("current {} mouse {}", tool_data.drag.current_viewport(document.metadata()), input.mouse.position);
tool_data.drag.offset_viewport(mouse_delta, document.metadata());
// Auto-panning
let messages = [

View File

@@ -0,0 +1,84 @@
use crate::consts::{DRAG_DIRECTION_MODE_DETERMINATION_THRESHOLD, SELECTION_TOLERANCE};
use crate::messages::{portfolio::document::utility_types::document_metadata::DocumentMetadata, preferences::SelectionMode, tool::tool_messages::tool_prelude::*};
/// Represents the current drag in progress
#[derive(Clone, Debug, Default)]
pub struct DragState {
pub start_document: DVec2,
pub current_document: DVec2,
/// Selection mode is set when the drag exceeds a certain distance. Once resolved, the selection mode cannot change.
resolved_selection_mode: Option<SelectionMode>,
}
impl DragState {
pub fn new(input: &InputPreprocessorMessageHandler, metadata: &DocumentMetadata) -> Self {
let document_mouse = metadata.document_to_viewport.inverse().transform_point2(input.mouse.position);
Self {
start_document: document_mouse,
current_document: document_mouse,
resolved_selection_mode: None,
}
}
pub fn set_current(&mut self, input: &InputPreprocessorMessageHandler, metadata: &DocumentMetadata) {
self.current_document = metadata.document_to_viewport.inverse().transform_point2(input.mouse.position);
}
pub fn offset_viewport(&mut self, offset: DVec2, metadata: &DocumentMetadata) {
self.current_document = self.current_document + metadata.document_to_viewport.inverse().transform_vector2(offset);
}
pub fn start_viewport(&self, metadata: &DocumentMetadata) -> DVec2 {
metadata.document_to_viewport.transform_point2(self.start_document)
}
pub fn current_viewport(&self, metadata: &DocumentMetadata) -> DVec2 {
metadata.document_to_viewport.transform_point2(self.current_document)
}
pub fn start_current_viewport(&self, metadata: &DocumentMetadata) -> [DVec2; 2] {
[self.start_viewport(metadata), self.current_viewport(metadata)]
}
pub fn total_drag_delta_document(&self) -> DVec2 {
self.current_document - self.start_document
}
pub fn total_drag_delta_viewport(&self, metadata: &DocumentMetadata) -> DVec2 {
metadata.document_to_viewport.transform_vector2(self.total_drag_delta_document())
}
pub fn inverse_drag_delta_viewport(&self, metadata: &DocumentMetadata) -> DVec2 {
-self.total_drag_delta_viewport(metadata)
}
pub fn update_selection_mode(&mut self, metadata: &DocumentMetadata, preferences: &PreferencesMessageHandler) -> SelectionMode {
if let Some(resolved_selection_mode) = self.resolved_selection_mode {
return resolved_selection_mode;
}
if preferences.get_selection_mode() != SelectionMode::Directional {
self.resolved_selection_mode = Some(preferences.get_selection_mode());
return preferences.get_selection_mode();
}
let [start, current] = self.start_current_viewport(metadata);
// Drag direction cannot be resolved TODO: why not consider only X distance?
if start.distance_squared(current) >= DRAG_DIRECTION_MODE_DETERMINATION_THRESHOLD.powi(2) {
let selection_mode = if current.x < start.x { SelectionMode::Touched } else { SelectionMode::Enclosed };
self.resolved_selection_mode = Some(selection_mode);
return selection_mode;
}
SelectionMode::default()
}
/// A viewport quad representing the drag bounds. Expanded if the start == end
pub fn expanded_selection_box_viewport(&self, metadata: &DocumentMetadata) -> [DVec2; 2] {
let [start, current] = self.start_current_viewport(metadata);
if start == current {
let tolerance = DVec2::splat(SELECTION_TOLERANCE);
[current - tolerance, current + tolerance]
} else {
[start, current]
}
}
}

View File

@@ -1,5 +1,5 @@
use super::super::tool_prelude::*;
use super::DragState;
use super::drag_state::DragState;
use crate::messages::portfolio::document::utility_types::network_interface::{FlowType, NodeTemplate};
use crate::messages::portfolio::document::utility_types::nodes::SelectedNodes;
use crate::messages::tool::tool_messages::select_tool::LayerNodeIdentifier;