mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-20 11:28:30 +08:00
WIP: Defer message
This commit is contained in:
@@ -4,6 +4,7 @@ pub const EXPORTS_TO_TOP_EDGE_PIXEL_GAP: u32 = 72;
|
||||
pub const EXPORTS_TO_RIGHT_EDGE_PIXEL_GAP: u32 = 120;
|
||||
pub const IMPORTS_TO_TOP_EDGE_PIXEL_GAP: u32 = 72;
|
||||
pub const IMPORTS_TO_LEFT_EDGE_PIXEL_GAP: u32 = 120;
|
||||
pub const TOOLTIP_DELAY: u32 = 800;
|
||||
|
||||
// VIEWPORT
|
||||
pub const VIEWPORT_ZOOM_WHEEL_RATE: f64 = (1. / 600.) * 3.;
|
||||
|
||||
@@ -140,6 +140,7 @@ impl Dispatcher {
|
||||
Message::Defer(message) => {
|
||||
let context = DeferMessageContext {
|
||||
portfolio: &self.message_handlers.portfolio_message_handler,
|
||||
time: self.message_handlers.input_preprocessor_message_handler.time,
|
||||
};
|
||||
self.message_handlers.defer_message_handler.process_message(message, &mut queue, context);
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use crate::messages::prelude::*;
|
||||
|
||||
#[impl_message(Message, Defer)]
|
||||
@@ -8,4 +10,6 @@ pub enum DeferMessage {
|
||||
AfterGraphRun { messages: Vec<Message> },
|
||||
TriggerNavigationReady,
|
||||
AfterNavigationReady { messages: Vec<Message> },
|
||||
RequestDeferredMessage { timeout: Duration, message: Box<Message> },
|
||||
CheckDeferredMessages,
|
||||
}
|
||||
|
||||
@@ -1,8 +1,15 @@
|
||||
use std::{
|
||||
collections::BTreeMap,
|
||||
ops::Bound,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
use crate::messages::prelude::*;
|
||||
|
||||
#[derive(ExtractField)]
|
||||
pub struct DeferMessageContext<'a> {
|
||||
pub portfolio: &'a PortfolioMessageHandler,
|
||||
pub time: Instant,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, ExtractField)]
|
||||
@@ -10,6 +17,7 @@ pub struct DeferMessageHandler {
|
||||
after_graph_run: HashMap<DocumentId, Vec<(u64, Message)>>,
|
||||
after_viewport_resize: Vec<Message>,
|
||||
current_graph_submission_id: u64,
|
||||
after_time_elapsed: BTreeMap<Instant, Message>,
|
||||
}
|
||||
|
||||
#[message_handler_data]
|
||||
@@ -49,6 +57,15 @@ impl MessageHandler<DeferMessage, DeferMessageContext<'_>> for DeferMessageHandl
|
||||
responses.add_front(message);
|
||||
}
|
||||
}
|
||||
DeferMessage::RequestDeferredMessage { timeout, message } => {
|
||||
self.after_time_elapsed.insert(context.time + timeout, *message);
|
||||
}
|
||||
DeferMessage::CheckDeferredMessages => {
|
||||
let after_current_time = self.after_time_elapsed.split_off((Bound::Unbounded, Bound::Excluded(context.time)));
|
||||
for (_, message) in std::mem::replace(self.after_time_elapsed, after_current_time) {
|
||||
responses.add(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -305,6 +305,10 @@ pub enum FrontendMessage {
|
||||
layout_target: LayoutTarget,
|
||||
diff: Vec<WidgetDiff>,
|
||||
},
|
||||
UpdateTooltip {
|
||||
position: Option<FrontendXY>,
|
||||
text: String,
|
||||
},
|
||||
UpdateWirePathInProgress {
|
||||
#[serde(rename = "wirePathInProgress")]
|
||||
wire_path_in_progress: Option<WirePathInProgress>,
|
||||
|
||||
@@ -4,7 +4,7 @@ use crate::messages::input_mapper::utility_types::misc::FrameTimeInfo;
|
||||
use crate::messages::portfolio::utility_types::KeyboardPlatformLayout;
|
||||
use crate::messages::prelude::*;
|
||||
use glam::DVec2;
|
||||
use std::time::Duration;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
#[derive(ExtractField)]
|
||||
pub struct InputPreprocessorMessageContext {
|
||||
@@ -14,7 +14,7 @@ pub struct InputPreprocessorMessageContext {
|
||||
#[derive(Debug, Default, ExtractField)]
|
||||
pub struct InputPreprocessorMessageHandler {
|
||||
pub frame_time: FrameTimeInfo,
|
||||
pub time: u64,
|
||||
pub time: Instant,
|
||||
pub keyboard: KeyStates,
|
||||
pub mouse: MouseState,
|
||||
pub viewport_bounds: ViewportBounds,
|
||||
@@ -114,7 +114,7 @@ impl MessageHandler<InputPreprocessorMessage, InputPreprocessorMessageContext> f
|
||||
}
|
||||
InputPreprocessorMessage::CurrentTime { timestamp } => {
|
||||
responses.add(AnimationMessage::SetTime { time: timestamp as f64 });
|
||||
self.time = timestamp;
|
||||
self.time = Instant::from(timestamp);
|
||||
self.frame_time.advance_timestamp(Duration::from_millis(timestamp));
|
||||
}
|
||||
InputPreprocessorMessage::WheelScroll { editor_mouse_state, modifier_keys } => {
|
||||
|
||||
@@ -215,6 +215,7 @@ pub enum NodeGraphMessage {
|
||||
SetLockedOrVisibilitySideEffects {
|
||||
node_ids: Vec<NodeId>,
|
||||
},
|
||||
TryDisplayTooltip,
|
||||
UpdateBoxSelection,
|
||||
UpdateImportsExports,
|
||||
UpdateLayerPanel,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use super::utility_types::{BoxSelection, ContextMenuInformation, DragStart};
|
||||
use super::{document_node_definitions, node_properties};
|
||||
use crate::consts::GRID_SIZE;
|
||||
use crate::consts::{GRID_SIZE, TOOLTIP_DELAY};
|
||||
use crate::messages::input_mapper::utility_types::macros::action_keys;
|
||||
use crate::messages::layout::utility_types::widget_prelude::*;
|
||||
use crate::messages::portfolio::document::document_message_handler::navigation_controls;
|
||||
@@ -94,6 +94,12 @@ pub struct NodeGraphMessageHandler {
|
||||
end_index: Option<usize>,
|
||||
// The rendered string for each thumbnail
|
||||
pub thumbnails: HashMap<NodeId, Graphic>,
|
||||
// If an input is being hovered. Used for tooltip
|
||||
hovering_input: bool,
|
||||
// If an output is being hovered. Used for tooltip
|
||||
hovering_output: bool,
|
||||
// If a node is being hovered. Used for tooltip
|
||||
hovering_node: bool,
|
||||
}
|
||||
|
||||
/// NodeGraphMessageHandler always modifies the network which the selected nodes are in. No GraphOperationMessages should be added here, since those messages will always affect the document network.
|
||||
@@ -1143,6 +1149,44 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
|
||||
.unwrap_or_else(|| modify_import_export.reorder_imports_exports.input_ports().count() + 1),
|
||||
);
|
||||
responses.add(FrontendMessage::UpdateExportReorderIndex { index: self.end_index });
|
||||
} else if !self.hovering_input && !self.hovering_output {
|
||||
if network_interface.input_connector_from_click(ipp.mouse.position, breadcrumb_network_path).is_some() {
|
||||
self.hovering_input = true;
|
||||
responses.add(DeferMessage::RequestDeferredMessage {
|
||||
message: Box::new(NodeGraphMessage::TryDisplayTooltip.into()),
|
||||
timeout: Duration::from_millis(TOOLTIP_DELAY),
|
||||
});
|
||||
}
|
||||
if network_interface.output_connector_from_click(ipp.mouse.position, breadcrumb_network_path).is_some() {
|
||||
self.hovering_output = true;
|
||||
responses.add(DeferMessage::RequestDeferredMessage {
|
||||
message: Box::new(NodeGraphMessage::TryDisplayTooltip.into()),
|
||||
timeout: Duration::from_millis(TOOLTIP_DELAY),
|
||||
})
|
||||
}
|
||||
} else if self.hovering_input {
|
||||
if !network_interface.input_connector_from_click(ipp.mouse.position, breadcrumb_network_path).is_some() {
|
||||
self.hovering_input = false;
|
||||
responses.add(FrontendMessage::UpdateTooltip { position: None, text: String::new() });
|
||||
}
|
||||
} else if self.hovering_output {
|
||||
if !network_interface.output_connector_from_click(ipp.mouse.position, breadcrumb_network_path).is_some() {
|
||||
self.hovering_output = false;
|
||||
responses.add(FrontendMessage::UpdateTooltip { position: None, text: String::new() });
|
||||
}
|
||||
} else if !self.hovering_node {
|
||||
if network_interface.node_from_click(ipp.mouse.position, breadcrumb_network_path) {
|
||||
self.hovering_node = true;
|
||||
responses.add(DeferMessage::RequestDeferredMessage {
|
||||
message: Box::new(NodeGraphMessage::TryDisplayTooltip.into()),
|
||||
timeout: Duration::from_millis(TOOLTIP_DELAY),
|
||||
})
|
||||
}
|
||||
} else if self.hovering_node {
|
||||
if !network_interface.node_from_click(ipp.mouse.position, breadcrumb_network_path) {
|
||||
self.hovering_node = false;
|
||||
responses.add(FrontendMessage::UpdateTooltip { position: None, text: String::new() });
|
||||
}
|
||||
}
|
||||
}
|
||||
NodeGraphMessage::PointerUp => {
|
||||
@@ -1595,8 +1639,6 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
|
||||
responses.add(DocumentMessage::DocumentStructureChanged);
|
||||
responses.add(PropertiesPanelMessage::Refresh);
|
||||
responses.add(NodeGraphMessage::UpdateActionButtons);
|
||||
|
||||
responses.add(FrontendMessage::RequestNativeNodeGraphRender);
|
||||
responses.add(NodeGraphMessage::UpdateImportsExports);
|
||||
self.update_node_graph_hints(responses);
|
||||
}
|
||||
@@ -1807,6 +1849,54 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
|
||||
responses.add(NodeGraphMessage::SetVisibility { node_id, visible });
|
||||
responses.add(NodeGraphMessage::SetLockedOrVisibilitySideEffects { node_ids: vec![node_id] });
|
||||
}
|
||||
NodeGraphMessage::TryDisplayTooltip => {
|
||||
if let Some(input) = network_interface.input_connector_from_click(ipp.mouse.position, breadcrumb_network_path) {
|
||||
let text = network_interface.input_tooltip_text(&input, breadcrumb_network_path);
|
||||
if let Some(position) = network_interface.input_position(&input, breadcrumb_network_path) {
|
||||
let Some(network_metadata) = network_interface.network_metadata(breadcrumb_network_path) else {
|
||||
return;
|
||||
};
|
||||
let position = network_metadata.persistent_metadata.navigation_metadata.node_graph_to_viewport.transform_point2(position);
|
||||
let xy = FrontendXY {
|
||||
x: position.x as i32,
|
||||
y: position.y as i32,
|
||||
};
|
||||
responses.add(FrontendMessage::UpdateTooltip { position: Some(xy), text });
|
||||
}
|
||||
} else if let Some(output) = network_interface.output_connector_from_click(ipp.mouse.position, breadcrumb_network_path) {
|
||||
let text = network_interface.output_tooltip_text(&output, breadcrumb_network_path);
|
||||
if let Some(position) = network_interface.output_position(&output, breadcrumb_network_path) {
|
||||
let Some(network_metadata) = network_interface.network_metadata(breadcrumb_network_path) else {
|
||||
return;
|
||||
};
|
||||
let position = network_metadata.persistent_metadata.navigation_metadata.node_graph_to_viewport.transform_point2(position);
|
||||
|
||||
let xy = FrontendXY {
|
||||
x: position.x as i32,
|
||||
y: position.y as i32,
|
||||
};
|
||||
responses.add(FrontendMessage::UpdateTooltip { position: Some(xy), text });
|
||||
}
|
||||
} else if let Some(node_id) = network_interface.node_from_click(ipp.mouse.position, breadcrumb_network_path) {
|
||||
let text = network_interface.node_tooltip_text(&node_id, breadcrumb_network_path);
|
||||
if let Some(position) = network_interface.position(&node_id, breadcrumb_network_path) {
|
||||
let Some(network_metadata) = network_interface.network_metadata(breadcrumb_network_path) else {
|
||||
return;
|
||||
};
|
||||
let position = network_metadata
|
||||
.persistent_metadata
|
||||
.navigation_metadata
|
||||
.node_graph_to_viewport
|
||||
.transform_point2(DVec2::new(position.x as f64 * 24., position.y as f64 * 24.));
|
||||
|
||||
let xy = FrontendXY {
|
||||
x: position.x as i32,
|
||||
y: position.y as i32,
|
||||
};
|
||||
responses.add(FrontendMessage::UpdateTooltip { position: Some(xy), text });
|
||||
}
|
||||
}
|
||||
}
|
||||
NodeGraphMessage::SetPinned { node_id, pinned } => {
|
||||
network_interface.set_pinned(&node_id, selection_network_path, pinned);
|
||||
}
|
||||
|
||||
+97
-79
@@ -144,24 +144,9 @@ impl NodeNetworkInterface {
|
||||
}
|
||||
let input_type = self.input_type(input_connector, network_path);
|
||||
let data_type = input_type.displayed_type();
|
||||
let resolved_type = input_type.resolved_type_name();
|
||||
|
||||
let connected_to = self
|
||||
.upstream_output_connector(input_connector, network_path)
|
||||
.map(|output_connector| match output_connector {
|
||||
OutputConnector::Node { node_id, output_index } => {
|
||||
let mut name = self.display_name(&node_id, network_path);
|
||||
if cfg!(debug_assertions) {
|
||||
name.push_str(&format!(" (id: {node_id})"));
|
||||
}
|
||||
format!("{name} output {output_index}")
|
||||
}
|
||||
OutputConnector::Import(import_index) => format!("Import index {import_index}"),
|
||||
})
|
||||
.unwrap_or("nothing".to_string());
|
||||
|
||||
let (name, description) = match input_connector {
|
||||
InputConnector::Node { node_id, input_index } => self.displayed_input_name_and_description(node_id, *input_index, network_path),
|
||||
let name = match input_connector {
|
||||
InputConnector::Node { node_id, input_index } => self.displayed_input_name_and_description(node_id, *input_index, network_path).0,
|
||||
InputConnector::Export(export_index) => {
|
||||
// Get export name from parent node metadata input, which must match the number of exports.
|
||||
// Empty string means to use type, or "Export + index" if type is empty determined
|
||||
@@ -173,44 +158,26 @@ impl NodeNetworkInterface {
|
||||
.unwrap_or_default()
|
||||
};
|
||||
|
||||
let export_name = if !export_name.is_empty() {
|
||||
if !export_name.is_empty() {
|
||||
export_name
|
||||
} else if let Some(export_type_name) = input_type.compiled_nested_type_name() {
|
||||
export_type_name
|
||||
} else {
|
||||
format!("Export index {}", export_index)
|
||||
};
|
||||
|
||||
(export_name, String::new())
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// TODO: Move in separate Tooltip overlay
|
||||
// let valid_types = match self.valid_input_types(&input_connector, network_path) {
|
||||
// Ok(input_types) => input_types.iter().map(|ty| ty.to_string()).collect(),
|
||||
// Err(e) => {
|
||||
// log::error!("Error getting valid types for input {input_connector:?}: {e}");
|
||||
// Vec::new()
|
||||
// }
|
||||
// };
|
||||
|
||||
let connected_to_node = self.upstream_output_connector(input_connector, network_path).and_then(|output_connector| output_connector.node_id());
|
||||
|
||||
Some(FrontendGraphInput {
|
||||
data_type,
|
||||
resolved_type,
|
||||
name,
|
||||
description,
|
||||
connected_to,
|
||||
connected_to_node,
|
||||
})
|
||||
Some(FrontendGraphInput { data_type, name, connected_to_node })
|
||||
}
|
||||
|
||||
/// Returns None if there is an error, it is the document network, a hidden primary output or import
|
||||
pub fn frontend_output_from_connector(&mut self, output_connector: &OutputConnector, network_path: &[NodeId]) -> Option<FrontendGraphOutput> {
|
||||
let output_type = self.output_type(output_connector, network_path);
|
||||
|
||||
let (name, description) = match output_connector {
|
||||
let name = match output_connector {
|
||||
OutputConnector::Node { node_id, output_index } => {
|
||||
// Do not display the primary output port for a node if it is a network node with a hidden primary export
|
||||
if *output_index == 0 && self.hidden_primary_output(node_id, network_path) {
|
||||
@@ -220,8 +187,7 @@ impl NodeNetworkInterface {
|
||||
let node_metadata = self.node_metadata(node_id, network_path)?;
|
||||
let output_name = node_metadata.persistent_metadata.output_names.get(*output_index).cloned().unwrap_or_default();
|
||||
|
||||
let output_name = if !output_name.is_empty() { output_name } else { output_type.resolved_type_name() };
|
||||
(output_name, String::new())
|
||||
if !output_name.is_empty() { output_name } else { output_type.resolved_type_name() }
|
||||
}
|
||||
OutputConnector::Import(import_index) => {
|
||||
// Get the import name from the encapsulating node input metadata
|
||||
@@ -233,53 +199,19 @@ impl NodeNetworkInterface {
|
||||
if *import_index == 0 && self.hidden_primary_import(network_path) {
|
||||
return None;
|
||||
};
|
||||
let (import_name, description) = self.displayed_input_name_and_description(encapsulating_node_id, *import_index, encapsulating_path);
|
||||
let import_name = self.displayed_input_name_and_description(encapsulating_node_id, *import_index, encapsulating_path).0;
|
||||
|
||||
let import_name = if !import_name.is_empty() {
|
||||
if !import_name.is_empty() {
|
||||
import_name
|
||||
} else if let Some(import_type_name) = output_type.compiled_nested_type_name() {
|
||||
import_type_name
|
||||
} else {
|
||||
format!("Import index {}", *import_index)
|
||||
};
|
||||
|
||||
(import_name, description)
|
||||
}
|
||||
}
|
||||
};
|
||||
let data_type = output_type.displayed_type();
|
||||
let resolved_type = output_type.resolved_type_name();
|
||||
let mut connected_to = self
|
||||
.outward_wires(network_path)
|
||||
.and_then(|outward_wires| outward_wires.get(output_connector))
|
||||
.cloned()
|
||||
.unwrap_or_else(|| {
|
||||
log::error!("Could not get {output_connector:?} in outward wires");
|
||||
Vec::new()
|
||||
})
|
||||
.iter()
|
||||
.map(|input| match input {
|
||||
InputConnector::Node { node_id, input_index } => {
|
||||
let mut name = self.display_name(node_id, network_path);
|
||||
if cfg!(debug_assertions) {
|
||||
name.push_str(&format!(" (id: {node_id})"));
|
||||
}
|
||||
format!("{name} input {input_index}")
|
||||
}
|
||||
InputConnector::Export(export_index) => format!("Export index {export_index}"),
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
if connected_to.is_empty() {
|
||||
connected_to.push("nothing".to_string());
|
||||
}
|
||||
|
||||
Some(FrontendGraphOutput {
|
||||
data_type,
|
||||
resolved_type,
|
||||
name,
|
||||
description,
|
||||
connected_to,
|
||||
})
|
||||
Some(FrontendGraphOutput { data_type, name })
|
||||
}
|
||||
|
||||
pub fn chain_width(&self, node_id: &NodeId, network_path: &[NodeId]) -> u32 {
|
||||
@@ -545,4 +477,90 @@ impl NodeNetworkInterface {
|
||||
|
||||
Some(vector_wire)
|
||||
}
|
||||
|
||||
pub fn input_tooltip_text(&mut self, input_connector: &InputConnector, network_path: &[NodeId]) -> String {
|
||||
let input_type = self.input_type(input_connector, network_path);
|
||||
let data_type_str = format!("Data Type: {input_type:?}");
|
||||
|
||||
let connected_to = self
|
||||
.upstream_output_connector(input_connector, network_path)
|
||||
.map(|output_connector| match output_connector {
|
||||
OutputConnector::Node { node_id, output_index } => {
|
||||
let mut name = self.display_name(&node_id, network_path);
|
||||
if cfg!(debug_assertions) {
|
||||
name.push_str(&format!(" (id: {node_id})"));
|
||||
}
|
||||
format!("{name} output {output_index}")
|
||||
}
|
||||
OutputConnector::Import(import_index) => format!("Import index {import_index}"),
|
||||
})
|
||||
.unwrap_or("nothing".to_string());
|
||||
let connected_to_str = format!("Connected to: {connected_to}");
|
||||
|
||||
let valid_types = match self.valid_input_types(input_connector, network_path) {
|
||||
Ok(valid) => valid,
|
||||
Err(e) => {
|
||||
log::error!("Could not get valid types in input tooltip text: {e}");
|
||||
return String::new();
|
||||
}
|
||||
};
|
||||
|
||||
let valid_types_str = if !valid_types.is_empty() {
|
||||
let mut strings = valid_types.iter().map(|x| format!("• {x}")).collect::<Vec<_>>();
|
||||
strings.sort();
|
||||
strings.join("\n")
|
||||
} else {
|
||||
"None".to_string()
|
||||
};
|
||||
|
||||
let valid_types_str = format!("Valid Types:\n{}", valid_types_str);
|
||||
|
||||
format!("{data_type_str}\n\n{connected_to_str}\n\n{valid_types_str}")
|
||||
}
|
||||
|
||||
pub fn output_tooltip_text(&mut self, output_connector: &OutputConnector, network_path: &[NodeId]) -> String {
|
||||
let output_type = self.output_type(output_connector, network_path);
|
||||
let data_type_str = format!("Data Type: {output_type:?}");
|
||||
|
||||
let mut connected_to = self
|
||||
.outward_wires(network_path)
|
||||
.and_then(|outward_wires| outward_wires.get(output_connector))
|
||||
.cloned()
|
||||
.unwrap_or_else(|| {
|
||||
log::error!("Could not get {output_connector:?} in outward wires");
|
||||
Vec::new()
|
||||
})
|
||||
.iter()
|
||||
.map(|input| match input {
|
||||
InputConnector::Node { node_id, input_index } => {
|
||||
let mut name = self.display_name(node_id, network_path);
|
||||
if cfg!(debug_assertions) {
|
||||
name.push_str(&format!(" (id: {node_id})"));
|
||||
}
|
||||
format!("{name} input {input_index}")
|
||||
}
|
||||
InputConnector::Export(export_index) => format!("Export index {export_index}"),
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
connected_to.sort();
|
||||
if connected_to.is_empty() {
|
||||
connected_to.push("nothing".to_string());
|
||||
}
|
||||
let connected_to = connected_to.join("\n");
|
||||
let connected_to_str = format!("Connected to:\n{connected_to}");
|
||||
|
||||
format!("{data_type_str}\n\n{connected_to_str}")
|
||||
}
|
||||
|
||||
pub fn node_tooltip_text(&mut self, node_id: &NodeId, network_path: &[NodeId]) -> String {
|
||||
let display_name = self.display_name(node_id, network_path);
|
||||
let description = self.description(node_id, network_path);
|
||||
let Some(reference) = self.reference(node_id, network_path) else {
|
||||
log::error!("Could not get referende in node_tooltip_text for {node_id}");
|
||||
return String::new();
|
||||
};
|
||||
|
||||
format!("{display_name}\nReference: {reference}\n\n{description}")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -562,7 +562,7 @@ struct PathToolData {
|
||||
saved_selection_before_handle_drag: HashMap<LayerNodeIdentifier, (HashSet<ManipulatorPointId>, HashSet<SegmentId>)>,
|
||||
handle_drag_toggle: bool,
|
||||
saved_points_before_anchor_convert_smooth_sharp: HashMap<LayerNodeIdentifier, Vec<ManipulatorPointId>>,
|
||||
last_click_time: u64,
|
||||
last_click_time: Instant,
|
||||
dragging_state: DraggingState,
|
||||
angle: f64,
|
||||
pivot_gizmo: PivotGizmo,
|
||||
|
||||
Reference in New Issue
Block a user