mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-22 22:18:12 +08:00
Merge remote-tracking branch 'origin/master' into spiral-node
This commit is contained in:
@@ -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 = 4.;
|
||||
|
||||
// COMPASS ROSE
|
||||
pub const COMPASS_ROSE_RING_INNER_DIAMETER: f64 = 13.;
|
||||
@@ -133,8 +134,8 @@ pub const SCALE_EFFECT: f64 = 0.5;
|
||||
|
||||
// COLORS
|
||||
pub const COLOR_OVERLAY_BLUE: &str = "#00a8ff";
|
||||
pub const COLOR_OVERLAY_BLUE_50: &str = "rgba(0, 168, 255, 0.5)";
|
||||
pub const COLOR_OVERLAY_YELLOW: &str = "#ffc848";
|
||||
pub const COLOR_OVERLAY_YELLOW_DULL: &str = "#d7ba8b";
|
||||
pub const COLOR_OVERLAY_GREEN: &str = "#63ce63";
|
||||
pub const COLOR_OVERLAY_RED: &str = "#ef5454";
|
||||
pub const COLOR_OVERLAY_GRAY: &str = "#cccccc";
|
||||
|
||||
@@ -40,7 +40,6 @@ impl DispatcherMessageHandlers {
|
||||
/// The last occurrence of the message in the message queue is sufficient to ensure correct behavior.
|
||||
/// In addition, these messages do not change any state in the backend (aside from caches).
|
||||
const SIDE_EFFECT_FREE_MESSAGES: &[MessageDiscriminant] = &[
|
||||
MessageDiscriminant::Portfolio(PortfolioMessageDiscriminant::Document(DocumentMessageDiscriminant::NodeGraph(NodeGraphMessageDiscriminant::SendGraph))),
|
||||
MessageDiscriminant::Portfolio(PortfolioMessageDiscriminant::Document(DocumentMessageDiscriminant::PropertiesPanel(
|
||||
PropertiesPanelMessageDiscriminant::Refresh,
|
||||
))),
|
||||
@@ -141,6 +140,7 @@ impl Dispatcher {
|
||||
let graphene_std::renderer::RenderMetadata {
|
||||
upstream_footprints: footprints,
|
||||
local_transforms,
|
||||
first_instance_source_id,
|
||||
click_targets,
|
||||
clip_targets,
|
||||
} = render_metadata;
|
||||
@@ -150,6 +150,7 @@ impl Dispatcher {
|
||||
DocumentMessage::UpdateUpstreamTransforms {
|
||||
upstream_footprints: footprints,
|
||||
local_transforms,
|
||||
first_instance_source_id,
|
||||
},
|
||||
DocumentMessage::UpdateClickTargets { click_targets },
|
||||
DocumentMessage::UpdateClipTargets { clip_targets },
|
||||
|
||||
@@ -15,3 +15,4 @@ pub mod node_graph_executor;
|
||||
#[cfg(test)]
|
||||
pub mod test_utils;
|
||||
pub mod utility_traits;
|
||||
pub mod utility_types;
|
||||
|
||||
@@ -24,7 +24,7 @@ enum AnimationState {
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, PartialEq)]
|
||||
#[derive(Default, Debug, Clone, PartialEq, ExtractField)]
|
||||
pub struct AnimationMessageHandler {
|
||||
/// Used to re-send the UI on the next frame after playback starts
|
||||
live_preview_recently_zero: bool,
|
||||
@@ -57,6 +57,7 @@ impl AnimationMessageHandler {
|
||||
}
|
||||
}
|
||||
|
||||
#[message_handler_data]
|
||||
impl MessageHandler<AnimationMessage, ()> for AnimationMessageHandler {
|
||||
fn process_message(&mut self, message: AnimationMessage, responses: &mut VecDeque<Message>, _data: ()) {
|
||||
match message {
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
use crate::messages::prelude::*;
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
#[derive(Debug, Clone, Default, ExtractField)]
|
||||
pub struct BroadcastMessageHandler {
|
||||
listeners: HashMap<BroadcastEvent, Vec<Message>>,
|
||||
}
|
||||
|
||||
#[message_handler_data]
|
||||
impl MessageHandler<BroadcastMessage, ()> for BroadcastMessageHandler {
|
||||
fn process_message(&mut self, message: BroadcastMessage, responses: &mut VecDeque<Message>, _data: ()) {
|
||||
match message {
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
use super::utility_types::MessageLoggingVerbosity;
|
||||
use crate::messages::prelude::*;
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
#[derive(Debug, Default, ExtractField)]
|
||||
pub struct DebugMessageHandler {
|
||||
pub message_logging_verbosity: MessageLoggingVerbosity,
|
||||
}
|
||||
|
||||
#[message_handler_data]
|
||||
impl MessageHandler<DebugMessage, ()> for DebugMessageHandler {
|
||||
fn process_message(&mut self, message: DebugMessage, responses: &mut VecDeque<Message>, _data: ()) {
|
||||
match message {
|
||||
|
||||
@@ -2,19 +2,21 @@ use super::simple_dialogs::{self, AboutGraphiteDialog, ComingSoonDialog, DemoArt
|
||||
use crate::messages::layout::utility_types::widget_prelude::*;
|
||||
use crate::messages::prelude::*;
|
||||
|
||||
#[derive(ExtractField)]
|
||||
pub struct DialogMessageData<'a> {
|
||||
pub portfolio: &'a PortfolioMessageHandler,
|
||||
pub preferences: &'a PreferencesMessageHandler,
|
||||
}
|
||||
|
||||
/// Stores the dialogs which require state. These are the ones that have their own message handlers, and are not the ones defined in `simple_dialogs`.
|
||||
#[derive(Debug, Default, Clone)]
|
||||
#[derive(Debug, Default, Clone, ExtractField)]
|
||||
pub struct DialogMessageHandler {
|
||||
export_dialog: ExportDialogMessageHandler,
|
||||
new_document_dialog: NewDocumentDialogMessageHandler,
|
||||
preferences_dialog: PreferencesDialogMessageHandler,
|
||||
}
|
||||
|
||||
#[message_handler_data]
|
||||
impl MessageHandler<DialogMessage, DialogMessageData<'_>> for DialogMessageHandler {
|
||||
fn process_message(&mut self, message: DialogMessage, responses: &mut VecDeque<Message>, data: DialogMessageData) {
|
||||
let DialogMessageData { portfolio, preferences } = data;
|
||||
|
||||
@@ -3,12 +3,13 @@ use crate::messages::layout::utility_types::widget_prelude::*;
|
||||
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
|
||||
use crate::messages::prelude::*;
|
||||
|
||||
#[derive(ExtractField)]
|
||||
pub struct ExportDialogMessageData<'a> {
|
||||
pub portfolio: &'a PortfolioMessageHandler,
|
||||
}
|
||||
|
||||
/// A dialog to allow users to customize their file export.
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, ExtractField)]
|
||||
pub struct ExportDialogMessageHandler {
|
||||
pub file_type: FileType,
|
||||
pub scale_factor: f64,
|
||||
@@ -31,6 +32,7 @@ impl Default for ExportDialogMessageHandler {
|
||||
}
|
||||
}
|
||||
|
||||
#[message_handler_data]
|
||||
impl MessageHandler<ExportDialogMessage, ExportDialogMessageData<'_>> for ExportDialogMessageHandler {
|
||||
fn process_message(&mut self, message: ExportDialogMessage, responses: &mut VecDeque<Message>, data: ExportDialogMessageData) {
|
||||
let ExportDialogMessageData { portfolio } = data;
|
||||
|
||||
+2
-1
@@ -4,13 +4,14 @@ use glam::{IVec2, UVec2};
|
||||
use graph_craft::document::NodeId;
|
||||
|
||||
/// A dialog to allow users to set some initial options about a new document.
|
||||
#[derive(Debug, Clone, Default)]
|
||||
#[derive(Debug, Clone, Default, ExtractField)]
|
||||
pub struct NewDocumentDialogMessageHandler {
|
||||
pub name: String,
|
||||
pub infinite: bool,
|
||||
pub dimensions: UVec2,
|
||||
}
|
||||
|
||||
#[message_handler_data]
|
||||
impl MessageHandler<NewDocumentDialogMessage, ()> for NewDocumentDialogMessageHandler {
|
||||
fn process_message(&mut self, message: NewDocumentDialogMessage, responses: &mut VecDeque<Message>, _data: ()) {
|
||||
match message {
|
||||
|
||||
@@ -1,17 +1,19 @@
|
||||
use crate::consts::{VIEWPORT_ZOOM_WHEEL_RATE, VIEWPORT_ZOOM_WHEEL_RATE_CHANGE};
|
||||
use crate::messages::layout::utility_types::widget_prelude::*;
|
||||
use crate::messages::portfolio::document::node_graph::utility_types::GraphWireStyle;
|
||||
use crate::messages::portfolio::document::utility_types::wires::GraphWireStyle;
|
||||
use crate::messages::preferences::SelectionMode;
|
||||
use crate::messages::prelude::*;
|
||||
|
||||
#[derive(ExtractField)]
|
||||
pub struct PreferencesDialogMessageData<'a> {
|
||||
pub preferences: &'a PreferencesMessageHandler,
|
||||
}
|
||||
|
||||
/// A dialog to allow users to customize Graphite editor options
|
||||
#[derive(Debug, Clone, Default)]
|
||||
#[derive(Debug, Clone, Default, ExtractField)]
|
||||
pub struct PreferencesDialogMessageHandler {}
|
||||
|
||||
#[message_handler_data]
|
||||
impl MessageHandler<PreferencesDialogMessage, PreferencesDialogMessageData<'_>> for PreferencesDialogMessageHandler {
|
||||
fn process_message(&mut self, message: PreferencesDialogMessage, responses: &mut VecDeque<Message>, data: PreferencesDialogMessageData) {
|
||||
let PreferencesDialogMessageData { preferences } = data;
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
use super::utility_types::{FrontendDocumentDetails, MouseCursorIcon};
|
||||
use crate::messages::layout::utility_types::widget_prelude::*;
|
||||
use crate::messages::portfolio::document::node_graph::utility_types::{
|
||||
BoxSelection, ContextMenuInformation, FrontendClickTargets, FrontendGraphInput, FrontendGraphOutput, FrontendNode, FrontendNodeType, FrontendNodeWire, Transform, WirePath,
|
||||
BoxSelection, ContextMenuInformation, FrontendClickTargets, FrontendGraphInput, FrontendGraphOutput, FrontendNode, FrontendNodeType, Transform,
|
||||
};
|
||||
use crate::messages::portfolio::document::utility_types::nodes::{JsRawBuffer, LayerPanelEntry, RawBuffer};
|
||||
use crate::messages::portfolio::document::utility_types::wires::{WirePath, WirePathUpdate};
|
||||
use crate::messages::prelude::*;
|
||||
use crate::messages::tool::utility_types::HintData;
|
||||
use graph_craft::document::NodeId;
|
||||
@@ -250,12 +251,16 @@ pub enum FrontendMessage {
|
||||
UpdateMouseCursor {
|
||||
cursor: MouseCursorIcon,
|
||||
},
|
||||
UpdateNodeGraph {
|
||||
UpdateNodeGraphNodes {
|
||||
nodes: Vec<FrontendNode>,
|
||||
wires: Vec<FrontendNodeWire>,
|
||||
#[serde(rename = "wiresDirectNotGridAligned")]
|
||||
wires_direct_not_grid_aligned: bool,
|
||||
},
|
||||
UpdateVisibleNodes {
|
||||
nodes: Vec<NodeId>,
|
||||
},
|
||||
UpdateNodeGraphWires {
|
||||
wires: Vec<WirePathUpdate>,
|
||||
},
|
||||
ClearAllNodeGraphWires,
|
||||
UpdateNodeGraphControlBarLayout {
|
||||
#[serde(rename = "layoutTarget")]
|
||||
layout_target: LayoutTarget,
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
use crate::messages::prelude::*;
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
#[derive(Debug, Default, ExtractField)]
|
||||
pub struct GlobalsMessageHandler {}
|
||||
|
||||
#[message_handler_data]
|
||||
impl MessageHandler<GlobalsMessage, ()> for GlobalsMessageHandler {
|
||||
fn process_message(&mut self, message: GlobalsMessage, _responses: &mut VecDeque<Message>, _data: ()) {
|
||||
match message {
|
||||
|
||||
@@ -6,16 +6,18 @@ use crate::messages::portfolio::utility_types::KeyboardPlatformLayout;
|
||||
use crate::messages::prelude::*;
|
||||
use std::fmt::Write;
|
||||
|
||||
#[derive(ExtractField)]
|
||||
pub struct InputMapperMessageData<'a> {
|
||||
pub input: &'a InputPreprocessorMessageHandler,
|
||||
pub actions: ActionList,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
#[derive(Debug, Default, ExtractField)]
|
||||
pub struct InputMapperMessageHandler {
|
||||
mapping: Mapping,
|
||||
}
|
||||
|
||||
#[message_handler_data]
|
||||
impl MessageHandler<InputMapperMessage, InputMapperMessageData<'_>> for InputMapperMessageHandler {
|
||||
fn process_message(&mut self, message: InputMapperMessage, responses: &mut VecDeque<Message>, data: InputMapperMessageData) {
|
||||
let InputMapperMessageData { input, actions } = data;
|
||||
|
||||
@@ -225,7 +225,7 @@ pub fn input_mappings() -> Mapping {
|
||||
entry!(KeyDown(Backspace); action_dispatch=PathToolMessage::Delete),
|
||||
entry!(KeyUp(MouseLeft); action_dispatch=PathToolMessage::DragStop { extend_selection: Shift, shrink_selection: Alt }),
|
||||
entry!(KeyDown(Enter); action_dispatch=PathToolMessage::Enter { extend_selection: Shift, shrink_selection: Alt }),
|
||||
entry!(DoubleClick(MouseButton::Left); action_dispatch=PathToolMessage::FlipSmoothSharp),
|
||||
entry!(DoubleClick(MouseButton::Left); action_dispatch=PathToolMessage::DoubleClick { extend_selection: Shift, shrink_selection: Alt }),
|
||||
entry!(KeyDown(ArrowRight); action_dispatch=PathToolMessage::NudgeSelectedPoints { delta_x: NUDGE_AMOUNT, delta_y: 0. }),
|
||||
entry!(KeyDown(ArrowRight); modifiers=[Shift], action_dispatch=PathToolMessage::NudgeSelectedPoints { delta_x: BIG_NUDGE_AMOUNT, delta_y: 0. }),
|
||||
entry!(KeyDown(ArrowRight); modifiers=[ArrowUp], action_dispatch=PathToolMessage::NudgeSelectedPoints { delta_x: NUDGE_AMOUNT, delta_y: -NUDGE_AMOUNT }),
|
||||
|
||||
@@ -2,16 +2,18 @@ use crate::messages::input_mapper::input_mapper_message_handler::InputMapperMess
|
||||
use crate::messages::input_mapper::utility_types::input_keyboard::KeysGroup;
|
||||
use crate::messages::prelude::*;
|
||||
|
||||
#[derive(ExtractField)]
|
||||
pub struct KeyMappingMessageData<'a> {
|
||||
pub input: &'a InputPreprocessorMessageHandler,
|
||||
pub actions: ActionList,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
#[derive(Debug, Default, ExtractField)]
|
||||
pub struct KeyMappingMessageHandler {
|
||||
mapping_handler: InputMapperMessageHandler,
|
||||
}
|
||||
|
||||
#[message_handler_data]
|
||||
impl MessageHandler<KeyMappingMessage, KeyMappingMessageData<'_>> for KeyMappingMessageHandler {
|
||||
fn process_message(&mut self, message: KeyMappingMessage, responses: &mut VecDeque<Message>, data: KeyMappingMessageData) {
|
||||
let KeyMappingMessageData { input, actions } = data;
|
||||
|
||||
@@ -6,11 +6,12 @@ use crate::messages::prelude::*;
|
||||
use glam::DVec2;
|
||||
use std::time::Duration;
|
||||
|
||||
#[derive(ExtractField)]
|
||||
pub struct InputPreprocessorMessageData {
|
||||
pub keyboard_platform: KeyboardPlatformLayout,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
#[derive(Debug, Default, ExtractField)]
|
||||
pub struct InputPreprocessorMessageHandler {
|
||||
pub frame_time: FrameTimeInfo,
|
||||
pub time: u64,
|
||||
@@ -19,6 +20,7 @@ pub struct InputPreprocessorMessageHandler {
|
||||
pub viewport_bounds: ViewportBounds,
|
||||
}
|
||||
|
||||
#[message_handler_data]
|
||||
impl MessageHandler<InputPreprocessorMessage, InputPreprocessorMessageData> for InputPreprocessorMessageHandler {
|
||||
fn process_message(&mut self, message: InputPreprocessorMessage, responses: &mut VecDeque<Message>, data: InputPreprocessorMessageData) {
|
||||
let InputPreprocessorMessageData { keyboard_platform } = data;
|
||||
|
||||
@@ -6,7 +6,7 @@ use graphene_std::text::Font;
|
||||
use graphene_std::vector::style::{FillChoice, GradientStops};
|
||||
use serde_json::Value;
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
#[derive(Debug, Clone, Default, ExtractField)]
|
||||
pub struct LayoutMessageHandler {
|
||||
layouts: [Layout; LayoutTarget::LayoutTargetLength as usize],
|
||||
}
|
||||
@@ -342,6 +342,15 @@ impl LayoutMessageHandler {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn custom_data() -> MessageData {
|
||||
// TODO: When <https://github.com/dtolnay/proc-macro2/issues/503> is resolved and released,
|
||||
// TODO: use <https://doc.rust-lang.org/stable/proc_macro/struct.Span.html#method.line> to get
|
||||
// TODO: the line number instead of hardcoding it to the magic number on the following line.
|
||||
// TODO: Also, utilize the line number in the actual output, since it is currently unused.
|
||||
MessageData::new(String::from("Function"), vec![(String::from("Fn(&MessageDiscriminant) -> Option<KeysGroup>"), 350)], file!())
|
||||
}
|
||||
|
||||
#[message_handler_data(CustomData)]
|
||||
impl<F: Fn(&MessageDiscriminant) -> Option<KeysGroup>> MessageHandler<LayoutMessage, F> for LayoutMessageHandler {
|
||||
fn process_message(&mut self, message: LayoutMessage, responses: &mut std::collections::VecDeque<Message>, action_input_mapping: F) {
|
||||
match message {
|
||||
|
||||
@@ -471,6 +471,8 @@ pub struct ReferencePointInput {
|
||||
|
||||
pub disabled: bool,
|
||||
|
||||
pub tooltip: String,
|
||||
|
||||
// Callbacks
|
||||
#[serde(skip)]
|
||||
#[derivative(Debug = "ignore", PartialEq = "ignore")]
|
||||
|
||||
@@ -45,3 +45,86 @@ impl specta::Type for MessageDiscriminant {
|
||||
specta::DataType::Any
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use std::io::Write;
|
||||
|
||||
#[test]
|
||||
fn generate_message_tree() {
|
||||
let result = Message::build_message_tree();
|
||||
let mut file = std::fs::File::create("../hierarchical_message_system_tree.txt").unwrap();
|
||||
file.write_all(format!("{} `{}`\n", result.name(), result.path()).as_bytes()).unwrap();
|
||||
if let Some(variants) = result.variants() {
|
||||
for (i, variant) in variants.iter().enumerate() {
|
||||
let is_last = i == variants.len() - 1;
|
||||
print_tree_node(variant, "", is_last, &mut file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn print_tree_node(tree: &DebugMessageTree, prefix: &str, is_last: bool, file: &mut std::fs::File) {
|
||||
// Print the current node
|
||||
let (branch, child_prefix) = if tree.has_message_handler_data_fields() || tree.has_message_handler_fields() {
|
||||
("├── ", format!("{}│ ", prefix))
|
||||
} else {
|
||||
if is_last {
|
||||
("└── ", format!("{} ", prefix))
|
||||
} else {
|
||||
("├── ", format!("{}│ ", prefix))
|
||||
}
|
||||
};
|
||||
|
||||
if tree.path().is_empty() {
|
||||
file.write_all(format!("{}{}{}\n", prefix, branch, tree.name()).as_bytes()).unwrap();
|
||||
} else {
|
||||
file.write_all(format!("{}{}{} `{}`\n", prefix, branch, tree.name(), tree.path()).as_bytes()).unwrap();
|
||||
}
|
||||
|
||||
// Print children if any
|
||||
if let Some(variants) = tree.variants() {
|
||||
let len = variants.len();
|
||||
for (i, variant) in variants.iter().enumerate() {
|
||||
let is_last_child = i == len - 1;
|
||||
print_tree_node(variant, &child_prefix, is_last_child, file);
|
||||
}
|
||||
}
|
||||
|
||||
// Print handler field if any
|
||||
if let Some(data) = tree.message_handler_fields() {
|
||||
let len = data.fields().len();
|
||||
let (branch, child_prefix) = if tree.has_message_handler_data_fields() {
|
||||
("├── ", format!("{}│ ", prefix))
|
||||
} else {
|
||||
("└── ", format!("{} ", prefix))
|
||||
};
|
||||
if data.path().is_empty() {
|
||||
file.write_all(format!("{}{}{}\n", prefix, branch, data.name()).as_bytes()).unwrap();
|
||||
} else {
|
||||
file.write_all(format!("{}{}{} `{}`\n", prefix, branch, data.name(), data.path()).as_bytes()).unwrap();
|
||||
}
|
||||
for (i, field) in data.fields().iter().enumerate() {
|
||||
let is_last_field = i == len - 1;
|
||||
let branch = if is_last_field { "└── " } else { "├── " };
|
||||
|
||||
file.write_all(format!("{}{}{}\n", child_prefix, branch, field.0).as_bytes()).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
// Print data field if any
|
||||
if let Some(data) = tree.message_handler_data_fields() {
|
||||
let len = data.fields().len();
|
||||
if data.path().is_empty() {
|
||||
file.write_all(format!("{}{}{}\n", prefix, "└── ", data.name()).as_bytes()).unwrap();
|
||||
} else {
|
||||
file.write_all(format!("{}{}{} `{}`\n", prefix, "└── ", data.name(), data.path()).as_bytes()).unwrap();
|
||||
}
|
||||
for (i, field) in data.fields().iter().enumerate() {
|
||||
let is_last_field = i == len - 1;
|
||||
let branch = if is_last_field { "└── " } else { "├── " };
|
||||
file.write_all(format!("{}{}{}\n", format!("{} ", prefix), branch, field.0).as_bytes()).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -182,6 +182,7 @@ pub enum DocumentMessage {
|
||||
UpdateUpstreamTransforms {
|
||||
upstream_footprints: HashMap<NodeId, Footprint>,
|
||||
local_transforms: HashMap<NodeId, DAffine2>,
|
||||
first_instance_source_id: HashMap<NodeId, Option<NodeId>>,
|
||||
},
|
||||
UpdateClickTargets {
|
||||
click_targets: HashMap<NodeId, Vec<ClickTarget>>,
|
||||
|
||||
@@ -38,6 +38,7 @@ use graphene_std::vector::click_target::{ClickTarget, ClickTargetType};
|
||||
use graphene_std::vector::style::ViewMode;
|
||||
use std::time::Duration;
|
||||
|
||||
#[derive(ExtractField)]
|
||||
pub struct DocumentMessageData<'a> {
|
||||
pub document_id: DocumentId,
|
||||
pub ipp: &'a InputPreprocessorMessageHandler,
|
||||
@@ -48,7 +49,7 @@ pub struct DocumentMessageData<'a> {
|
||||
pub device_pixel_ratio: f64,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
|
||||
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize, ExtractField)]
|
||||
#[serde(default)]
|
||||
pub struct DocumentMessageHandler {
|
||||
// ======================
|
||||
@@ -168,6 +169,7 @@ impl Default for DocumentMessageHandler {
|
||||
}
|
||||
}
|
||||
|
||||
#[message_handler_data]
|
||||
impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessageHandler {
|
||||
fn process_message(&mut self, message: DocumentMessage, responses: &mut VecDeque<Message>, data: DocumentMessageData) {
|
||||
let DocumentMessageData {
|
||||
@@ -444,6 +446,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
|
||||
DocumentMessage::EnterNestedNetwork { node_id } => {
|
||||
self.breadcrumb_network_path.push(node_id);
|
||||
self.selection_network_path.clone_from(&self.breadcrumb_network_path);
|
||||
responses.add(NodeGraphMessage::UnloadWires);
|
||||
responses.add(NodeGraphMessage::SendGraph);
|
||||
responses.add(DocumentMessage::ZoomCanvasToFitAll);
|
||||
responses.add(NodeGraphMessage::SetGridAlignedEdges);
|
||||
@@ -473,9 +476,10 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
|
||||
self.breadcrumb_network_path.pop();
|
||||
self.selection_network_path.clone_from(&self.breadcrumb_network_path);
|
||||
}
|
||||
responses.add(NodeGraphMessage::UnloadWires);
|
||||
responses.add(NodeGraphMessage::SendGraph);
|
||||
responses.add(DocumentMessage::PTZUpdate);
|
||||
responses.add(NodeGraphMessage::SetGridAlignedEdges);
|
||||
responses.add(NodeGraphMessage::SendGraph);
|
||||
}
|
||||
DocumentMessage::FlipSelectedLayers { flip_axis } => {
|
||||
let scale = match flip_axis {
|
||||
@@ -525,6 +529,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
|
||||
}
|
||||
}
|
||||
DocumentMessage::GraphViewOverlay { open } => {
|
||||
let opened = !self.graph_view_overlay_open && open;
|
||||
self.graph_view_overlay_open = open;
|
||||
|
||||
responses.add(FrontendMessage::UpdateGraphViewOverlay { open });
|
||||
@@ -537,6 +542,9 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
|
||||
|
||||
responses.add(DocumentMessage::RenderRulers);
|
||||
responses.add(DocumentMessage::RenderScrollbars);
|
||||
if opened {
|
||||
responses.add(NodeGraphMessage::UnloadWires);
|
||||
}
|
||||
if open {
|
||||
responses.add(ToolMessage::DeactivateTools);
|
||||
responses.add(OverlaysMessage::Draw); // Clear the overlays
|
||||
@@ -744,6 +752,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
|
||||
// Nudge translation without resizing
|
||||
if !resize {
|
||||
let transform = DAffine2::from_translation(DVec2::from_angle(-self.document_ptz.tilt()).rotate(DVec2::new(delta_x, delta_y)));
|
||||
responses.add(SelectToolMessage::ShiftSelectedNodes { offset: transform.translation });
|
||||
|
||||
for layer in self.network_interface.shallowest_unique_layers(&[]).filter(|layer| can_move(*layer)) {
|
||||
responses.add(GraphOperationMessage::TransformChange {
|
||||
@@ -1179,6 +1188,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
|
||||
OverlaysType::HoverOutline => visibility_settings.hover_outline = visible,
|
||||
OverlaysType::SelectionOutline => visibility_settings.selection_outline = visible,
|
||||
OverlaysType::Pivot => visibility_settings.pivot = visible,
|
||||
OverlaysType::Origin => visibility_settings.origin = visible,
|
||||
OverlaysType::Path => visibility_settings.path = visible,
|
||||
OverlaysType::Anchors => {
|
||||
visibility_settings.anchors = visible;
|
||||
@@ -1299,8 +1309,10 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
|
||||
DocumentMessage::UpdateUpstreamTransforms {
|
||||
upstream_footprints,
|
||||
local_transforms,
|
||||
first_instance_source_id,
|
||||
} => {
|
||||
self.network_interface.update_transforms(upstream_footprints, local_transforms);
|
||||
self.network_interface.update_first_instance_source_id(first_instance_source_id);
|
||||
}
|
||||
DocumentMessage::UpdateClickTargets { click_targets } => {
|
||||
// TODO: Allow non layer nodes to have click targets
|
||||
@@ -1708,6 +1720,14 @@ impl DocumentMessageHandler {
|
||||
.reduce(graphene_std::renderer::Quad::combine_bounds)
|
||||
}
|
||||
|
||||
pub fn selected_visible_and_unlock_layers_bounding_box_document(&self) -> Option<[DVec2; 2]> {
|
||||
self.network_interface
|
||||
.selected_nodes()
|
||||
.selected_visible_and_unlocked_layers(&self.network_interface)
|
||||
.map(|layer| self.metadata().nonzero_bounding_box(layer))
|
||||
.reduce(graphene_std::renderer::Quad::combine_bounds)
|
||||
}
|
||||
|
||||
pub fn document_network(&self) -> &NodeNetwork {
|
||||
self.network_interface.document_network()
|
||||
}
|
||||
@@ -1878,6 +1898,7 @@ impl DocumentMessageHandler {
|
||||
responses.add(NodeGraphMessage::SelectedNodesUpdated);
|
||||
responses.add(NodeGraphMessage::ForceRunDocumentGraph);
|
||||
// TODO: Remove once the footprint is used to load the imports/export distances from the edge
|
||||
responses.add(NodeGraphMessage::UnloadWires);
|
||||
responses.add(NodeGraphMessage::SetGridAlignedEdges);
|
||||
responses.add(Message::StartBuffer);
|
||||
Some(previous_network)
|
||||
@@ -1909,7 +1930,8 @@ impl DocumentMessageHandler {
|
||||
responses.add(PortfolioMessage::UpdateOpenDocumentsList);
|
||||
responses.add(NodeGraphMessage::SelectedNodesUpdated);
|
||||
responses.add(NodeGraphMessage::ForceRunDocumentGraph);
|
||||
|
||||
responses.add(NodeGraphMessage::UnloadWires);
|
||||
responses.add(NodeGraphMessage::SendWires);
|
||||
Some(previous_network)
|
||||
}
|
||||
|
||||
@@ -2066,7 +2088,7 @@ impl DocumentMessageHandler {
|
||||
/// Loads all of the fonts in the document.
|
||||
pub fn load_layer_resources(&self, responses: &mut VecDeque<Message>) {
|
||||
let mut fonts = HashSet::new();
|
||||
for (_node_id, node) in self.document_network().recursive_nodes() {
|
||||
for (_node_id, node, _) in self.document_network().recursive_nodes() {
|
||||
for input in &node.inputs {
|
||||
if let Some(TaggedValue::Font(font)) = input.as_value() {
|
||||
fonts.insert(font.clone());
|
||||
@@ -2259,6 +2281,24 @@ impl DocumentMessageHandler {
|
||||
]
|
||||
},
|
||||
},
|
||||
LayoutGroup::Row {
|
||||
widgets: {
|
||||
let mut checkbox_id = CheckboxId::default();
|
||||
vec![
|
||||
CheckboxInput::new(self.overlays_visibility_settings.pivot)
|
||||
.on_update(|optional_input: &CheckboxInput| {
|
||||
DocumentMessage::SetOverlaysVisibility {
|
||||
visible: optional_input.checked,
|
||||
overlays_type: Some(OverlaysType::Origin),
|
||||
}
|
||||
.into()
|
||||
})
|
||||
.for_label(checkbox_id.clone())
|
||||
.widget_holder(),
|
||||
TextLabel::new("Transform Origin".to_string()).for_checkbox(&mut checkbox_id).widget_holder(),
|
||||
]
|
||||
},
|
||||
},
|
||||
LayoutGroup::Row {
|
||||
widgets: {
|
||||
let mut checkbox_id = CheckboxId::default();
|
||||
|
||||
@@ -3,7 +3,7 @@ use crate::messages::portfolio::document::utility_types::document_metadata::Laye
|
||||
use crate::messages::portfolio::document::utility_types::network_interface::NodeTemplate;
|
||||
use crate::messages::prelude::*;
|
||||
use bezier_rs::Subpath;
|
||||
use glam::{DAffine2, DVec2, IVec2};
|
||||
use glam::{DAffine2, IVec2};
|
||||
use graph_craft::document::NodeId;
|
||||
use graphene_std::Artboard;
|
||||
use graphene_std::brush::brush_stroke::BrushStroke;
|
||||
@@ -52,10 +52,6 @@ pub enum GraphOperationMessage {
|
||||
transform_in: TransformIn,
|
||||
skip_rerender: bool,
|
||||
},
|
||||
TransformSetPivot {
|
||||
layer: LayerNodeIdentifier,
|
||||
pivot: DVec2,
|
||||
},
|
||||
Vector {
|
||||
layer: LayerNodeIdentifier,
|
||||
modification_type: VectorModificationType,
|
||||
|
||||
+3
-10
@@ -21,17 +21,19 @@ struct ArtboardInfo {
|
||||
merge_node: NodeId,
|
||||
}
|
||||
|
||||
#[derive(ExtractField)]
|
||||
pub struct GraphOperationMessageData<'a> {
|
||||
pub network_interface: &'a mut NodeNetworkInterface,
|
||||
pub collapsed: &'a mut CollapsedLayers,
|
||||
pub node_graph: &'a mut NodeGraphMessageHandler,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Default, serde::Serialize, serde::Deserialize)]
|
||||
#[derive(Debug, Clone, PartialEq, Default, serde::Serialize, serde::Deserialize, ExtractField)]
|
||||
pub struct GraphOperationMessageHandler {}
|
||||
|
||||
// GraphOperationMessageHandler always modified the document network. This is so changes to the layers panel will only affect the document network.
|
||||
// For changes to the selected network, use NodeGraphMessageHandler. No NodeGraphMessage's should be added here, since they will affect the selected nested network.
|
||||
#[message_handler_data]
|
||||
impl MessageHandler<GraphOperationMessage, GraphOperationMessageData<'_>> for GraphOperationMessageHandler {
|
||||
fn process_message(&mut self, message: GraphOperationMessage, responses: &mut VecDeque<Message>, data: GraphOperationMessageData) {
|
||||
let network_interface = data.network_interface;
|
||||
@@ -89,15 +91,6 @@ impl MessageHandler<GraphOperationMessage, GraphOperationMessageData<'_>> for Gr
|
||||
modify_inputs.transform_set(transform, transform_in, skip_rerender);
|
||||
}
|
||||
}
|
||||
GraphOperationMessage::TransformSetPivot { layer, pivot } => {
|
||||
if layer == LayerNodeIdentifier::ROOT_PARENT {
|
||||
log::error!("Cannot run TransformSetPivot on ROOT_PARENT");
|
||||
return;
|
||||
}
|
||||
if let Some(mut modify_inputs) = ModifyInputsContext::new_with_layer(layer, network_interface, responses) {
|
||||
modify_inputs.pivot_set(pivot);
|
||||
}
|
||||
}
|
||||
GraphOperationMessage::Vector { layer, modification_type } => {
|
||||
if layer == LayerNodeIdentifier::ROOT_PARENT {
|
||||
log::error!("Cannot run Vector on ROOT_PARENT");
|
||||
|
||||
@@ -4,7 +4,7 @@ use crate::messages::portfolio::document::utility_types::document_metadata::Laye
|
||||
use crate::messages::portfolio::document::utility_types::network_interface::{self, InputConnector, NodeNetworkInterface, OutputConnector};
|
||||
use crate::messages::prelude::*;
|
||||
use bezier_rs::Subpath;
|
||||
use glam::{DAffine2, DVec2, IVec2};
|
||||
use glam::{DAffine2, IVec2};
|
||||
use graph_craft::concrete;
|
||||
use graph_craft::document::value::TaggedValue;
|
||||
use graph_craft::document::{NodeId, NodeInput};
|
||||
@@ -97,6 +97,8 @@ impl<'a> ModifyInputsContext<'a> {
|
||||
};
|
||||
}
|
||||
|
||||
let layer_input_connector = post_node_input_connector.clone();
|
||||
|
||||
// Sink post_node down to the end of the non layer chain that feeds into post_node, such that pre_node is the layer node at insert_index + 1, or None if insert_index is the last layer
|
||||
loop {
|
||||
let pre_node_output_connector = network_interface.upstream_output_connector(&post_node_input_connector, &[]);
|
||||
@@ -105,6 +107,11 @@ impl<'a> ModifyInputsContext<'a> {
|
||||
Some(OutputConnector::Node { node_id: pre_node_id, .. }) if !network_interface.is_layer(&pre_node_id, &[]) => {
|
||||
// Update post_node_input_connector for the next iteration
|
||||
post_node_input_connector = InputConnector::node(pre_node_id, 0);
|
||||
// Insert directly under layer if moving to the end of a layer stack that ends with a non layer node that does not have an exposed primary input
|
||||
let primary_is_exposed = network_interface.input_from_connector(&post_node_input_connector, &[]).is_some_and(|input| input.is_exposed());
|
||||
if !primary_is_exposed {
|
||||
return layer_input_connector;
|
||||
}
|
||||
}
|
||||
_ => break, // Break if pre_node_output_connector is None or if pre_node_id is a layer
|
||||
}
|
||||
@@ -451,12 +458,6 @@ impl<'a> ModifyInputsContext<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn pivot_set(&mut self, new_pivot: DVec2) {
|
||||
let Some(transform_node_id) = self.existing_node_id("Transform", true) else { return };
|
||||
|
||||
self.set_input_with_refresh(InputConnector::node(transform_node_id, 5), NodeInput::value(TaggedValue::DVec2(new_pivot), 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);
|
||||
|
||||
@@ -13,6 +13,7 @@ use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo};
|
||||
use glam::{DAffine2, DVec2};
|
||||
use graph_craft::document::NodeId;
|
||||
|
||||
#[derive(ExtractField)]
|
||||
pub struct NavigationMessageData<'a> {
|
||||
pub network_interface: &'a mut NodeNetworkInterface,
|
||||
pub breadcrumb_network_path: &'a [NodeId],
|
||||
@@ -23,7 +24,7 @@ pub struct NavigationMessageData<'a> {
|
||||
pub preferences: &'a PreferencesMessageHandler,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Default)]
|
||||
#[derive(Debug, Clone, PartialEq, Default, ExtractField)]
|
||||
pub struct NavigationMessageHandler {
|
||||
navigation_operation: NavigationOperation,
|
||||
mouse_position: ViewportPosition,
|
||||
@@ -31,6 +32,7 @@ pub struct NavigationMessageHandler {
|
||||
abortable_pan_start: Option<f64>,
|
||||
}
|
||||
|
||||
#[message_handler_data]
|
||||
impl MessageHandler<NavigationMessage, NavigationMessageData<'_>> for NavigationMessageHandler {
|
||||
fn process_message(&mut self, message: NavigationMessage, responses: &mut VecDeque<Message>, data: NavigationMessageData) {
|
||||
let NavigationMessageData {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
+7
-7
@@ -1,5 +1,5 @@
|
||||
use super::DocumentNodeDefinition;
|
||||
use crate::messages::portfolio::document::utility_types::network_interface::{DocumentNodePersistentMetadata, NodeTemplate, PropertiesRow, WidgetOverride};
|
||||
use crate::messages::portfolio::document::utility_types::network_interface::{DocumentNodePersistentMetadata, InputMetadata, NodeTemplate, WidgetOverride};
|
||||
use graph_craft::ProtoNodeIdentifier;
|
||||
use graph_craft::document::*;
|
||||
use graphene_std::registry::*;
|
||||
@@ -21,7 +21,7 @@ pub(super) fn post_process_nodes(mut custom: Vec<DocumentNodeDefinition>) -> Vec
|
||||
};
|
||||
}
|
||||
|
||||
let node_registry = graphene_core::registry::NODE_REGISTRY.lock().unwrap();
|
||||
let node_registry = NODE_REGISTRY.lock().unwrap();
|
||||
'outer: for (id, metadata) in NODE_METADATA.lock().unwrap().iter() {
|
||||
for node in custom.iter() {
|
||||
let DocumentNodeDefinition {
|
||||
@@ -32,7 +32,7 @@ pub(super) fn post_process_nodes(mut custom: Vec<DocumentNodeDefinition>) -> Vec
|
||||
..
|
||||
} = node;
|
||||
match implementation {
|
||||
DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier { name }) if name == id => continue 'outer,
|
||||
DocumentNodeImplementation::ProtoNode(name) if name == id => continue 'outer,
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
@@ -67,13 +67,13 @@ pub(super) fn post_process_nodes(mut custom: Vec<DocumentNodeDefinition>) -> Vec
|
||||
},
|
||||
persistent_node_metadata: DocumentNodePersistentMetadata {
|
||||
// TODO: Store information for input overrides in the node macro
|
||||
input_properties: fields
|
||||
input_metadata: fields
|
||||
.iter()
|
||||
.map(|f| match f.widget_override {
|
||||
RegistryWidgetOverride::None => (f.name, f.description).into(),
|
||||
RegistryWidgetOverride::Hidden => PropertiesRow::with_override(f.name, f.description, WidgetOverride::Hidden),
|
||||
RegistryWidgetOverride::String(str) => PropertiesRow::with_override(f.name, f.description, WidgetOverride::String(str.to_string())),
|
||||
RegistryWidgetOverride::Custom(str) => PropertiesRow::with_override(f.name, f.description, WidgetOverride::Custom(str.to_string())),
|
||||
RegistryWidgetOverride::Hidden => InputMetadata::with_name_description_override(f.name, f.description, WidgetOverride::Hidden),
|
||||
RegistryWidgetOverride::String(str) => InputMetadata::with_name_description_override(f.name, f.description, WidgetOverride::String(str.to_string())),
|
||||
RegistryWidgetOverride::Custom(str) => InputMetadata::with_name_description_override(f.name, f.description, WidgetOverride::Custom(str.to_string())),
|
||||
})
|
||||
.collect(),
|
||||
output_names: vec![output_type.to_string()],
|
||||
|
||||
@@ -33,6 +33,7 @@ pub enum NodeGraphMessage {
|
||||
node_id: Option<NodeId>,
|
||||
node_type: String,
|
||||
xy: Option<(i32, i32)>,
|
||||
add_transaction: bool,
|
||||
},
|
||||
CreateWire {
|
||||
output_connector: OutputConnector,
|
||||
@@ -123,6 +124,9 @@ pub enum NodeGraphMessage {
|
||||
},
|
||||
SendClickTargets,
|
||||
EndSendClickTargets,
|
||||
UnloadWires,
|
||||
SendWires,
|
||||
UpdateVisibleNodes,
|
||||
SendGraph,
|
||||
SetGridAlignedEdges,
|
||||
SetInputValue {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use super::utility_types::{BoxSelection, ContextMenuInformation, DragStart, FrontendGraphInput, FrontendGraphOutput, FrontendNode, FrontendNodeWire, WirePath};
|
||||
use super::utility_types::{BoxSelection, ContextMenuInformation, DragStart, FrontendGraphInput, FrontendGraphOutput, FrontendNode};
|
||||
use super::{document_node_definitions, node_properties};
|
||||
use crate::consts::GRID_SIZE;
|
||||
use crate::messages::input_mapper::utility_types::macros::action_keys;
|
||||
@@ -13,6 +13,7 @@ use crate::messages::portfolio::document::utility_types::network_interface::{
|
||||
self, InputConnector, NodeNetworkInterface, NodeTemplate, NodeTypePersistentMetadata, OutputConnector, Previewing, TypeSource,
|
||||
};
|
||||
use crate::messages::portfolio::document::utility_types::nodes::{CollapsedLayers, LayerPanelEntry};
|
||||
use crate::messages::portfolio::document::utility_types::wires::{GraphWireStyle, WirePath, WirePathUpdate, build_vector_wire};
|
||||
use crate::messages::prelude::*;
|
||||
use crate::messages::tool::common_functionality::auto_panning::AutoPanning;
|
||||
use crate::messages::tool::common_functionality::graph_modification_utils::get_clip_mode;
|
||||
@@ -26,7 +27,7 @@ use graphene_std::*;
|
||||
use renderer::Quad;
|
||||
use std::cmp::Ordering;
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, ExtractField)]
|
||||
pub struct NodeGraphHandlerData<'a> {
|
||||
pub network_interface: &'a mut NodeNetworkInterface,
|
||||
pub selection_network_path: &'a [NodeId],
|
||||
@@ -40,7 +41,7 @@ pub struct NodeGraphHandlerData<'a> {
|
||||
pub preferences: &'a PreferencesMessageHandler,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, ExtractField)]
|
||||
pub struct NodeGraphMessageHandler {
|
||||
// TODO: Remove network and move to NodeNetworkInterface
|
||||
pub network: Vec<NodeId>,
|
||||
@@ -67,6 +68,7 @@ pub struct NodeGraphMessageHandler {
|
||||
select_if_not_dragged: Option<NodeId>,
|
||||
/// The start of the dragged line (cannot be moved), stored in node graph coordinates
|
||||
pub wire_in_progress_from_connector: Option<DVec2>,
|
||||
wire_in_progress_type: FrontendGraphDataType,
|
||||
/// The end point of the dragged line (cannot be moved), stored in node graph coordinates
|
||||
pub wire_in_progress_to_connector: Option<DVec2>,
|
||||
/// State for the context menu popups.
|
||||
@@ -77,15 +79,20 @@ pub struct NodeGraphMessageHandler {
|
||||
auto_panning: AutoPanning,
|
||||
/// The node to preview on mouse up if alt-clicked
|
||||
preview_on_mouse_up: Option<NodeId>,
|
||||
// The index of the import that is being moved
|
||||
/// The index of the import that is being moved
|
||||
reordering_import: Option<usize>,
|
||||
// The index of the export that is being moved
|
||||
/// The index of the export that is being moved
|
||||
reordering_export: Option<usize>,
|
||||
// The end index of the moved port
|
||||
/// The end index of the moved port
|
||||
end_index: Option<usize>,
|
||||
/// Used to keep track of what nodes are sent to the front end so that only visible ones are sent to the frontend
|
||||
frontend_nodes: Vec<NodeId>,
|
||||
/// Used to keep track of what wires are sent to the front end so the old ones can be removed
|
||||
frontend_wires: HashSet<(NodeId, usize)>,
|
||||
}
|
||||
|
||||
/// 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.
|
||||
#[message_handler_data]
|
||||
impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGraphMessageHandler {
|
||||
fn process_message(&mut self, message: NodeGraphMessage, responses: &mut VecDeque<Message>, data: NodeGraphHandlerData<'a>) {
|
||||
let NodeGraphHandlerData {
|
||||
@@ -175,7 +182,12 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
||||
responses.add(PropertiesPanelMessage::Refresh);
|
||||
responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||
}
|
||||
NodeGraphMessage::CreateNodeFromContextMenu { node_id, node_type, xy } => {
|
||||
NodeGraphMessage::CreateNodeFromContextMenu {
|
||||
node_id,
|
||||
node_type,
|
||||
xy,
|
||||
add_transaction,
|
||||
} => {
|
||||
let (x, y) = if let Some((x, y)) = xy {
|
||||
(x, y)
|
||||
} else if let Some(node_graph_ptz) = network_interface.node_graph_ptz(breadcrumb_network_path) {
|
||||
@@ -197,7 +209,10 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
||||
let node_template = document_node_type.default_node_template();
|
||||
self.context_menu = None;
|
||||
|
||||
responses.add(DocumentMessage::AddTransaction);
|
||||
if add_transaction {
|
||||
responses.add(DocumentMessage::AddTransaction);
|
||||
}
|
||||
|
||||
responses.add(NodeGraphMessage::InsertNode {
|
||||
node_id,
|
||||
node_template: node_template.clone(),
|
||||
@@ -220,13 +235,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
||||
};
|
||||
|
||||
// Ensure connection is to correct input of new node. If it does not have an input then do not connect
|
||||
if let Some((input_index, _)) = node_template
|
||||
.document_node
|
||||
.inputs
|
||||
.iter()
|
||||
.enumerate()
|
||||
.find(|(_, input)| input.is_exposed_to_frontend(selection_network_path.is_empty()))
|
||||
{
|
||||
if let Some((input_index, _)) = node_template.document_node.inputs.iter().enumerate().find(|(_, input)| input.is_exposed()) {
|
||||
responses.add(NodeGraphMessage::CreateWire {
|
||||
output_connector: *output_connector,
|
||||
input_connector: InputConnector::node(node_id, input_index),
|
||||
@@ -236,6 +245,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
||||
}
|
||||
|
||||
self.wire_in_progress_from_connector = None;
|
||||
self.wire_in_progress_type = FrontendGraphDataType::General;
|
||||
self.wire_in_progress_to_connector = None;
|
||||
}
|
||||
responses.add(FrontendMessage::UpdateWirePathInProgress { wire_path: None });
|
||||
@@ -367,9 +377,14 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
||||
responses.add(DocumentMessage::CommitTransaction);
|
||||
|
||||
// Update the graph UI and re-render
|
||||
responses.add(PropertiesPanelMessage::Refresh);
|
||||
responses.add(NodeGraphMessage::SendGraph);
|
||||
responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||
if graph_view_overlay_open {
|
||||
responses.add(PropertiesPanelMessage::Refresh);
|
||||
responses.add(NodeGraphMessage::SendGraph);
|
||||
} else {
|
||||
responses.add(DocumentMessage::GraphViewOverlay { open: true });
|
||||
responses.add(NavigationMessage::FitViewportToSelection);
|
||||
responses.add(DocumentMessage::ZoomCanvasTo100Percent);
|
||||
}
|
||||
}
|
||||
NodeGraphMessage::InsertNode { node_id, node_template } => {
|
||||
network_interface.insert_node(node_id, node_template, selection_network_path);
|
||||
@@ -629,6 +644,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
||||
// Abort dragging a wire
|
||||
if self.wire_in_progress_from_connector.is_some() {
|
||||
self.wire_in_progress_from_connector = None;
|
||||
self.wire_in_progress_type = FrontendGraphDataType::General;
|
||||
self.wire_in_progress_to_connector = None;
|
||||
responses.add(DocumentMessage::AbortTransaction);
|
||||
responses.add(FrontendMessage::UpdateWirePathInProgress { wire_path: None });
|
||||
@@ -707,6 +723,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
||||
if self.context_menu.is_some() {
|
||||
self.context_menu = None;
|
||||
self.wire_in_progress_from_connector = None;
|
||||
self.wire_in_progress_type = FrontendGraphDataType::General;
|
||||
self.wire_in_progress_to_connector = None;
|
||||
responses.add(FrontendMessage::UpdateContextMenuInformation {
|
||||
context_menu_information: self.context_menu.clone(),
|
||||
@@ -740,6 +757,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
||||
};
|
||||
let Some(output_connector) = output_connector else { return };
|
||||
self.wire_in_progress_from_connector = network_interface.output_position(&output_connector, selection_network_path);
|
||||
self.wire_in_progress_type = FrontendGraphDataType::from_type(&network_interface.input_type(clicked_input, breadcrumb_network_path).0);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -749,6 +767,15 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
||||
self.initial_disconnecting = false;
|
||||
|
||||
self.wire_in_progress_from_connector = network_interface.output_position(&clicked_output, selection_network_path);
|
||||
if let Some((output_type, source)) = clicked_output
|
||||
.node_id()
|
||||
.map(|node_id| network_interface.output_type(&node_id, clicked_output.index(), breadcrumb_network_path))
|
||||
{
|
||||
self.wire_in_progress_type = FrontendGraphDataType::displayed_type(&output_type, &source);
|
||||
} else {
|
||||
self.wire_in_progress_type = FrontendGraphDataType::General;
|
||||
}
|
||||
|
||||
self.update_node_graph_hints(responses);
|
||||
return;
|
||||
}
|
||||
@@ -895,9 +922,18 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
||||
false
|
||||
}
|
||||
});
|
||||
let vector_wire = build_vector_wire(
|
||||
wire_in_progress_from_connector,
|
||||
wire_in_progress_to_connector,
|
||||
from_connector_is_layer,
|
||||
to_connector_is_layer,
|
||||
GraphWireStyle::Direct,
|
||||
);
|
||||
let mut path_string = String::new();
|
||||
let _ = vector_wire.subpath_to_svg(&mut path_string, DAffine2::IDENTITY);
|
||||
let wire_path = WirePath {
|
||||
path_string: Self::build_wire_path_string(wire_in_progress_from_connector, wire_in_progress_to_connector, from_connector_is_layer, to_connector_is_layer),
|
||||
data_type: FrontendGraphDataType::General,
|
||||
path_string,
|
||||
data_type: self.wire_in_progress_type,
|
||||
thick: false,
|
||||
dashed: false,
|
||||
};
|
||||
@@ -941,7 +977,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
||||
self.update_node_graph_hints(responses);
|
||||
} else if self.reordering_import.is_some() {
|
||||
let Some(modify_import_export) = network_interface.modify_import_export(selection_network_path) else {
|
||||
log::error!("Could not get modify import export in PointerUp");
|
||||
log::error!("Could not get modify import export in PointerMove");
|
||||
return;
|
||||
};
|
||||
// Find the first import that is below the mouse position
|
||||
@@ -961,7 +997,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
||||
responses.add(FrontendMessage::UpdateImportReorderIndex { index: self.end_index });
|
||||
} else if self.reordering_export.is_some() {
|
||||
let Some(modify_import_export) = network_interface.modify_import_export(selection_network_path) else {
|
||||
log::error!("Could not get modify import export in PointerUp");
|
||||
log::error!("Could not get modify import export in PointerMove");
|
||||
return;
|
||||
};
|
||||
// Find the first export that is below the mouse position
|
||||
@@ -1043,15 +1079,13 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
||||
// Get the compatible type from the output connector
|
||||
let compatible_type = output_connector.and_then(|output_connector| {
|
||||
output_connector.node_id().and_then(|node_id| {
|
||||
let output_index = output_connector.index();
|
||||
// Get the output types from the network interface
|
||||
let output_types = network_interface.output_types(&node_id, selection_network_path);
|
||||
let (output_type, type_source) = network_interface.output_type(&node_id, output_connector.index(), selection_network_path);
|
||||
|
||||
// Extract the type if available
|
||||
output_types.get(output_index).and_then(|type_option| type_option.as_ref()).map(|(output_type, _)| {
|
||||
// Create a search term based on the type
|
||||
format!("type:{}", output_type.clone().nested_type())
|
||||
})
|
||||
match type_source {
|
||||
TypeSource::RandomProtonodeImplementation | TypeSource::Error(_) => None,
|
||||
_ => Some(format!("type:{}", output_type.nested_type())),
|
||||
}
|
||||
})
|
||||
});
|
||||
let appear_right_of_mouse = if ipp.mouse.position.x > ipp.viewport_bounds.size().x - 173. { -173. } else { 0. };
|
||||
@@ -1117,107 +1151,56 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
||||
let has_primary_output_connection = network_interface
|
||||
.outward_wires(selection_network_path)
|
||||
.is_some_and(|outward_wires| outward_wires.get(&OutputConnector::node(selected_node_id, 0)).is_some_and(|outward_wires| !outward_wires.is_empty()));
|
||||
let Some(network) = network_interface.nested_network(selection_network_path) else {
|
||||
return;
|
||||
};
|
||||
if let Some(selected_node) = network.nodes.get(&selected_node_id) {
|
||||
// Check if any downstream node has any input that feeds into the primary export of the selected node
|
||||
let primary_input_is_value = selected_node.inputs.first().is_some_and(|first_input| first_input.as_value().is_some());
|
||||
// Check that neither the primary input or output of the selected node are already connected.
|
||||
if !has_primary_output_connection && primary_input_is_value {
|
||||
if !has_primary_output_connection {
|
||||
let Some(network) = network_interface.nested_network(selection_network_path) else {
|
||||
return;
|
||||
};
|
||||
let Some(selected_node) = network.nodes.get(&selected_node_id) else {
|
||||
return;
|
||||
};
|
||||
// Check that the first visible input is disconnected
|
||||
let selected_node_input_connect_index = selected_node
|
||||
.inputs
|
||||
.iter()
|
||||
.enumerate()
|
||||
.find(|input| input.1.is_exposed())
|
||||
.filter(|input| input.1.as_value().is_some())
|
||||
.map(|input| input.0);
|
||||
if let Some(selected_node_input_connect_index) = selected_node_input_connect_index {
|
||||
let Some(bounding_box) = network_interface.node_bounding_box(&selected_node_id, selection_network_path) else {
|
||||
log::error!("Could not get bounding box for node: {selected_node_id}");
|
||||
return;
|
||||
};
|
||||
// TODO: Cache all wire locations if this is a performance issue
|
||||
let overlapping_wires = Self::collect_wires(network_interface, selection_network_path)
|
||||
.into_iter()
|
||||
.filter(|frontend_wire| {
|
||||
// Prevent inserting on a link that is connected upstream to the selected node
|
||||
if network_interface
|
||||
.upstream_flow_back_from_nodes(vec![selected_node_id], selection_network_path, network_interface::FlowType::UpstreamFlow)
|
||||
.any(|upstream_id| {
|
||||
frontend_wire.wire_end.node_id().is_some_and(|wire_end_id| wire_end_id == upstream_id)
|
||||
|| frontend_wire.wire_start.node_id().is_some_and(|wire_start_id| wire_start_id == upstream_id)
|
||||
}) {
|
||||
return false;
|
||||
}
|
||||
let mut wires_to_check = network_interface.node_graph_input_connectors(selection_network_path).into_iter().collect::<HashSet<_>>();
|
||||
// Prevent inserting on a link that is connected upstream to the selected node
|
||||
for upstream_node in network_interface.upstream_flow_back_from_nodes(vec![selected_node_id], selection_network_path, network_interface::FlowType::UpstreamFlow) {
|
||||
for input_index in 0..network_interface.number_of_inputs(&upstream_node, selection_network_path) {
|
||||
wires_to_check.remove(&InputConnector::node(upstream_node, input_index));
|
||||
}
|
||||
}
|
||||
|
||||
let overlapping_wires = wires_to_check
|
||||
.into_iter()
|
||||
.filter_map(|input| {
|
||||
// Prevent inserting a layer into a chain
|
||||
if network_interface.is_layer(&selected_node_id, selection_network_path)
|
||||
&& frontend_wire
|
||||
.wire_start
|
||||
.node_id()
|
||||
.is_some_and(|wire_start_id| network_interface.is_chain(&wire_start_id, selection_network_path))
|
||||
&& input.node_id().is_some_and(|input_node_id| network_interface.is_chain(&input_node_id, selection_network_path))
|
||||
{
|
||||
return false;
|
||||
return None;
|
||||
}
|
||||
|
||||
let Some(input_position) = network_interface.input_position(&frontend_wire.wire_end, selection_network_path) else {
|
||||
log::error!("Could not get input port position for {:?}", frontend_wire.wire_end);
|
||||
return false;
|
||||
};
|
||||
|
||||
let Some(output_position) = network_interface.output_position(&frontend_wire.wire_start, selection_network_path) else {
|
||||
log::error!("Could not get output port position for {:?}", frontend_wire.wire_start);
|
||||
return false;
|
||||
};
|
||||
|
||||
let start_node_is_layer = frontend_wire
|
||||
.wire_end
|
||||
.node_id()
|
||||
.is_some_and(|wire_start_id| network_interface.is_layer(&wire_start_id, selection_network_path));
|
||||
let end_node_is_layer = frontend_wire
|
||||
.wire_end
|
||||
.node_id()
|
||||
.is_some_and(|wire_end_id| network_interface.is_layer(&wire_end_id, selection_network_path));
|
||||
|
||||
let locations = Self::build_wire_path_locations(output_position, input_position, start_node_is_layer, end_node_is_layer);
|
||||
let bezier = bezier_rs::Bezier::from_cubic_dvec2(
|
||||
(locations[0].x, locations[0].y).into(),
|
||||
(locations[1].x, locations[1].y).into(),
|
||||
(locations[2].x, locations[2].y).into(),
|
||||
(locations[3].x, locations[3].y).into(),
|
||||
);
|
||||
|
||||
!bezier.rectangle_intersections(bounding_box[0], bounding_box[1]).is_empty() || bezier.is_contained_within(bounding_box[0], bounding_box[1])
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.into_iter()
|
||||
.filter_map(|mut wire| {
|
||||
if let Some(end_node_id) = wire.wire_end.node_id() {
|
||||
let Some(actual_index_from_exposed) = (0..network_interface.number_of_inputs(&end_node_id, selection_network_path))
|
||||
.filter(|&input_index| {
|
||||
network_interface
|
||||
.input_from_connector(&InputConnector::Node { node_id: end_node_id, input_index }, selection_network_path)
|
||||
.is_some_and(|input| input.is_exposed_to_frontend(selection_network_path.is_empty()))
|
||||
})
|
||||
.nth(wire.wire_end.input_index())
|
||||
else {
|
||||
log::error!("Could not get exposed input index for {:?}", wire.wire_end);
|
||||
return None;
|
||||
};
|
||||
wire.wire_end = InputConnector::Node {
|
||||
node_id: end_node_id,
|
||||
input_index: actual_index_from_exposed,
|
||||
};
|
||||
}
|
||||
Some(wire)
|
||||
let (wire, is_stack) = network_interface.vector_wire_from_input(&input, preferences.graph_wire_style, selection_network_path)?;
|
||||
wire.rectangle_intersections_exist(bounding_box[0], bounding_box[1]).then_some((input, is_stack))
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let is_stack_wire = |wire: &FrontendNodeWire| match (wire.wire_start.node_id(), wire.wire_end.node_id(), wire.wire_end.input_index()) {
|
||||
(Some(start_id), Some(end_id), input_index) => {
|
||||
input_index == 0 && network_interface.is_layer(&start_id, selection_network_path) && network_interface.is_layer(&end_id, selection_network_path)
|
||||
}
|
||||
_ => false,
|
||||
};
|
||||
|
||||
// Prioritize vertical thick lines and cancel if there are multiple potential wires
|
||||
let mut node_wires = Vec::new();
|
||||
let mut stack_wires = Vec::new();
|
||||
for wire in overlapping_wires {
|
||||
if is_stack_wire(&wire) { stack_wires.push(wire) } else { node_wires.push(wire) }
|
||||
for (overlapping_wire_input, is_stack) in overlapping_wires {
|
||||
if is_stack {
|
||||
stack_wires.push(overlapping_wire_input)
|
||||
} else {
|
||||
node_wires.push(overlapping_wire_input)
|
||||
}
|
||||
}
|
||||
|
||||
let overlapping_wire = if network_interface.is_layer(&selected_node_id, selection_network_path) {
|
||||
@@ -1234,29 +1217,13 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
||||
None
|
||||
};
|
||||
if let Some(overlapping_wire) = overlapping_wire {
|
||||
let Some(network) = network_interface.nested_network(selection_network_path) else {
|
||||
return;
|
||||
};
|
||||
// Ensure connection is to first visible input of selected node. If it does not have an input then do not connect
|
||||
if let Some((selected_node_input_index, _)) = network
|
||||
.nodes
|
||||
.get(&selected_node_id)
|
||||
.unwrap()
|
||||
.inputs
|
||||
.iter()
|
||||
.enumerate()
|
||||
.find(|(_, input)| input.is_exposed_to_frontend(selection_network_path.is_empty()))
|
||||
{
|
||||
responses.add(NodeGraphMessage::InsertNodeBetween {
|
||||
node_id: selected_node_id,
|
||||
input_connector: overlapping_wire.wire_end,
|
||||
insert_node_input_index: selected_node_input_index,
|
||||
});
|
||||
|
||||
responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||
|
||||
responses.add(NodeGraphMessage::SendGraph);
|
||||
}
|
||||
responses.add(NodeGraphMessage::InsertNodeBetween {
|
||||
node_id: selected_node_id,
|
||||
input_connector: *overlapping_wire,
|
||||
insert_node_input_index: selected_node_input_connect_index,
|
||||
});
|
||||
responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||
responses.add(NodeGraphMessage::SendGraph);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1283,6 +1250,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
||||
self.begin_dragging = false;
|
||||
self.box_selection_start = None;
|
||||
self.wire_in_progress_from_connector = None;
|
||||
self.wire_in_progress_type = FrontendGraphDataType::General;
|
||||
self.wire_in_progress_to_connector = None;
|
||||
self.reordering_export = None;
|
||||
self.reordering_import = None;
|
||||
@@ -1357,23 +1325,52 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
||||
click_targets: Some(network_interface.collect_frontend_click_targets(breadcrumb_network_path)),
|
||||
}),
|
||||
NodeGraphMessage::EndSendClickTargets => responses.add(FrontendMessage::UpdateClickTargets { click_targets: None }),
|
||||
NodeGraphMessage::UnloadWires => {
|
||||
for input in network_interface.node_graph_input_connectors(breadcrumb_network_path) {
|
||||
network_interface.unload_wire(&input, breadcrumb_network_path);
|
||||
}
|
||||
|
||||
responses.add(FrontendMessage::ClearAllNodeGraphWires);
|
||||
}
|
||||
NodeGraphMessage::SendWires => {
|
||||
let wires = self.collect_wires(network_interface, preferences.graph_wire_style, breadcrumb_network_path);
|
||||
responses.add(FrontendMessage::UpdateNodeGraphWires { wires });
|
||||
}
|
||||
NodeGraphMessage::UpdateVisibleNodes => {
|
||||
let Some(network_metadata) = network_interface.network_metadata(breadcrumb_network_path) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let viewport_bbox = ipp.document_bounds();
|
||||
let document_bbox: [DVec2; 2] = viewport_bbox.map(|p| network_metadata.persistent_metadata.navigation_metadata.node_graph_to_viewport.inverse().transform_point2(p));
|
||||
|
||||
let mut nodes = Vec::new();
|
||||
for node_id in &self.frontend_nodes {
|
||||
let Some(node_bbox) = network_interface.node_bounding_box(node_id, breadcrumb_network_path) else {
|
||||
log::error!("Could not get bbox for node: {:?}", node_id);
|
||||
continue;
|
||||
};
|
||||
|
||||
if node_bbox[1].x >= document_bbox[0].x && node_bbox[0].x <= document_bbox[1].x && node_bbox[1].y >= document_bbox[0].y && node_bbox[0].y <= document_bbox[1].y {
|
||||
nodes.push(*node_id);
|
||||
}
|
||||
}
|
||||
|
||||
responses.add(FrontendMessage::UpdateVisibleNodes { nodes });
|
||||
}
|
||||
NodeGraphMessage::SendGraph => {
|
||||
responses.add(NodeGraphMessage::UpdateLayerPanel);
|
||||
responses.add(DocumentMessage::DocumentStructureChanged);
|
||||
responses.add(PropertiesPanelMessage::Refresh);
|
||||
if breadcrumb_network_path == selection_network_path && graph_view_overlay_open {
|
||||
// TODO: Implement culling of nodes and wires whose bounding boxes are outside of the viewport
|
||||
let wires = Self::collect_wires(network_interface, breadcrumb_network_path);
|
||||
let nodes = self.collect_nodes(network_interface, breadcrumb_network_path);
|
||||
self.frontend_nodes = nodes.iter().map(|node| node.id).collect();
|
||||
responses.add(FrontendMessage::UpdateNodeGraphNodes { nodes });
|
||||
responses.add(NodeGraphMessage::UpdateVisibleNodes);
|
||||
|
||||
let (layer_widths, chain_widths, has_left_input_wire) = network_interface.collect_layer_widths(breadcrumb_network_path);
|
||||
let wires_direct_not_grid_aligned = preferences.graph_wire_style.is_direct();
|
||||
|
||||
responses.add(NodeGraphMessage::UpdateImportsExports);
|
||||
responses.add(FrontendMessage::UpdateNodeGraph {
|
||||
nodes,
|
||||
wires,
|
||||
wires_direct_not_grid_aligned,
|
||||
});
|
||||
responses.add(FrontendMessage::UpdateLayerWidths {
|
||||
layer_widths,
|
||||
chain_widths,
|
||||
@@ -1455,6 +1452,8 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
||||
Ordering::Equal => {}
|
||||
}
|
||||
}
|
||||
|
||||
responses.add(NodeGraphMessage::SendWires);
|
||||
}
|
||||
NodeGraphMessage::ToggleSelectedAsLayersOrNodes => {
|
||||
let Some(selected_nodes) = network_interface.selected_nodes_in_nested_network(selection_network_path) else {
|
||||
@@ -1474,6 +1473,8 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
||||
}
|
||||
NodeGraphMessage::ShiftNodePosition { node_id, x, y } => {
|
||||
network_interface.shift_absolute_node_position(&node_id, IVec2::new(x, y), selection_network_path);
|
||||
|
||||
responses.add(NodeGraphMessage::SendWires);
|
||||
}
|
||||
NodeGraphMessage::SetToNodeOrLayer { node_id, is_layer } => {
|
||||
if is_layer && !network_interface.is_eligible_to_be_layer(&node_id, selection_network_path) {
|
||||
@@ -1487,6 +1488,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
||||
});
|
||||
responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||
responses.add(NodeGraphMessage::SendGraph);
|
||||
responses.add(NodeGraphMessage::SendWires);
|
||||
}
|
||||
NodeGraphMessage::SetDisplayName {
|
||||
node_id,
|
||||
@@ -1623,7 +1625,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
||||
// }
|
||||
|
||||
let Some(network_metadata) = network_interface.network_metadata(selection_network_path) else {
|
||||
log::error!("Could not get network metadata in PointerMove");
|
||||
log::error!("Could not get network metadata in UpdateBoxSelection");
|
||||
return;
|
||||
};
|
||||
|
||||
@@ -1689,7 +1691,8 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
||||
)
|
||||
.into_iter()
|
||||
.next();
|
||||
|
||||
responses.add(NodeGraphMessage::UpdateVisibleNodes);
|
||||
responses.add(NodeGraphMessage::SendWires);
|
||||
responses.add(FrontendMessage::UpdateImportsExports {
|
||||
imports,
|
||||
exports,
|
||||
@@ -1835,6 +1838,7 @@ impl NodeGraphMessageHandler {
|
||||
node_id: Some(node_id),
|
||||
node_type: node_type.clone(),
|
||||
xy: None,
|
||||
add_transaction: true,
|
||||
}
|
||||
.into(),
|
||||
NodeGraphMessage::SelectedNodesSet { nodes: vec![node_id] }.into(),
|
||||
@@ -2151,69 +2155,39 @@ impl NodeGraphMessageHandler {
|
||||
}
|
||||
}
|
||||
|
||||
fn collect_wires(network_interface: &NodeNetworkInterface, breadcrumb_network_path: &[NodeId]) -> Vec<FrontendNodeWire> {
|
||||
let Some(network) = network_interface.nested_network(breadcrumb_network_path) else {
|
||||
log::error!("Could not get network when collecting wires");
|
||||
return Vec::new();
|
||||
};
|
||||
let mut wires = network
|
||||
.nodes
|
||||
fn collect_wires(&mut self, network_interface: &mut NodeNetworkInterface, graph_wire_style: GraphWireStyle, breadcrumb_network_path: &[NodeId]) -> Vec<WirePathUpdate> {
|
||||
let mut added_wires = network_interface
|
||||
.node_graph_input_connectors(breadcrumb_network_path)
|
||||
.iter()
|
||||
.flat_map(|(wire_end, node)| node.inputs.iter().filter(|input| input.is_exposed()).enumerate().map(move |(index, input)| (input, wire_end, index)))
|
||||
.filter_map(|(input, &wire_end, wire_end_input_index)| {
|
||||
match *input {
|
||||
NodeInput::Node {
|
||||
node_id: wire_start,
|
||||
output_index: wire_start_output_index,
|
||||
// TODO: add ui for lambdas
|
||||
lambda: _,
|
||||
} => Some(FrontendNodeWire {
|
||||
wire_start: OutputConnector::node(wire_start, wire_start_output_index),
|
||||
wire_end: InputConnector::node(wire_end, wire_end_input_index),
|
||||
dashed: false,
|
||||
}),
|
||||
NodeInput::Network { import_index, .. } => Some(FrontendNodeWire {
|
||||
wire_start: OutputConnector::Import(import_index),
|
||||
wire_end: InputConnector::node(wire_end, wire_end_input_index),
|
||||
dashed: false,
|
||||
}),
|
||||
_ => None,
|
||||
}
|
||||
})
|
||||
.filter_map(|connector| network_interface.newly_loaded_input_wire(connector, graph_wire_style, breadcrumb_network_path))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// Connect primary export to root node, since previewing a node will change the primary export
|
||||
if let Some(root_node) = network_interface.root_node(breadcrumb_network_path) {
|
||||
wires.push(FrontendNodeWire {
|
||||
wire_start: OutputConnector::node(root_node.node_id, root_node.output_index),
|
||||
wire_end: InputConnector::Export(0),
|
||||
dashed: false,
|
||||
});
|
||||
let changed_wire_inputs = added_wires.iter().map(|update| (update.id, update.input_index)).collect::<Vec<_>>();
|
||||
self.frontend_wires.extend(changed_wire_inputs);
|
||||
|
||||
let mut orphaned_wire_inputs = self.frontend_wires.clone();
|
||||
self.frontend_wires = network_interface
|
||||
.node_graph_wire_inputs(breadcrumb_network_path)
|
||||
.iter()
|
||||
.filter_map(|visible_wire_input| orphaned_wire_inputs.take(visible_wire_input))
|
||||
.collect::<HashSet<_>>();
|
||||
added_wires.extend(orphaned_wire_inputs.into_iter().map(|(id, input_index)| WirePathUpdate {
|
||||
id,
|
||||
input_index,
|
||||
wire_path_update: None,
|
||||
}));
|
||||
|
||||
if let Some(wire_to_root) = network_interface.wire_to_root(graph_wire_style, breadcrumb_network_path) {
|
||||
added_wires.push(wire_to_root);
|
||||
} else {
|
||||
added_wires.push(WirePathUpdate {
|
||||
id: NodeId(u64::MAX),
|
||||
input_index: usize::MAX,
|
||||
wire_path_update: None,
|
||||
})
|
||||
}
|
||||
|
||||
// Connect rest of exports to their actual export field since they are not affected by previewing. Only connect the primary export if it is dashed
|
||||
for (i, export) in network.exports.iter().enumerate() {
|
||||
let dashed = matches!(network_interface.previewing(breadcrumb_network_path), Previewing::Yes { .. }) && i == 0;
|
||||
if dashed || i != 0 {
|
||||
if let NodeInput::Node { node_id, output_index, .. } = export {
|
||||
wires.push(FrontendNodeWire {
|
||||
wire_start: OutputConnector::Node {
|
||||
node_id: *node_id,
|
||||
output_index: *output_index,
|
||||
},
|
||||
wire_end: InputConnector::Export(i),
|
||||
dashed,
|
||||
});
|
||||
} else if let NodeInput::Network { import_index, .. } = *export {
|
||||
wires.push(FrontendNodeWire {
|
||||
wire_start: OutputConnector::Import(import_index),
|
||||
wire_end: InputConnector::Export(i),
|
||||
dashed,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
wires
|
||||
added_wires
|
||||
}
|
||||
|
||||
fn collect_nodes(&self, network_interface: &mut NodeNetworkInterface, breadcrumb_network_path: &[NodeId]) -> Vec<FrontendNode> {
|
||||
@@ -2237,6 +2211,7 @@ impl NodeGraphMessageHandler {
|
||||
log::error!("Could not get position for node {node_id}");
|
||||
}
|
||||
}
|
||||
|
||||
let mut frontend_inputs_lookup = frontend_inputs_lookup(breadcrumb_network_path, network_interface);
|
||||
let Some(network) = network_interface.nested_network(breadcrumb_network_path) else {
|
||||
log::error!("Could not get nested network when collecting nodes");
|
||||
@@ -2252,13 +2227,14 @@ impl NodeGraphMessageHandler {
|
||||
let node_id_path = [breadcrumb_network_path, (&[node_id])].concat();
|
||||
|
||||
let inputs = frontend_inputs_lookup.remove(&node_id).unwrap_or_default();
|
||||
|
||||
let mut inputs = inputs.into_iter().map(|input| {
|
||||
input.map(|input| FrontendGraphInput {
|
||||
data_type: FrontendGraphDataType::displayed_type(&input.ty, &input.type_source),
|
||||
resolved_type: Some(format!("{:?}", &input.ty)),
|
||||
resolved_type: format!("{:?}", &input.ty),
|
||||
valid_types: input.valid_types.iter().map(|ty| ty.to_string()).collect(),
|
||||
name: input.input_name.unwrap_or_else(|| input.ty.nested_type().to_string()),
|
||||
description: input.input_description.unwrap_or_default(),
|
||||
name: input.input_name,
|
||||
description: input.input_description,
|
||||
connected_to: input.output_connector,
|
||||
})
|
||||
});
|
||||
@@ -2266,20 +2242,16 @@ impl NodeGraphMessageHandler {
|
||||
let primary_input = inputs.next().flatten();
|
||||
let exposed_inputs = inputs.flatten().collect();
|
||||
|
||||
let output_types = network_interface.output_types(&node_id, breadcrumb_network_path);
|
||||
let primary_output_type = output_types.first().cloned().flatten();
|
||||
let frontend_data_type = if let Some((output_type, type_source)) = &primary_output_type {
|
||||
FrontendGraphDataType::displayed_type(output_type, type_source)
|
||||
} else {
|
||||
FrontendGraphDataType::General
|
||||
};
|
||||
let (output_type, type_source) = network_interface.output_type(&node_id, 0, breadcrumb_network_path);
|
||||
let frontend_data_type = FrontendGraphDataType::displayed_type(&output_type, &type_source);
|
||||
|
||||
let connected_to = outward_wires.get(&OutputConnector::node(node_id, 0)).cloned().unwrap_or_default();
|
||||
let primary_output = if network_interface.has_primary_output(&node_id, breadcrumb_network_path) && !output_types.is_empty() {
|
||||
let primary_output = if network_interface.has_primary_output(&node_id, breadcrumb_network_path) {
|
||||
Some(FrontendGraphOutput {
|
||||
data_type: frontend_data_type,
|
||||
name: "Output 1".to_string(),
|
||||
description: String::new(),
|
||||
resolved_type: primary_output_type.map(|(input, _)| format!("{input:?}")),
|
||||
resolved_type: format!("{:?}", output_type),
|
||||
connected_to,
|
||||
})
|
||||
} else {
|
||||
@@ -2287,15 +2259,13 @@ impl NodeGraphMessageHandler {
|
||||
};
|
||||
|
||||
let mut exposed_outputs = Vec::new();
|
||||
for (index, exposed_output) in output_types.iter().enumerate() {
|
||||
if index == 0 && network_interface.has_primary_output(&node_id, breadcrumb_network_path) {
|
||||
for output_index in 0..network_interface.number_of_outputs(&node_id, breadcrumb_network_path) {
|
||||
if output_index == 0 && network_interface.has_primary_output(&node_id, breadcrumb_network_path) {
|
||||
continue;
|
||||
}
|
||||
let frontend_data_type = if let Some((output_type, type_source)) = &exposed_output {
|
||||
FrontendGraphDataType::displayed_type(output_type, type_source)
|
||||
} else {
|
||||
FrontendGraphDataType::General
|
||||
};
|
||||
let (output_type, type_source) = network_interface.output_type(&node_id, 0, breadcrumb_network_path);
|
||||
let data_type = FrontendGraphDataType::displayed_type(&output_type, &type_source);
|
||||
|
||||
let Some(node_metadata) = network_metadata.persistent_metadata.node_metadata.get(&node_id) else {
|
||||
log::error!("Could not get node_metadata when getting output for {node_id}");
|
||||
continue;
|
||||
@@ -2303,17 +2273,17 @@ impl NodeGraphMessageHandler {
|
||||
let output_name = node_metadata
|
||||
.persistent_metadata
|
||||
.output_names
|
||||
.get(index)
|
||||
.map(|output_name| output_name.to_string())
|
||||
.get(output_index)
|
||||
.cloned()
|
||||
.filter(|output_name| !output_name.is_empty())
|
||||
.unwrap_or_else(|| exposed_output.clone().map(|(output_type, _)| output_type.nested_type().to_string()).unwrap_or_default());
|
||||
.unwrap_or_else(|| output_type.nested_type().to_string());
|
||||
|
||||
let connected_to = outward_wires.get(&OutputConnector::node(node_id, index)).cloned().unwrap_or_default();
|
||||
let connected_to = outward_wires.get(&OutputConnector::node(node_id, output_index)).cloned().unwrap_or_default();
|
||||
exposed_outputs.push(FrontendGraphOutput {
|
||||
data_type: frontend_data_type,
|
||||
data_type,
|
||||
name: output_name,
|
||||
description: String::new(),
|
||||
resolved_type: exposed_output.clone().map(|(input, _)| format!("{input:?}")),
|
||||
resolved_type: format!("{:?}", output_type),
|
||||
connected_to,
|
||||
});
|
||||
}
|
||||
@@ -2416,9 +2386,9 @@ impl NodeGraphMessageHandler {
|
||||
network_interface.upstream_flow_back_from_nodes(vec![node_id], &[], network_interface::FlowType::HorizontalFlow).last().is_some_and(|node_id|
|
||||
network_interface.document_node(&node_id, &[]).map_or_else(||{log::error!("Could not get node {node_id} in update_layer_panel"); false}, |node| {
|
||||
if network_interface.is_layer(&node_id, &[]) {
|
||||
node.inputs.iter().filter(|input| input.is_exposed_to_frontend(true)).nth(1).is_some_and(|input| input.as_value().is_some())
|
||||
node.inputs.iter().filter(|input| input.is_exposed()).nth(1).is_some_and(|input| input.as_value().is_some())
|
||||
} else {
|
||||
node.inputs.iter().filter(|input| input.is_exposed_to_frontend(true)).nth(0).is_some_and(|input| input.as_value().is_some())
|
||||
node.inputs.iter().filter(|input| input.is_exposed()).nth(0).is_some_and(|input| input.as_value().is_some())
|
||||
}
|
||||
}))
|
||||
);
|
||||
@@ -2467,66 +2437,6 @@ impl NodeGraphMessageHandler {
|
||||
}
|
||||
}
|
||||
|
||||
fn build_wire_path_string(output_position: DVec2, input_position: DVec2, vertical_out: bool, vertical_in: bool) -> String {
|
||||
let locations = Self::build_wire_path_locations(output_position, input_position, vertical_out, vertical_in);
|
||||
let smoothing = 0.5;
|
||||
let delta01 = DVec2::new((locations[1].x - locations[0].x) * smoothing, (locations[1].y - locations[0].y) * smoothing);
|
||||
let delta23 = DVec2::new((locations[3].x - locations[2].x) * smoothing, (locations[3].y - locations[2].y) * smoothing);
|
||||
format!(
|
||||
"M{},{} L{},{} C{},{} {},{} {},{} L{},{}",
|
||||
locations[0].x,
|
||||
locations[0].y,
|
||||
locations[1].x,
|
||||
locations[1].y,
|
||||
locations[1].x + delta01.x,
|
||||
locations[1].y + delta01.y,
|
||||
locations[2].x - delta23.x,
|
||||
locations[2].y - delta23.y,
|
||||
locations[2].x,
|
||||
locations[2].y,
|
||||
locations[3].x,
|
||||
locations[3].y
|
||||
)
|
||||
}
|
||||
|
||||
fn build_wire_path_locations(output_position: DVec2, input_position: DVec2, vertical_out: bool, vertical_in: bool) -> Vec<DVec2> {
|
||||
let horizontal_gap = (output_position.x - input_position.x).abs();
|
||||
let vertical_gap = (output_position.y - input_position.y).abs();
|
||||
// TODO: Finish this commented out code replacement for the code below it based on this diagram: <https://files.keavon.com/-/SuperbWideFoxterrier/capture.png>
|
||||
// // Straight: stacking lines which are always straight, or a straight horizontal wire between two aligned nodes
|
||||
// if ((verticalOut && vertical_in) || (!verticalOut && !vertical_in && vertical_gap === 0)) {
|
||||
// return [
|
||||
// { x: output_position.x, y: output_position.y },
|
||||
// { x: input_position.x, y: input_position.y },
|
||||
// ];
|
||||
// }
|
||||
|
||||
// // L-shape bend
|
||||
// if (verticalOut !== vertical_in) {
|
||||
// }
|
||||
|
||||
let curve_length = 24.;
|
||||
let curve_falloff_rate = curve_length * std::f64::consts::PI * 2.;
|
||||
|
||||
let horizontal_curve_amount = -(2_f64.powf((-10. * horizontal_gap) / curve_falloff_rate)) + 1.;
|
||||
let vertical_curve_amount = -(2_f64.powf((-10. * vertical_gap) / curve_falloff_rate)) + 1.;
|
||||
let horizontal_curve = horizontal_curve_amount * curve_length;
|
||||
let vertical_curve = vertical_curve_amount * curve_length;
|
||||
|
||||
vec![
|
||||
output_position,
|
||||
DVec2::new(
|
||||
if vertical_out { output_position.x } else { output_position.x + horizontal_curve },
|
||||
if vertical_out { output_position.y - vertical_curve } else { output_position.y },
|
||||
),
|
||||
DVec2::new(
|
||||
if vertical_in { input_position.x } else { input_position.x - horizontal_curve },
|
||||
if vertical_in { input_position.y + vertical_curve } else { input_position.y },
|
||||
),
|
||||
DVec2::new(input_position.x, input_position.y),
|
||||
]
|
||||
}
|
||||
|
||||
pub fn update_node_graph_hints(&self, responses: &mut VecDeque<Message>) {
|
||||
// A wire is in progress and its start and end connectors are set
|
||||
let wiring = self.wire_in_progress_from_connector.is_some();
|
||||
@@ -2570,8 +2480,8 @@ impl NodeGraphMessageHandler {
|
||||
|
||||
#[derive(Default)]
|
||||
struct InputLookup {
|
||||
input_name: Option<String>,
|
||||
input_description: Option<String>,
|
||||
input_name: String,
|
||||
input_description: String,
|
||||
ty: Type,
|
||||
type_source: TypeSource,
|
||||
valid_types: Vec<Type>,
|
||||
@@ -2586,34 +2496,31 @@ fn frontend_inputs_lookup(breadcrumb_network_path: &[NodeId], network_interface:
|
||||
return Default::default();
|
||||
};
|
||||
let mut frontend_inputs_lookup = HashMap::new();
|
||||
for (&node_id, node) in network.nodes.iter() {
|
||||
let mut inputs = Vec::with_capacity(node.inputs.len());
|
||||
for (index, input) in node.inputs.iter().enumerate() {
|
||||
let is_exposed = input.is_exposed_to_frontend(breadcrumb_network_path.is_empty());
|
||||
|
||||
// Skip not exposed inputs (they still get an entry to help with finding the primary input)
|
||||
if !is_exposed {
|
||||
inputs.push(None);
|
||||
continue;
|
||||
}
|
||||
|
||||
for (node_id, index, output_connector, is_exposed) in network
|
||||
.nodes
|
||||
.iter()
|
||||
.flat_map(|(node_id, node)| {
|
||||
node.inputs
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(index, input)| (*node_id, index, OutputConnector::from_input(input), input.is_exposed()))
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
{
|
||||
// Skip not exposed inputs (they still get an entry to help with finding the primary input)
|
||||
let lookup = if !is_exposed {
|
||||
None
|
||||
} else {
|
||||
// Get the name from the metadata here (since it also requires a reference to the `network_interface`)
|
||||
let input_name = network_interface
|
||||
.input_name(node_id, index, breadcrumb_network_path)
|
||||
.filter(|s| !s.is_empty())
|
||||
.map(|name| name.to_string());
|
||||
let input_description = network_interface.input_description(node_id, index, breadcrumb_network_path).map(|description| description.to_string());
|
||||
// Get the output connector that feeds into this input (done here as well for simplicity)
|
||||
let connector = OutputConnector::from_input(input);
|
||||
|
||||
inputs.push(Some(InputLookup {
|
||||
let (input_name, input_description) = network_interface.displayed_input_name_and_description(&node_id, index, breadcrumb_network_path);
|
||||
Some(InputLookup {
|
||||
input_name,
|
||||
input_description,
|
||||
output_connector: connector,
|
||||
output_connector,
|
||||
..Default::default()
|
||||
}));
|
||||
}
|
||||
frontend_inputs_lookup.insert(node_id, inputs);
|
||||
})
|
||||
};
|
||||
frontend_inputs_lookup.entry(node_id).or_insert_with(Vec::new).push(lookup);
|
||||
}
|
||||
|
||||
for (&node_id, value) in frontend_inputs_lookup.iter_mut() {
|
||||
@@ -2656,6 +2563,7 @@ impl Default for NodeGraphMessageHandler {
|
||||
select_if_not_dragged: None,
|
||||
wire_in_progress_from_connector: None,
|
||||
wire_in_progress_to_connector: None,
|
||||
wire_in_progress_type: FrontendGraphDataType::General,
|
||||
context_menu: None,
|
||||
deselect_on_pointer_up: None,
|
||||
auto_panning: Default::default(),
|
||||
@@ -2663,6 +2571,8 @@ impl Default for NodeGraphMessageHandler {
|
||||
reordering_export: None,
|
||||
reordering_import: None,
|
||||
end_index: None,
|
||||
frontend_nodes: Vec::new(),
|
||||
frontend_wires: HashSet::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,17 +60,12 @@ pub fn expose_widget(node_id: NodeId, index: usize, data_type: FrontendGraphData
|
||||
"Expose this parameter as a node input in the graph"
|
||||
})
|
||||
.on_update(move |_parameter| {
|
||||
Message::Batched(Box::new([
|
||||
NodeGraphMessage::ExposeInput {
|
||||
input_connector: InputConnector::node(node_id, index),
|
||||
set_to_exposed: !exposed,
|
||||
start_transaction: true,
|
||||
}
|
||||
.into(),
|
||||
DocumentMessage::GraphViewOverlay { open: true }.into(),
|
||||
NavigationMessage::FitViewportToSelection.into(),
|
||||
DocumentMessage::ZoomCanvasTo100Percent.into(),
|
||||
]))
|
||||
Message::Batched(Box::new([NodeGraphMessage::ExposeInput {
|
||||
input_connector: InputConnector::node(node_id, index),
|
||||
set_to_exposed: !exposed,
|
||||
start_transaction: true,
|
||||
}
|
||||
.into()]))
|
||||
})
|
||||
.widget_holder()
|
||||
}
|
||||
@@ -85,28 +80,31 @@ pub fn add_blank_assist(widgets: &mut Vec<WidgetHolder>) {
|
||||
]);
|
||||
}
|
||||
|
||||
pub fn start_widgets(parameter_widgets_info: ParameterWidgetsInfo, data_type: FrontendGraphDataType) -> Vec<WidgetHolder> {
|
||||
start_widgets_exposable(parameter_widgets_info, data_type, true)
|
||||
}
|
||||
|
||||
pub fn start_widgets_exposable(parameter_widgets_info: ParameterWidgetsInfo, data_type: FrontendGraphDataType, exposable: bool) -> Vec<WidgetHolder> {
|
||||
pub fn start_widgets(parameter_widgets_info: ParameterWidgetsInfo) -> Vec<WidgetHolder> {
|
||||
let ParameterWidgetsInfo {
|
||||
document_node,
|
||||
node_id,
|
||||
index,
|
||||
name,
|
||||
description,
|
||||
input_type,
|
||||
blank_assist,
|
||||
exposeable,
|
||||
} = parameter_widgets_info;
|
||||
|
||||
let Some(document_node) = document_node else {
|
||||
log::warn!("A widget failed to be built because its document node is invalid.");
|
||||
return vec![];
|
||||
};
|
||||
|
||||
let Some(input) = document_node.inputs.get(index) else {
|
||||
log::warn!("A widget failed to be built because its node's input index is invalid.");
|
||||
return vec![];
|
||||
};
|
||||
let description = if description != "TODO" { description } else { "" };
|
||||
let description = if description != "TODO" { description } else { String::new() };
|
||||
let mut widgets = Vec::with_capacity(6);
|
||||
if exposable {
|
||||
widgets.push(expose_widget(node_id, index, data_type, input.is_exposed()));
|
||||
if exposeable {
|
||||
widgets.push(expose_widget(node_id, index, input_type, input.is_exposed()));
|
||||
}
|
||||
widgets.push(TextLabel::new(name).tooltip(description).widget_holder());
|
||||
if blank_assist {
|
||||
@@ -126,18 +124,6 @@ pub(crate) fn property_from_type(
|
||||
step: Option<f64>,
|
||||
context: &mut NodePropertiesContext,
|
||||
) -> Result<Vec<LayoutGroup>, Vec<LayoutGroup>> {
|
||||
let Some(network) = context.network_interface.nested_network(context.selection_network_path) else {
|
||||
log::warn!("A widget failed to be built for node {node_id}, index {index} because the network could not be determined");
|
||||
return Err(vec![]);
|
||||
};
|
||||
let Some(document_node) = network.nodes.get(&node_id) else {
|
||||
log::warn!("A widget failed to be built for node {node_id}, index {index} because the document node does not exist");
|
||||
return Err(vec![]);
|
||||
};
|
||||
|
||||
let name = context.network_interface.input_name(node_id, index, context.selection_network_path).unwrap_or_default();
|
||||
let description = context.network_interface.input_description(node_id, index, context.selection_network_path).unwrap_or_default();
|
||||
|
||||
let (mut number_min, mut number_max, range) = number_options;
|
||||
let mut number_input = NumberInput::default();
|
||||
if let Some((range_start, range_end)) = range {
|
||||
@@ -158,7 +144,7 @@ pub(crate) fn property_from_type(
|
||||
let min = |x: f64| number_min.unwrap_or(x);
|
||||
let max = |x: f64| number_max.unwrap_or(x);
|
||||
|
||||
let default_info = ParameterWidgetsInfo::new(document_node, node_id, index, name, description, true);
|
||||
let default_info = ParameterWidgetsInfo::new(node_id, index, true, context);
|
||||
|
||||
let mut extra_widgets = vec![];
|
||||
let widgets = match ty {
|
||||
@@ -176,6 +162,7 @@ pub(crate) fn property_from_type(
|
||||
Some("SeedValue") => number_widget(default_info, number_input.int().min(min(0.))).into(),
|
||||
Some("Resolution") => coordinate_widget(default_info, "W", "H", unit.unwrap_or(" px"), Some(64.)),
|
||||
Some("PixelSize") => coordinate_widget(default_info, "X", "Y", unit.unwrap_or(" px"), None),
|
||||
Some("TextArea") => text_area_widget(default_info).into(),
|
||||
|
||||
// For all other types, use TypeId-based matching
|
||||
_ => {
|
||||
@@ -247,7 +234,7 @@ pub(crate) fn property_from_type(
|
||||
// OTHER
|
||||
// =====
|
||||
_ => {
|
||||
let mut widgets = start_widgets(default_info, FrontendGraphDataType::General);
|
||||
let mut widgets = start_widgets(default_info);
|
||||
widgets.extend_from_slice(&[
|
||||
Separator::new(SeparatorType::Unrelated).widget_holder(),
|
||||
TextLabel::new("-")
|
||||
@@ -277,8 +264,9 @@ pub(crate) fn property_from_type(
|
||||
pub fn text_widget(parameter_widgets_info: ParameterWidgetsInfo) -> Vec<WidgetHolder> {
|
||||
let ParameterWidgetsInfo { document_node, node_id, index, .. } = parameter_widgets_info;
|
||||
|
||||
let mut widgets = start_widgets(parameter_widgets_info, FrontendGraphDataType::General);
|
||||
let mut widgets = start_widgets(parameter_widgets_info);
|
||||
|
||||
let Some(document_node) = document_node else { return Vec::new() };
|
||||
let Some(input) = document_node.inputs.get(index) else {
|
||||
log::warn!("A widget failed to be built because its node's input index is invalid.");
|
||||
return vec![];
|
||||
@@ -298,8 +286,9 @@ pub fn text_widget(parameter_widgets_info: ParameterWidgetsInfo) -> Vec<WidgetHo
|
||||
pub fn text_area_widget(parameter_widgets_info: ParameterWidgetsInfo) -> Vec<WidgetHolder> {
|
||||
let ParameterWidgetsInfo { document_node, node_id, index, .. } = parameter_widgets_info;
|
||||
|
||||
let mut widgets = start_widgets(parameter_widgets_info, FrontendGraphDataType::General);
|
||||
let mut widgets = start_widgets(parameter_widgets_info);
|
||||
|
||||
let Some(document_node) = document_node else { return Vec::new() };
|
||||
let Some(input) = document_node.inputs.get(index) else {
|
||||
log::warn!("A widget failed to be built because its node's input index is invalid.");
|
||||
return vec![];
|
||||
@@ -319,8 +308,9 @@ pub fn text_area_widget(parameter_widgets_info: ParameterWidgetsInfo) -> Vec<Wid
|
||||
pub fn bool_widget(parameter_widgets_info: ParameterWidgetsInfo, checkbox_input: CheckboxInput) -> Vec<WidgetHolder> {
|
||||
let ParameterWidgetsInfo { document_node, node_id, index, .. } = parameter_widgets_info;
|
||||
|
||||
let mut widgets = start_widgets(parameter_widgets_info, FrontendGraphDataType::General);
|
||||
let mut widgets = start_widgets(parameter_widgets_info);
|
||||
|
||||
let Some(document_node) = document_node else { return Vec::new() };
|
||||
let Some(input) = document_node.inputs.get(index) else {
|
||||
log::warn!("A widget failed to be built because its node's input index is invalid.");
|
||||
return vec![];
|
||||
@@ -341,8 +331,9 @@ pub fn bool_widget(parameter_widgets_info: ParameterWidgetsInfo, checkbox_input:
|
||||
pub fn reference_point_widget(parameter_widgets_info: ParameterWidgetsInfo, disabled: bool) -> Vec<WidgetHolder> {
|
||||
let ParameterWidgetsInfo { document_node, node_id, index, .. } = parameter_widgets_info;
|
||||
|
||||
let mut widgets = start_widgets(parameter_widgets_info, FrontendGraphDataType::General);
|
||||
let mut widgets = start_widgets(parameter_widgets_info);
|
||||
|
||||
let Some(document_node) = document_node else { return Vec::new() };
|
||||
let Some(input) = document_node.inputs.get(index) else {
|
||||
log::warn!("A widget failed to be built because its node's input index is invalid.");
|
||||
return vec![];
|
||||
@@ -371,7 +362,7 @@ pub fn reference_point_widget(parameter_widgets_info: ParameterWidgetsInfo, disa
|
||||
pub fn footprint_widget(parameter_widgets_info: ParameterWidgetsInfo, extra_widgets: &mut Vec<LayoutGroup>) -> LayoutGroup {
|
||||
let ParameterWidgetsInfo { document_node, node_id, index, .. } = parameter_widgets_info;
|
||||
|
||||
let mut location_widgets = start_widgets(parameter_widgets_info, FrontendGraphDataType::General);
|
||||
let mut location_widgets = start_widgets(parameter_widgets_info);
|
||||
location_widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder());
|
||||
|
||||
let mut scale_widgets = vec![TextLabel::new("").widget_holder()];
|
||||
@@ -382,10 +373,12 @@ pub fn footprint_widget(parameter_widgets_info: ParameterWidgetsInfo, extra_widg
|
||||
add_blank_assist(&mut resolution_widgets);
|
||||
resolution_widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder());
|
||||
|
||||
let Some(document_node) = document_node else { return LayoutGroup::default() };
|
||||
let Some(input) = document_node.inputs.get(index) else {
|
||||
log::warn!("A widget failed to be built because its node's input index is invalid.");
|
||||
return Vec::new().into();
|
||||
};
|
||||
|
||||
if let Some(&TaggedValue::Footprint(footprint)) = input.as_non_exposed_value() {
|
||||
let top_left = footprint.transform.transform_point2(DVec2::ZERO);
|
||||
let bounds = footprint.scale();
|
||||
@@ -517,8 +510,9 @@ pub fn footprint_widget(parameter_widgets_info: ParameterWidgetsInfo, extra_widg
|
||||
pub fn coordinate_widget(parameter_widgets_info: ParameterWidgetsInfo, x: &str, y: &str, unit: &str, min: Option<f64>) -> LayoutGroup {
|
||||
let ParameterWidgetsInfo { document_node, node_id, index, .. } = parameter_widgets_info;
|
||||
|
||||
let mut widgets = start_widgets(parameter_widgets_info, FrontendGraphDataType::Number);
|
||||
let mut widgets = start_widgets(parameter_widgets_info);
|
||||
|
||||
let Some(document_node) = document_node else { return LayoutGroup::default() };
|
||||
let Some(input) = document_node.inputs.get(index) else {
|
||||
log::warn!("A widget failed to be built because its node's input index is invalid.");
|
||||
return LayoutGroup::Row { widgets: vec![] };
|
||||
@@ -629,7 +623,7 @@ pub fn coordinate_widget(parameter_widgets_info: ParameterWidgetsInfo, x: &str,
|
||||
pub fn array_of_number_widget(parameter_widgets_info: ParameterWidgetsInfo, text_input: TextInput) -> Vec<WidgetHolder> {
|
||||
let ParameterWidgetsInfo { document_node, node_id, index, .. } = parameter_widgets_info;
|
||||
|
||||
let mut widgets = start_widgets(parameter_widgets_info, FrontendGraphDataType::Number);
|
||||
let mut widgets = start_widgets(parameter_widgets_info);
|
||||
|
||||
let from_string = |string: &str| {
|
||||
string
|
||||
@@ -641,6 +635,7 @@ pub fn array_of_number_widget(parameter_widgets_info: ParameterWidgetsInfo, text
|
||||
.map(TaggedValue::VecF64)
|
||||
};
|
||||
|
||||
let Some(document_node) = document_node else { return Vec::new() };
|
||||
let Some(input) = document_node.inputs.get(index) else {
|
||||
log::warn!("A widget failed to be built because its node's input index is invalid.");
|
||||
return vec![];
|
||||
@@ -660,7 +655,7 @@ pub fn array_of_number_widget(parameter_widgets_info: ParameterWidgetsInfo, text
|
||||
pub fn array_of_coordinates_widget(parameter_widgets_info: ParameterWidgetsInfo, text_props: TextInput) -> Vec<WidgetHolder> {
|
||||
let ParameterWidgetsInfo { document_node, node_id, index, .. } = parameter_widgets_info;
|
||||
|
||||
let mut widgets = start_widgets(parameter_widgets_info, FrontendGraphDataType::Number);
|
||||
let mut widgets = start_widgets(parameter_widgets_info);
|
||||
|
||||
let from_string = |string: &str| {
|
||||
string
|
||||
@@ -672,6 +667,7 @@ pub fn array_of_coordinates_widget(parameter_widgets_info: ParameterWidgetsInfo,
|
||||
.map(TaggedValue::VecDVec2)
|
||||
};
|
||||
|
||||
let Some(document_node) = document_node else { return Vec::new() };
|
||||
let Some(input) = document_node.inputs.get(index) else {
|
||||
log::warn!("A widget failed to be built because its node's input index is invalid.");
|
||||
return vec![];
|
||||
@@ -691,11 +687,12 @@ pub fn array_of_coordinates_widget(parameter_widgets_info: ParameterWidgetsInfo,
|
||||
pub fn font_inputs(parameter_widgets_info: ParameterWidgetsInfo) -> (Vec<WidgetHolder>, Option<Vec<WidgetHolder>>) {
|
||||
let ParameterWidgetsInfo { document_node, node_id, index, .. } = parameter_widgets_info;
|
||||
|
||||
let mut first_widgets = start_widgets(parameter_widgets_info, FrontendGraphDataType::General);
|
||||
let mut first_widgets = start_widgets(parameter_widgets_info);
|
||||
let mut second_widgets = None;
|
||||
|
||||
let from_font_input = |font: &FontInput| TaggedValue::Font(Font::new(font.font_family.clone(), font.font_style.clone()));
|
||||
|
||||
let Some(document_node) = document_node else { return (Vec::new(), None) };
|
||||
let Some(input) = document_node.inputs.get(index) else {
|
||||
log::warn!("A widget failed to be built because its node's input index is invalid.");
|
||||
return (vec![], None);
|
||||
@@ -725,7 +722,7 @@ pub fn font_inputs(parameter_widgets_info: ParameterWidgetsInfo) -> (Vec<WidgetH
|
||||
}
|
||||
|
||||
pub fn vector_data_widget(parameter_widgets_info: ParameterWidgetsInfo) -> Vec<WidgetHolder> {
|
||||
let mut widgets = start_widgets(parameter_widgets_info, FrontendGraphDataType::VectorData);
|
||||
let mut widgets = start_widgets(parameter_widgets_info);
|
||||
|
||||
widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder());
|
||||
widgets.push(TextLabel::new("Vector data is supplied through the node graph").widget_holder());
|
||||
@@ -734,7 +731,7 @@ pub fn vector_data_widget(parameter_widgets_info: ParameterWidgetsInfo) -> Vec<W
|
||||
}
|
||||
|
||||
pub fn raster_widget(parameter_widgets_info: ParameterWidgetsInfo) -> Vec<WidgetHolder> {
|
||||
let mut widgets = start_widgets(parameter_widgets_info, FrontendGraphDataType::Raster);
|
||||
let mut widgets = start_widgets(parameter_widgets_info);
|
||||
|
||||
widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder());
|
||||
widgets.push(TextLabel::new("Raster data is supplied through the node graph").widget_holder());
|
||||
@@ -743,7 +740,7 @@ pub fn raster_widget(parameter_widgets_info: ParameterWidgetsInfo) -> Vec<Widget
|
||||
}
|
||||
|
||||
pub fn group_widget(parameter_widgets_info: ParameterWidgetsInfo) -> Vec<WidgetHolder> {
|
||||
let mut widgets = start_widgets(parameter_widgets_info, FrontendGraphDataType::Group);
|
||||
let mut widgets = start_widgets(parameter_widgets_info);
|
||||
|
||||
widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder());
|
||||
widgets.push(TextLabel::new("Group data is supplied through the node graph").widget_holder());
|
||||
@@ -754,8 +751,9 @@ pub fn group_widget(parameter_widgets_info: ParameterWidgetsInfo) -> Vec<WidgetH
|
||||
pub fn number_widget(parameter_widgets_info: ParameterWidgetsInfo, number_props: NumberInput) -> Vec<WidgetHolder> {
|
||||
let ParameterWidgetsInfo { document_node, node_id, index, .. } = parameter_widgets_info;
|
||||
|
||||
let mut widgets = start_widgets(parameter_widgets_info, FrontendGraphDataType::Number);
|
||||
let mut widgets = start_widgets(parameter_widgets_info);
|
||||
|
||||
let Some(document_node) = document_node else { return Vec::new() };
|
||||
let Some(input) = document_node.inputs.get(index) else {
|
||||
log::warn!("A widget failed to be built because its node's input index is invalid.");
|
||||
return vec![];
|
||||
@@ -825,7 +823,8 @@ pub fn number_widget(parameter_widgets_info: ParameterWidgetsInfo, number_props:
|
||||
pub fn blend_mode_widget(parameter_widgets_info: ParameterWidgetsInfo) -> LayoutGroup {
|
||||
let ParameterWidgetsInfo { document_node, node_id, index, .. } = parameter_widgets_info;
|
||||
|
||||
let mut widgets = start_widgets(parameter_widgets_info, FrontendGraphDataType::General);
|
||||
let mut widgets = start_widgets(parameter_widgets_info);
|
||||
let Some(document_node) = document_node else { return LayoutGroup::default() };
|
||||
let Some(input) = document_node.inputs.get(index) else {
|
||||
log::warn!("A widget failed to be built because its node's input index is invalid.");
|
||||
return LayoutGroup::Row { widgets: vec![] };
|
||||
@@ -859,8 +858,9 @@ pub fn blend_mode_widget(parameter_widgets_info: ParameterWidgetsInfo) -> Layout
|
||||
pub fn color_widget(parameter_widgets_info: ParameterWidgetsInfo, color_button: ColorInput) -> LayoutGroup {
|
||||
let ParameterWidgetsInfo { document_node, node_id, index, .. } = parameter_widgets_info;
|
||||
|
||||
let mut widgets = start_widgets(parameter_widgets_info, FrontendGraphDataType::General);
|
||||
let mut widgets = start_widgets(parameter_widgets_info);
|
||||
|
||||
let Some(document_node) = document_node else { return LayoutGroup::default() };
|
||||
// Return early with just the label if the input is exposed to the graph, meaning we don't want to show the color picker widget in the Properties panel
|
||||
let NodeInput::Value { tagged_value, exposed: false } = &document_node.inputs[index] else {
|
||||
return LayoutGroup::Row { widgets };
|
||||
@@ -913,8 +913,9 @@ pub fn font_widget(parameter_widgets_info: ParameterWidgetsInfo) -> LayoutGroup
|
||||
pub fn curve_widget(parameter_widgets_info: ParameterWidgetsInfo) -> LayoutGroup {
|
||||
let ParameterWidgetsInfo { document_node, node_id, index, .. } = parameter_widgets_info;
|
||||
|
||||
let mut widgets = start_widgets(parameter_widgets_info, FrontendGraphDataType::General);
|
||||
let mut widgets = start_widgets(parameter_widgets_info);
|
||||
|
||||
let Some(document_node) = document_node else { return LayoutGroup::default() };
|
||||
let Some(input) = document_node.inputs.get(index) else {
|
||||
log::warn!("A widget failed to be built because its node's input index is invalid.");
|
||||
return LayoutGroup::Row { widgets: vec![] };
|
||||
@@ -939,14 +940,11 @@ pub fn get_document_node<'a>(node_id: NodeId, context: &'a NodePropertiesContext
|
||||
network.nodes.get(&node_id).ok_or(format!("node {node_id} not found in get_document_node"))
|
||||
}
|
||||
|
||||
pub fn query_node_and_input_info<'a>(node_id: NodeId, input_index: usize, context: &'a NodePropertiesContext<'a>) -> Result<(&'a DocumentNode, &'a str, &'a str), String> {
|
||||
pub fn query_node_and_input_info<'a>(node_id: NodeId, input_index: usize, context: &'a mut NodePropertiesContext<'a>) -> Result<(&'a DocumentNode, String, String), String> {
|
||||
let (name, description) = context.network_interface.displayed_input_name_and_description(&node_id, input_index, context.selection_network_path);
|
||||
let document_node = get_document_node(node_id, context)?;
|
||||
let input_name = context.network_interface.input_name(node_id, input_index, context.selection_network_path).unwrap_or_else(|| {
|
||||
log::warn!("input name not found in query_node_and_input_info");
|
||||
""
|
||||
});
|
||||
let input_description = context.network_interface.input_description(node_id, input_index, context.selection_network_path).unwrap_or_default();
|
||||
Ok((document_node, input_name, input_description))
|
||||
|
||||
Ok((document_node, name, description))
|
||||
}
|
||||
|
||||
pub fn query_noise_pattern_state(node_id: NodeId, context: &NodePropertiesContext) -> Result<(bool, bool, bool, bool, bool, bool), String> {
|
||||
@@ -995,6 +993,9 @@ pub fn query_assign_colors_randomize(node_id: NodeId, context: &NodePropertiesCo
|
||||
pub(crate) fn brightness_contrast_properties(node_id: NodeId, context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
|
||||
use graphene_std::raster::brightness_contrast::*;
|
||||
|
||||
// Use Classic
|
||||
let use_classic = bool_widget(ParameterWidgetsInfo::new(node_id, UseClassicInput::INDEX, true, context), CheckboxInput::default());
|
||||
|
||||
let document_node = match get_document_node(node_id, context) {
|
||||
Ok(document_node) => document_node,
|
||||
Err(err) => {
|
||||
@@ -1002,12 +1003,6 @@ pub(crate) fn brightness_contrast_properties(node_id: NodeId, context: &mut Node
|
||||
return Vec::new();
|
||||
}
|
||||
};
|
||||
|
||||
// Use Classic
|
||||
let use_classic = bool_widget(
|
||||
ParameterWidgetsInfo::from_index(document_node, node_id, UseClassicInput::INDEX, true, context),
|
||||
CheckboxInput::default(),
|
||||
);
|
||||
let use_classic_value = match document_node.inputs[UseClassicInput::INDEX].as_value() {
|
||||
Some(TaggedValue::Bool(use_classic_choice)) => *use_classic_choice,
|
||||
_ => false,
|
||||
@@ -1015,7 +1010,7 @@ pub(crate) fn brightness_contrast_properties(node_id: NodeId, context: &mut Node
|
||||
|
||||
// Brightness
|
||||
let brightness = number_widget(
|
||||
ParameterWidgetsInfo::from_index(document_node, node_id, BrightnessInput::INDEX, true, context),
|
||||
ParameterWidgetsInfo::new(node_id, BrightnessInput::INDEX, true, context),
|
||||
NumberInput::default()
|
||||
.unit("%")
|
||||
.mode_range()
|
||||
@@ -1026,7 +1021,7 @@ pub(crate) fn brightness_contrast_properties(node_id: NodeId, context: &mut Node
|
||||
|
||||
// Contrast
|
||||
let contrast = number_widget(
|
||||
ParameterWidgetsInfo::from_index(document_node, node_id, ContrastInput::INDEX, true, context),
|
||||
ParameterWidgetsInfo::new(node_id, ContrastInput::INDEX, true, context),
|
||||
NumberInput::default()
|
||||
.unit("%")
|
||||
.mode_range()
|
||||
@@ -1047,6 +1042,11 @@ pub(crate) fn brightness_contrast_properties(node_id: NodeId, context: &mut Node
|
||||
pub(crate) fn channel_mixer_properties(node_id: NodeId, context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
|
||||
use graphene_std::raster::channel_mixer::*;
|
||||
|
||||
let is_monochrome = bool_widget(ParameterWidgetsInfo::new(node_id, MonochromeInput::INDEX, true, context), CheckboxInput::default());
|
||||
let mut parameter_info = ParameterWidgetsInfo::new(node_id, OutputChannelInput::INDEX, true, context);
|
||||
parameter_info.exposeable = false;
|
||||
let output_channel = enum_choice::<RedGreenBlue>().for_socket(parameter_info).property_row();
|
||||
|
||||
let document_node = match get_document_node(node_id, context) {
|
||||
Ok(document_node) => document_node,
|
||||
Err(err) => {
|
||||
@@ -1054,22 +1054,12 @@ pub(crate) fn channel_mixer_properties(node_id: NodeId, context: &mut NodeProper
|
||||
return Vec::new();
|
||||
}
|
||||
};
|
||||
|
||||
// Monochrome
|
||||
let is_monochrome = bool_widget(
|
||||
ParameterWidgetsInfo::from_index(document_node, node_id, MonochromeInput::INDEX, true, context),
|
||||
CheckboxInput::default(),
|
||||
);
|
||||
let is_monochrome_value = match document_node.inputs[MonochromeInput::INDEX].as_value() {
|
||||
Some(TaggedValue::Bool(monochrome_choice)) => *monochrome_choice,
|
||||
_ => false,
|
||||
};
|
||||
|
||||
// Output channel choice
|
||||
let output_channel = enum_choice::<RedGreenBlue>()
|
||||
.for_socket(ParameterWidgetsInfo::from_index(document_node, node_id, OutputChannelInput::INDEX, true, context))
|
||||
.exposable(false)
|
||||
.property_row();
|
||||
let output_channel_value = match &document_node.inputs[OutputChannelInput::INDEX].as_value() {
|
||||
Some(TaggedValue::RedGreenBlue(choice)) => choice,
|
||||
_ => {
|
||||
@@ -1086,10 +1076,10 @@ pub(crate) fn channel_mixer_properties(node_id: NodeId, context: &mut NodeProper
|
||||
(false, RedGreenBlue::Blue) => (BlueRInput::INDEX, BlueGInput::INDEX, BlueBInput::INDEX, BlueCInput::INDEX),
|
||||
};
|
||||
let number_input = NumberInput::default().mode_range().min(-200.).max(200.).unit("%");
|
||||
let red = number_widget(ParameterWidgetsInfo::from_index(document_node, node_id, red_output_index, true, context), number_input.clone());
|
||||
let green = number_widget(ParameterWidgetsInfo::from_index(document_node, node_id, green_output_index, true, context), number_input.clone());
|
||||
let blue = number_widget(ParameterWidgetsInfo::from_index(document_node, node_id, blue_output_index, true, context), number_input.clone());
|
||||
let constant = number_widget(ParameterWidgetsInfo::from_index(document_node, node_id, constant_output_index, true, context), number_input);
|
||||
let red = number_widget(ParameterWidgetsInfo::new(node_id, red_output_index, true, context), number_input.clone());
|
||||
let green = number_widget(ParameterWidgetsInfo::new(node_id, green_output_index, true, context), number_input.clone());
|
||||
let blue = number_widget(ParameterWidgetsInfo::new(node_id, blue_output_index, true, context), number_input.clone());
|
||||
let constant = number_widget(ParameterWidgetsInfo::new(node_id, constant_output_index, true, context), number_input);
|
||||
|
||||
// Monochrome
|
||||
let mut layout = vec![LayoutGroup::Row { widgets: is_monochrome }];
|
||||
@@ -1110,6 +1100,10 @@ pub(crate) fn channel_mixer_properties(node_id: NodeId, context: &mut NodeProper
|
||||
pub(crate) fn selective_color_properties(node_id: NodeId, context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
|
||||
use graphene_std::raster::selective_color::*;
|
||||
|
||||
let mut default_info = ParameterWidgetsInfo::new(node_id, ColorsInput::INDEX, true, context);
|
||||
default_info.exposeable = false;
|
||||
let colors = enum_choice::<SelectiveColorChoice>().for_socket(default_info).property_row();
|
||||
|
||||
let document_node = match get_document_node(node_id, context) {
|
||||
Ok(document_node) => document_node,
|
||||
Err(err) => {
|
||||
@@ -1117,13 +1111,7 @@ pub(crate) fn selective_color_properties(node_id: NodeId, context: &mut NodeProp
|
||||
return Vec::new();
|
||||
}
|
||||
};
|
||||
|
||||
// Colors choice
|
||||
let colors = enum_choice::<SelectiveColorChoice>()
|
||||
.for_socket(ParameterWidgetsInfo::from_index(document_node, node_id, ColorsInput::INDEX, true, context))
|
||||
.exposable(false)
|
||||
.property_row();
|
||||
|
||||
let colors_choice = match &document_node.inputs[ColorsInput::INDEX].as_value() {
|
||||
Some(TaggedValue::SelectiveColorChoice(choice)) => choice,
|
||||
_ => {
|
||||
@@ -1131,7 +1119,6 @@ pub(crate) fn selective_color_properties(node_id: NodeId, context: &mut NodeProp
|
||||
return vec![];
|
||||
}
|
||||
};
|
||||
|
||||
// CMYK
|
||||
let (c_index, m_index, y_index, k_index) = match colors_choice {
|
||||
SelectiveColorChoice::Reds => (RCInput::INDEX, RMInput::INDEX, RYInput::INDEX, RKInput::INDEX),
|
||||
@@ -1145,14 +1132,14 @@ pub(crate) fn selective_color_properties(node_id: NodeId, context: &mut NodeProp
|
||||
SelectiveColorChoice::Blacks => (KCInput::INDEX, KMInput::INDEX, KYInput::INDEX, KKInput::INDEX),
|
||||
};
|
||||
let number_input = NumberInput::default().mode_range().min(-100.).max(100.).unit("%");
|
||||
let cyan = number_widget(ParameterWidgetsInfo::from_index(document_node, node_id, c_index, true, context), number_input.clone());
|
||||
let magenta = number_widget(ParameterWidgetsInfo::from_index(document_node, node_id, m_index, true, context), number_input.clone());
|
||||
let yellow = number_widget(ParameterWidgetsInfo::from_index(document_node, node_id, y_index, true, context), number_input.clone());
|
||||
let black = number_widget(ParameterWidgetsInfo::from_index(document_node, node_id, k_index, true, context), number_input);
|
||||
let cyan = number_widget(ParameterWidgetsInfo::new(node_id, c_index, true, context), number_input.clone());
|
||||
let magenta = number_widget(ParameterWidgetsInfo::new(node_id, m_index, true, context), number_input.clone());
|
||||
let yellow = number_widget(ParameterWidgetsInfo::new(node_id, y_index, true, context), number_input.clone());
|
||||
let black = number_widget(ParameterWidgetsInfo::new(node_id, k_index, true, context), number_input);
|
||||
|
||||
// Mode
|
||||
let mode = enum_choice::<RelativeAbsolute>()
|
||||
.for_socket(ParameterWidgetsInfo::from_index(document_node, node_id, ModeInput::INDEX, true, context))
|
||||
.for_socket(ParameterWidgetsInfo::new(node_id, ModeInput::INDEX, true, context))
|
||||
.property_row();
|
||||
|
||||
vec![
|
||||
@@ -1171,19 +1158,19 @@ pub(crate) fn selective_color_properties(node_id: NodeId, context: &mut NodeProp
|
||||
pub(crate) fn grid_properties(node_id: NodeId, context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
|
||||
use graphene_std::vector::generator_nodes::grid::*;
|
||||
|
||||
let document_node = match get_document_node(node_id, context) {
|
||||
Ok(document_node) => document_node,
|
||||
Err(err) => {
|
||||
log::error!("Could not get document node in exposure_properties: {err}");
|
||||
return Vec::new();
|
||||
}
|
||||
};
|
||||
let grid_type = enum_choice::<GridType>()
|
||||
.for_socket(ParameterWidgetsInfo::from_index(document_node, node_id, GridTypeInput::INDEX, true, context))
|
||||
.for_socket(ParameterWidgetsInfo::new(node_id, GridTypeInput::INDEX, true, context))
|
||||
.property_row();
|
||||
|
||||
let mut widgets = vec![grid_type];
|
||||
|
||||
let document_node = match get_document_node(node_id, context) {
|
||||
Ok(document_node) => document_node,
|
||||
Err(err) => {
|
||||
log::error!("Could not get document node in grid_properties: {err}");
|
||||
return Vec::new();
|
||||
}
|
||||
};
|
||||
let Some(grid_type_input) = document_node.inputs.get(GridTypeInput::INDEX) else {
|
||||
log::warn!("A widget failed to be built because its node's input index is invalid.");
|
||||
return vec![];
|
||||
@@ -1191,36 +1178,24 @@ pub(crate) fn grid_properties(node_id: NodeId, context: &mut NodePropertiesConte
|
||||
if let Some(&TaggedValue::GridType(grid_type)) = grid_type_input.as_non_exposed_value() {
|
||||
match grid_type {
|
||||
GridType::Rectangular => {
|
||||
let spacing = coordinate_widget(
|
||||
ParameterWidgetsInfo::from_index(document_node, node_id, SpacingInput::<f64>::INDEX, true, context),
|
||||
"W",
|
||||
"H",
|
||||
" px",
|
||||
Some(0.),
|
||||
);
|
||||
let spacing = coordinate_widget(ParameterWidgetsInfo::new(node_id, SpacingInput::<f64>::INDEX, true, context), "W", "H", " px", Some(0.));
|
||||
widgets.push(spacing);
|
||||
}
|
||||
GridType::Isometric => {
|
||||
let spacing = LayoutGroup::Row {
|
||||
widgets: number_widget(
|
||||
ParameterWidgetsInfo::from_index(document_node, node_id, SpacingInput::<f64>::INDEX, true, context),
|
||||
ParameterWidgetsInfo::new(node_id, SpacingInput::<f64>::INDEX, true, context),
|
||||
NumberInput::default().label("H").min(0.).unit(" px"),
|
||||
),
|
||||
};
|
||||
let angles = coordinate_widget(ParameterWidgetsInfo::from_index(document_node, node_id, AnglesInput::INDEX, true, context), "", "", "°", None);
|
||||
let angles = coordinate_widget(ParameterWidgetsInfo::new(node_id, AnglesInput::INDEX, true, context), "", "", "°", None);
|
||||
widgets.extend([spacing, angles]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let columns = number_widget(
|
||||
ParameterWidgetsInfo::from_index(document_node, node_id, ColumnsInput::INDEX, true, context),
|
||||
NumberInput::default().min(1.),
|
||||
);
|
||||
let rows = number_widget(
|
||||
ParameterWidgetsInfo::from_index(document_node, node_id, RowsInput::INDEX, true, context),
|
||||
NumberInput::default().min(1.),
|
||||
);
|
||||
let columns = number_widget(ParameterWidgetsInfo::new(node_id, ColumnsInput::INDEX, true, context), NumberInput::default().min(1.));
|
||||
let rows = number_widget(ParameterWidgetsInfo::new(node_id, RowsInput::INDEX, true, context), NumberInput::default().min(1.));
|
||||
|
||||
widgets.extend([LayoutGroup::Row { widgets: columns }, LayoutGroup::Row { widgets: rows }]);
|
||||
|
||||
@@ -1322,26 +1297,14 @@ pub(crate) fn sample_polyline_properties(node_id: NodeId, context: &mut NodeProp
|
||||
let is_quantity = matches!(current_spacing, Some(TaggedValue::PointSpacingType(PointSpacingType::Quantity)));
|
||||
|
||||
let spacing = enum_choice::<PointSpacingType>()
|
||||
.for_socket(ParameterWidgetsInfo::from_index(document_node, node_id, SpacingInput::INDEX, true, context))
|
||||
.for_socket(ParameterWidgetsInfo::new(node_id, SpacingInput::INDEX, true, context))
|
||||
.property_row();
|
||||
let separation = number_widget(
|
||||
ParameterWidgetsInfo::from_index(document_node, node_id, SeparationInput::INDEX, true, context),
|
||||
NumberInput::default().min(0.).unit(" px"),
|
||||
);
|
||||
let quantity = number_widget(
|
||||
ParameterWidgetsInfo::from_index(document_node, node_id, QuantityInput::INDEX, true, context),
|
||||
NumberInput::default().min(2.).int(),
|
||||
);
|
||||
let start_offset = number_widget(
|
||||
ParameterWidgetsInfo::from_index(document_node, node_id, StartOffsetInput::INDEX, true, context),
|
||||
NumberInput::default().min(0.).unit(" px"),
|
||||
);
|
||||
let stop_offset = number_widget(
|
||||
ParameterWidgetsInfo::from_index(document_node, node_id, StopOffsetInput::INDEX, true, context),
|
||||
NumberInput::default().min(0.).unit(" px"),
|
||||
);
|
||||
let separation = number_widget(ParameterWidgetsInfo::new(node_id, SeparationInput::INDEX, true, context), NumberInput::default().min(0.).unit(" px"));
|
||||
let quantity = number_widget(ParameterWidgetsInfo::new(node_id, QuantityInput::INDEX, true, context), NumberInput::default().min(2.).int());
|
||||
let start_offset = number_widget(ParameterWidgetsInfo::new(node_id, StartOffsetInput::INDEX, true, context), NumberInput::default().min(0.).unit(" px"));
|
||||
let stop_offset = number_widget(ParameterWidgetsInfo::new(node_id, StopOffsetInput::INDEX, true, context), NumberInput::default().min(0.).unit(" px"));
|
||||
let adaptive_spacing = bool_widget(
|
||||
ParameterWidgetsInfo::from_index(document_node, node_id, AdaptiveSpacingInput::INDEX, true, context),
|
||||
ParameterWidgetsInfo::new(node_id, AdaptiveSpacingInput::INDEX, true, context),
|
||||
CheckboxInput::default().disabled(is_quantity),
|
||||
);
|
||||
|
||||
@@ -1361,23 +1324,10 @@ pub(crate) fn sample_polyline_properties(node_id: NodeId, context: &mut NodeProp
|
||||
pub(crate) fn exposure_properties(node_id: NodeId, context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
|
||||
use graphene_std::raster::exposure::*;
|
||||
|
||||
let document_node = match get_document_node(node_id, context) {
|
||||
Ok(document_node) => document_node,
|
||||
Err(err) => {
|
||||
log::error!("Could not get document node in exposure_properties: {err}");
|
||||
return Vec::new();
|
||||
}
|
||||
};
|
||||
let exposure = number_widget(
|
||||
ParameterWidgetsInfo::from_index(document_node, node_id, ExposureInput::INDEX, true, context),
|
||||
NumberInput::default().min(-20.).max(20.),
|
||||
);
|
||||
let offset = number_widget(
|
||||
ParameterWidgetsInfo::from_index(document_node, node_id, OffsetInput::INDEX, true, context),
|
||||
NumberInput::default().min(-0.5).max(0.5),
|
||||
);
|
||||
let exposure = number_widget(ParameterWidgetsInfo::new(node_id, ExposureInput::INDEX, true, context), NumberInput::default().min(-20.).max(20.));
|
||||
let offset = number_widget(ParameterWidgetsInfo::new(node_id, OffsetInput::INDEX, true, context), NumberInput::default().min(-0.5).max(0.5));
|
||||
let gamma_correction = number_widget(
|
||||
ParameterWidgetsInfo::from_index(document_node, node_id, GammaCorrectionInput::INDEX, true, context),
|
||||
ParameterWidgetsInfo::new(node_id, GammaCorrectionInput::INDEX, true, context),
|
||||
NumberInput::default().min(0.01).max(9.99).increment_step(0.1),
|
||||
);
|
||||
|
||||
@@ -1391,6 +1341,14 @@ pub(crate) fn exposure_properties(node_id: NodeId, context: &mut NodePropertiesC
|
||||
pub(crate) fn rectangle_properties(node_id: NodeId, context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
|
||||
use graphene_std::vector::generator_nodes::rectangle::*;
|
||||
|
||||
// Corner Radius
|
||||
let mut corner_radius_row_1 = start_widgets(ParameterWidgetsInfo::new(node_id, CornerRadiusInput::<f64>::INDEX, true, context));
|
||||
corner_radius_row_1.push(Separator::new(SeparatorType::Unrelated).widget_holder());
|
||||
|
||||
let mut corner_radius_row_2 = vec![Separator::new(SeparatorType::Unrelated).widget_holder()];
|
||||
corner_radius_row_2.push(TextLabel::new("").widget_holder());
|
||||
add_blank_assist(&mut corner_radius_row_2);
|
||||
|
||||
let document_node = match get_document_node(node_id, context) {
|
||||
Ok(document_node) => document_node,
|
||||
Err(err) => {
|
||||
@@ -1398,23 +1356,6 @@ pub(crate) fn rectangle_properties(node_id: NodeId, context: &mut NodeProperties
|
||||
return Vec::new();
|
||||
}
|
||||
};
|
||||
// Size X
|
||||
let size_x = number_widget(ParameterWidgetsInfo::from_index(document_node, node_id, WidthInput::INDEX, true, context), NumberInput::default());
|
||||
|
||||
// Size Y
|
||||
let size_y = number_widget(ParameterWidgetsInfo::from_index(document_node, node_id, HeightInput::INDEX, true, context), NumberInput::default());
|
||||
|
||||
// Corner Radius
|
||||
let mut corner_radius_row_1 = start_widgets(
|
||||
ParameterWidgetsInfo::from_index(document_node, node_id, CornerRadiusInput::<f64>::INDEX, true, context),
|
||||
FrontendGraphDataType::Number,
|
||||
);
|
||||
corner_radius_row_1.push(Separator::new(SeparatorType::Unrelated).widget_holder());
|
||||
|
||||
let mut corner_radius_row_2 = vec![Separator::new(SeparatorType::Unrelated).widget_holder()];
|
||||
corner_radius_row_2.push(TextLabel::new("").widget_holder());
|
||||
add_blank_assist(&mut corner_radius_row_2);
|
||||
|
||||
let Some(input) = document_node.inputs.get(IndividualCornerRadiiInput::INDEX) else {
|
||||
log::warn!("A widget failed to be built because its node's input index is invalid.");
|
||||
return vec![];
|
||||
@@ -1508,8 +1449,14 @@ pub(crate) fn rectangle_properties(node_id: NodeId, context: &mut NodeProperties
|
||||
corner_radius_row_2.push(input_widget);
|
||||
}
|
||||
|
||||
// Size X
|
||||
let size_x = number_widget(ParameterWidgetsInfo::new(node_id, WidthInput::INDEX, true, context), NumberInput::default());
|
||||
|
||||
// Size Y
|
||||
let size_y = number_widget(ParameterWidgetsInfo::new(node_id, HeightInput::INDEX, true, context), NumberInput::default());
|
||||
|
||||
// Clamped
|
||||
let clamped = bool_widget(ParameterWidgetsInfo::from_index(document_node, node_id, ClampedInput::INDEX, true, context), CheckboxInput::default());
|
||||
let clamped = bool_widget(ParameterWidgetsInfo::new(node_id, ClampedInput::INDEX, true, context), CheckboxInput::default());
|
||||
|
||||
vec![
|
||||
LayoutGroup::Row { widgets: size_x },
|
||||
@@ -1561,7 +1508,7 @@ pub(crate) fn generate_node_properties(node_id: NodeId, context: &mut NodeProper
|
||||
if let Some(field) = graphene_std::registry::NODE_METADATA
|
||||
.lock()
|
||||
.unwrap()
|
||||
.get(&proto_node_identifier.name.clone().into_owned())
|
||||
.get(proto_node_identifier)
|
||||
.and_then(|metadata| metadata.fields.get(input_index))
|
||||
{
|
||||
number_options = (field.number_min, field.number_max, field.number_mode_range);
|
||||
@@ -1638,6 +1585,8 @@ pub(crate) fn generate_node_properties(node_id: NodeId, context: &mut NodeProper
|
||||
pub(crate) fn fill_properties(node_id: NodeId, context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
|
||||
use graphene_std::vector::fill::*;
|
||||
|
||||
let mut widgets_first_row = start_widgets(ParameterWidgetsInfo::new(node_id, FillInput::<Color>::INDEX, true, context));
|
||||
|
||||
let document_node = match get_document_node(node_id, context) {
|
||||
Ok(document_node) => document_node,
|
||||
Err(err) => {
|
||||
@@ -1646,11 +1595,6 @@ pub(crate) fn fill_properties(node_id: NodeId, context: &mut NodePropertiesConte
|
||||
}
|
||||
};
|
||||
|
||||
let mut widgets_first_row = start_widgets(
|
||||
ParameterWidgetsInfo::from_index(document_node, node_id, FillInput::<Color>::INDEX, true, context),
|
||||
FrontendGraphDataType::General,
|
||||
);
|
||||
|
||||
let (fill, backup_color, backup_gradient) = if let (Some(TaggedValue::Fill(fill)), &Some(&TaggedValue::OptionalColor(backup_color)), Some(TaggedValue::Gradient(backup_gradient))) = (
|
||||
&document_node.inputs[FillInput::<Color>::INDEX].as_value(),
|
||||
&document_node.inputs[BackupColorInput::INDEX].as_value(),
|
||||
@@ -1829,47 +1773,42 @@ pub fn stroke_properties(node_id: NodeId, context: &mut NodePropertiesContext) -
|
||||
return Vec::new();
|
||||
}
|
||||
};
|
||||
let join_value = match &document_node.inputs[JoinInput::INDEX].as_value() {
|
||||
Some(TaggedValue::StrokeJoin(x)) => x,
|
||||
_ => &StrokeJoin::Miter,
|
||||
};
|
||||
|
||||
let color = color_widget(
|
||||
ParameterWidgetsInfo::from_index(document_node, node_id, ColorInput::<Option<Color>>::INDEX, true, context),
|
||||
crate::messages::layout::utility_types::widgets::button_widgets::ColorInput::default(),
|
||||
);
|
||||
let weight = number_widget(
|
||||
ParameterWidgetsInfo::from_index(document_node, node_id, WeightInput::INDEX, true, context),
|
||||
NumberInput::default().unit(" px").min(0.),
|
||||
);
|
||||
let align = enum_choice::<StrokeAlign>()
|
||||
.for_socket(ParameterWidgetsInfo::from_index(document_node, node_id, AlignInput::INDEX, true, context))
|
||||
.property_row();
|
||||
let cap = enum_choice::<StrokeCap>()
|
||||
.for_socket(ParameterWidgetsInfo::from_index(document_node, node_id, CapInput::INDEX, true, context))
|
||||
.property_row();
|
||||
let join = enum_choice::<StrokeJoin>()
|
||||
.for_socket(ParameterWidgetsInfo::from_index(document_node, node_id, JoinInput::INDEX, true, context))
|
||||
.property_row();
|
||||
let miter_limit = number_widget(
|
||||
ParameterWidgetsInfo::from_index(document_node, node_id, MiterLimitInput::INDEX, true, context),
|
||||
NumberInput::default().min(0.).disabled({
|
||||
let join_value = match &document_node.inputs[JoinInput::INDEX].as_value() {
|
||||
Some(TaggedValue::StrokeJoin(x)) => x,
|
||||
_ => &StrokeJoin::Miter,
|
||||
};
|
||||
join_value != &StrokeJoin::Miter
|
||||
}),
|
||||
);
|
||||
let paint_order = enum_choice::<PaintOrder>()
|
||||
.for_socket(ParameterWidgetsInfo::from_index(document_node, node_id, PaintOrderInput::INDEX, true, context))
|
||||
.property_row();
|
||||
let dash_lengths_val = match &document_node.inputs[DashLengthsInput::INDEX].as_value() {
|
||||
Some(TaggedValue::VecF64(x)) => x,
|
||||
_ => &vec![],
|
||||
};
|
||||
let dash_lengths = array_of_number_widget(
|
||||
ParameterWidgetsInfo::from_index(document_node, node_id, DashLengthsInput::INDEX, true, context),
|
||||
TextInput::default().centered(true),
|
||||
let has_dash_lengths = dash_lengths_val.is_empty();
|
||||
let miter_limit_disabled = join_value != &StrokeJoin::Miter;
|
||||
|
||||
let color = color_widget(
|
||||
ParameterWidgetsInfo::new(node_id, ColorInput::<Option<Color>>::INDEX, true, context),
|
||||
crate::messages::layout::utility_types::widgets::button_widgets::ColorInput::default(),
|
||||
);
|
||||
let number_input = NumberInput::default().unit(" px").disabled(dash_lengths_val.is_empty());
|
||||
let dash_offset = number_widget(ParameterWidgetsInfo::from_index(document_node, node_id, DashOffsetInput::INDEX, true, context), number_input);
|
||||
let weight = number_widget(ParameterWidgetsInfo::new(node_id, WeightInput::INDEX, true, context), NumberInput::default().unit(" px").min(0.));
|
||||
let align = enum_choice::<StrokeAlign>()
|
||||
.for_socket(ParameterWidgetsInfo::new(node_id, AlignInput::INDEX, true, context))
|
||||
.property_row();
|
||||
let cap = enum_choice::<StrokeCap>().for_socket(ParameterWidgetsInfo::new(node_id, CapInput::INDEX, true, context)).property_row();
|
||||
let join = enum_choice::<StrokeJoin>()
|
||||
.for_socket(ParameterWidgetsInfo::new(node_id, JoinInput::INDEX, true, context))
|
||||
.property_row();
|
||||
|
||||
let miter_limit = number_widget(
|
||||
ParameterWidgetsInfo::new(node_id, MiterLimitInput::INDEX, true, context),
|
||||
NumberInput::default().min(0.).disabled(miter_limit_disabled),
|
||||
);
|
||||
let paint_order = enum_choice::<PaintOrder>()
|
||||
.for_socket(ParameterWidgetsInfo::new(node_id, PaintOrderInput::INDEX, true, context))
|
||||
.property_row();
|
||||
let disabled_number_input = NumberInput::default().unit(" px").disabled(has_dash_lengths);
|
||||
let dash_lengths = array_of_number_widget(ParameterWidgetsInfo::new(node_id, DashLengthsInput::INDEX, true, context), TextInput::default().centered(true));
|
||||
let number_input = disabled_number_input;
|
||||
let dash_offset = number_widget(ParameterWidgetsInfo::new(node_id, DashOffsetInput::INDEX, true, context), number_input);
|
||||
|
||||
vec![
|
||||
color,
|
||||
@@ -1887,6 +1826,13 @@ pub fn stroke_properties(node_id: NodeId, context: &mut NodePropertiesContext) -
|
||||
pub fn offset_path_properties(node_id: NodeId, context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
|
||||
use graphene_std::vector::offset_path::*;
|
||||
|
||||
let number_input = NumberInput::default().unit(" px");
|
||||
let distance = number_widget(ParameterWidgetsInfo::new(node_id, DistanceInput::INDEX, true, context), number_input);
|
||||
|
||||
let join = enum_choice::<StrokeJoin>()
|
||||
.for_socket(ParameterWidgetsInfo::new(node_id, JoinInput::INDEX, true, context))
|
||||
.property_row();
|
||||
|
||||
let document_node = match get_document_node(node_id, context) {
|
||||
Ok(document_node) => document_node,
|
||||
Err(err) => {
|
||||
@@ -1894,13 +1840,6 @@ pub fn offset_path_properties(node_id: NodeId, context: &mut NodePropertiesConte
|
||||
return Vec::new();
|
||||
}
|
||||
};
|
||||
let number_input = NumberInput::default().unit(" px");
|
||||
let distance = number_widget(ParameterWidgetsInfo::from_index(document_node, node_id, DistanceInput::INDEX, true, context), number_input);
|
||||
|
||||
let join = enum_choice::<StrokeJoin>()
|
||||
.for_socket(ParameterWidgetsInfo::from_index(document_node, node_id, JoinInput::INDEX, true, context))
|
||||
.property_row();
|
||||
|
||||
let number_input = NumberInput::default().min(0.).disabled({
|
||||
let join_val = match &document_node.inputs[JoinInput::INDEX].as_value() {
|
||||
Some(TaggedValue::StrokeJoin(x)) => x,
|
||||
@@ -1908,7 +1847,7 @@ pub fn offset_path_properties(node_id: NodeId, context: &mut NodePropertiesConte
|
||||
};
|
||||
join_val != &StrokeJoin::Miter
|
||||
});
|
||||
let miter_limit = number_widget(ParameterWidgetsInfo::from_index(document_node, node_id, MiterLimitInput::INDEX, true, context), number_input);
|
||||
let miter_limit = number_widget(ParameterWidgetsInfo::new(node_id, MiterLimitInput::INDEX, true, context), number_input);
|
||||
|
||||
vec![LayoutGroup::Row { widgets: distance }, join, LayoutGroup::Row { widgets: miter_limit }]
|
||||
}
|
||||
@@ -1916,20 +1855,16 @@ pub fn offset_path_properties(node_id: NodeId, context: &mut NodePropertiesConte
|
||||
pub fn math_properties(node_id: NodeId, context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
|
||||
use graphene_std::math_nodes::math::*;
|
||||
|
||||
let document_node = match get_document_node(node_id, context) {
|
||||
Ok(document_node) => document_node,
|
||||
Err(err) => {
|
||||
log::error!("Could not get document node in offset_path_properties: {err}");
|
||||
return Vec::new();
|
||||
}
|
||||
};
|
||||
|
||||
let expression = (|| {
|
||||
let mut widgets = start_widgets(
|
||||
ParameterWidgetsInfo::from_index(document_node, node_id, ExpressionInput::INDEX, true, context),
|
||||
FrontendGraphDataType::General,
|
||||
);
|
||||
let mut widgets = start_widgets(ParameterWidgetsInfo::new(node_id, ExpressionInput::INDEX, true, context));
|
||||
|
||||
let document_node = match get_document_node(node_id, context) {
|
||||
Ok(document_node) => document_node,
|
||||
Err(err) => {
|
||||
log::error!("Could not get document node in offset_path_properties: {err}");
|
||||
return Vec::new();
|
||||
}
|
||||
};
|
||||
let Some(input) = document_node.inputs.get(ExpressionInput::INDEX) else {
|
||||
log::warn!("A widget failed to be built because its node's input index is invalid.");
|
||||
return vec![];
|
||||
@@ -1962,10 +1897,7 @@ pub fn math_properties(node_id: NodeId, context: &mut NodePropertiesContext) ->
|
||||
}
|
||||
widgets
|
||||
})();
|
||||
let operand_b = number_widget(
|
||||
ParameterWidgetsInfo::from_index(document_node, node_id, OperandBInput::<f64>::INDEX, true, context),
|
||||
NumberInput::default(),
|
||||
);
|
||||
let operand_b = number_widget(ParameterWidgetsInfo::new(node_id, OperandBInput::<f64>::INDEX, true, context), NumberInput::default());
|
||||
let operand_a_hint = vec![TextLabel::new("(Operand A is the primary input)").widget_holder()];
|
||||
|
||||
vec![
|
||||
@@ -1976,44 +1908,37 @@ pub fn math_properties(node_id: NodeId, context: &mut NodePropertiesContext) ->
|
||||
}
|
||||
|
||||
pub struct ParameterWidgetsInfo<'a> {
|
||||
document_node: &'a DocumentNode,
|
||||
document_node: Option<&'a DocumentNode>,
|
||||
node_id: NodeId,
|
||||
index: usize,
|
||||
name: &'a str,
|
||||
description: &'a str,
|
||||
name: String,
|
||||
description: String,
|
||||
input_type: FrontendGraphDataType,
|
||||
blank_assist: bool,
|
||||
exposeable: bool,
|
||||
}
|
||||
|
||||
impl<'a> ParameterWidgetsInfo<'a> {
|
||||
pub fn new(document_node: &'a DocumentNode, node_id: NodeId, index: usize, name: &'a str, description: &'a str, blank_assist: bool) -> ParameterWidgetsInfo<'a> {
|
||||
pub fn new(node_id: NodeId, index: usize, blank_assist: bool, context: &'a mut NodePropertiesContext) -> ParameterWidgetsInfo<'a> {
|
||||
let (name, description) = context.network_interface.displayed_input_name_and_description(&node_id, index, context.selection_network_path);
|
||||
let input_type = FrontendGraphDataType::from_type(&context.network_interface.input_type(&InputConnector::node(node_id, index), context.selection_network_path).0);
|
||||
let document_node = context.network_interface.document_node(&node_id, context.selection_network_path);
|
||||
|
||||
ParameterWidgetsInfo {
|
||||
document_node,
|
||||
node_id,
|
||||
index,
|
||||
name,
|
||||
description,
|
||||
input_type,
|
||||
blank_assist,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_index(document_node: &'a DocumentNode, node_id: NodeId, index: usize, blank_assist: bool, context: &'a NodePropertiesContext) -> ParameterWidgetsInfo<'a> {
|
||||
let name = context.network_interface.input_name(node_id, index, context.selection_network_path).unwrap_or_default();
|
||||
let description = context.network_interface.input_description(node_id, index, context.selection_network_path).unwrap_or_default();
|
||||
|
||||
Self {
|
||||
document_node,
|
||||
node_id,
|
||||
index,
|
||||
name,
|
||||
description,
|
||||
blank_assist,
|
||||
exposeable: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub mod choice {
|
||||
use super::ParameterWidgetsInfo;
|
||||
use crate::messages::portfolio::document::node_graph::utility_types::FrontendGraphDataType;
|
||||
use crate::messages::tool::tool_messages::tool_prelude::*;
|
||||
use graph_craft::document::value::TaggedValue;
|
||||
use graphene_std::registry::{ChoiceTypeStatic, ChoiceWidgetHint};
|
||||
@@ -2046,11 +1971,7 @@ pub mod choice {
|
||||
|
||||
impl<E: ChoiceTypeStatic + 'static> EnumChoice<E> {
|
||||
pub fn for_socket(self, parameter_info: ParameterWidgetsInfo) -> ForSocket<Self> {
|
||||
ForSocket {
|
||||
widget_factory: self,
|
||||
parameter_info,
|
||||
exposable: true,
|
||||
}
|
||||
ForSocket { widget_factory: self, parameter_info }
|
||||
}
|
||||
|
||||
/// Not yet implemented!
|
||||
@@ -2141,7 +2062,6 @@ pub mod choice {
|
||||
pub struct ForSocket<'p, W> {
|
||||
widget_factory: W,
|
||||
parameter_info: ParameterWidgetsInfo<'p>,
|
||||
exposable: bool,
|
||||
}
|
||||
|
||||
impl<'p, W> ForSocket<'p, W>
|
||||
@@ -2158,14 +2078,14 @@ pub mod choice {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn exposable(self, exposable: bool) -> Self {
|
||||
Self { exposable, ..self }
|
||||
}
|
||||
|
||||
pub fn property_row(self) -> LayoutGroup {
|
||||
let ParameterWidgetsInfo { document_node, node_id, index, .. } = self.parameter_info;
|
||||
let Some(document_node) = document_node else {
|
||||
log::error!("Could not get document node when building property row for node {:?}", node_id);
|
||||
return LayoutGroup::Row { widgets: Vec::new() };
|
||||
};
|
||||
|
||||
let mut widgets = super::start_widgets_exposable(self.parameter_info, FrontendGraphDataType::General, self.exposable);
|
||||
let mut widgets = super::start_widgets(self.parameter_info);
|
||||
|
||||
let Some(input) = document_node.inputs.get(index) else {
|
||||
log::warn!("A widget failed to be built because its node's input index is invalid.");
|
||||
|
||||
@@ -2,6 +2,7 @@ use crate::messages::portfolio::document::utility_types::network_interface::{Inp
|
||||
use graph_craft::document::NodeId;
|
||||
use graph_craft::document::value::TaggedValue;
|
||||
use graphene_std::Type;
|
||||
use std::borrow::Cow;
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Hash, serde::Serialize, serde::Deserialize, specta::Type)]
|
||||
pub enum FrontendGraphDataType {
|
||||
@@ -15,7 +16,7 @@ pub enum FrontendGraphDataType {
|
||||
}
|
||||
|
||||
impl FrontendGraphDataType {
|
||||
fn with_type(input: &Type) -> Self {
|
||||
pub fn from_type(input: &Type) -> Self {
|
||||
match TaggedValue::from_type_or_none(input) {
|
||||
TaggedValue::Image(_) | TaggedValue::RasterData(_) => Self::Raster,
|
||||
TaggedValue::Subpaths(_) | TaggedValue::VectorData(_) => Self::VectorData,
|
||||
@@ -38,7 +39,7 @@ impl FrontendGraphDataType {
|
||||
pub fn displayed_type(input: &Type, type_source: &TypeSource) -> Self {
|
||||
match type_source {
|
||||
TypeSource::Error(_) | TypeSource::RandomProtonodeImplementation => Self::General,
|
||||
_ => Self::with_type(input),
|
||||
_ => Self::from_type(input),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -50,7 +51,7 @@ pub struct FrontendGraphInput {
|
||||
pub name: String,
|
||||
pub description: String,
|
||||
#[serde(rename = "resolvedType")]
|
||||
pub resolved_type: Option<String>,
|
||||
pub resolved_type: String,
|
||||
#[serde(rename = "validTypes")]
|
||||
pub valid_types: Vec<String>,
|
||||
#[serde(rename = "connectedTo")]
|
||||
@@ -64,7 +65,7 @@ pub struct FrontendGraphOutput {
|
||||
pub name: String,
|
||||
pub description: String,
|
||||
#[serde(rename = "resolvedType")]
|
||||
pub resolved_type: Option<String>,
|
||||
pub resolved_type: String,
|
||||
#[serde(rename = "connectedTo")]
|
||||
pub connected_to: Vec<InputConnector>,
|
||||
}
|
||||
@@ -96,44 +97,27 @@ pub struct FrontendNode {
|
||||
pub ui_only: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
|
||||
pub struct FrontendNodeWire {
|
||||
#[serde(rename = "wireStart")]
|
||||
pub wire_start: OutputConnector,
|
||||
#[serde(rename = "wireEnd")]
|
||||
pub wire_end: InputConnector,
|
||||
pub dashed: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
|
||||
pub struct FrontendNodeType {
|
||||
pub name: String,
|
||||
pub category: String,
|
||||
pub name: Cow<'static, str>,
|
||||
pub category: Cow<'static, str>,
|
||||
#[serde(rename = "inputTypes")]
|
||||
pub input_types: Option<Vec<String>>,
|
||||
pub input_types: Option<Vec<Cow<'static, str>>>,
|
||||
}
|
||||
|
||||
impl FrontendNodeType {
|
||||
pub fn new(name: &'static str, category: &'static str) -> Self {
|
||||
pub fn new(name: impl Into<Cow<'static, str>>, category: impl Into<Cow<'static, str>>) -> Self {
|
||||
Self {
|
||||
name: name.to_string(),
|
||||
category: category.to_string(),
|
||||
name: name.into(),
|
||||
category: category.into(),
|
||||
input_types: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_input_types(name: &'static str, category: &'static str, input_types: Vec<String>) -> Self {
|
||||
pub fn with_input_types(name: impl Into<Cow<'static, str>>, category: impl Into<Cow<'static, str>>, input_types: Vec<Cow<'static, str>>) -> Self {
|
||||
Self {
|
||||
name: name.to_string(),
|
||||
category: category.to_string(),
|
||||
input_types: Some(input_types),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_owned_strings_and_input_types(name: String, category: String, input_types: Vec<String>) -> Self {
|
||||
Self {
|
||||
name,
|
||||
category,
|
||||
name: name.into(),
|
||||
category: category.into(),
|
||||
input_types: Some(input_types),
|
||||
}
|
||||
}
|
||||
@@ -153,16 +137,6 @@ pub struct Transform {
|
||||
pub y: f64,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
|
||||
pub struct WirePath {
|
||||
#[serde(rename = "pathString")]
|
||||
pub path_string: String,
|
||||
#[serde(rename = "dataType")]
|
||||
pub data_type: FrontendGraphDataType,
|
||||
pub thick: bool,
|
||||
pub dashed: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
|
||||
pub struct BoxSelection {
|
||||
#[serde(rename = "startX")]
|
||||
@@ -217,39 +191,10 @@ pub struct FrontendClickTargets {
|
||||
pub modify_import_export: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
|
||||
pub enum Direction {
|
||||
Up,
|
||||
Down,
|
||||
Left,
|
||||
Right,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Default, serde::Serialize, serde::Deserialize, specta::Type)]
|
||||
pub enum GraphWireStyle {
|
||||
#[default]
|
||||
Direct = 0,
|
||||
GridAligned = 1,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for GraphWireStyle {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
GraphWireStyle::GridAligned => write!(f, "Grid-Aligned"),
|
||||
GraphWireStyle::Direct => write!(f, "Direct"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl GraphWireStyle {
|
||||
pub fn tooltip_description(&self) -> &'static str {
|
||||
match self {
|
||||
GraphWireStyle::GridAligned => "Wires follow the grid, running in straight lines between nodes",
|
||||
GraphWireStyle::Direct => "Wires bend to run at an angle directly between nodes",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_direct(&self) -> bool {
|
||||
*self == GraphWireStyle::Direct
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
use super::utility_types::{OverlayProvider, OverlaysVisibilitySettings};
|
||||
use crate::messages::prelude::*;
|
||||
|
||||
#[derive(ExtractField)]
|
||||
pub struct OverlaysMessageData<'a> {
|
||||
pub visibility_settings: OverlaysVisibilitySettings,
|
||||
pub ipp: &'a InputPreprocessorMessageHandler,
|
||||
pub device_pixel_ratio: f64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
#[derive(Debug, Clone, Default, ExtractField)]
|
||||
pub struct OverlaysMessageHandler {
|
||||
pub overlay_providers: HashSet<OverlayProvider>,
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
@@ -16,6 +17,7 @@ pub struct OverlaysMessageHandler {
|
||||
context: Option<web_sys::CanvasRenderingContext2d>,
|
||||
}
|
||||
|
||||
#[message_handler_data]
|
||||
impl MessageHandler<OverlaysMessage, OverlaysMessageData<'_>> for OverlaysMessageHandler {
|
||||
fn process_message(&mut self, message: OverlaysMessage, responses: &mut VecDeque<Message>, data: OverlaysMessageData) {
|
||||
let OverlaysMessageData { visibility_settings, ipp, .. } = data;
|
||||
|
||||
@@ -119,7 +119,7 @@ pub fn path_overlays(document: &DocumentMessageHandler, draw_handles: DrawHandle
|
||||
|
||||
for layer in document.network_interface.selected_nodes().selected_layers(document.metadata()) {
|
||||
let Some(vector_data) = document.network_interface.compute_modified_vector(layer) else { continue };
|
||||
let transform = document.metadata().transform_to_viewport(layer);
|
||||
let transform = document.metadata().transform_to_viewport_if_feeds(layer, &document.network_interface);
|
||||
if display_path {
|
||||
overlay_context.outline_vector(&vector_data, transform);
|
||||
}
|
||||
@@ -196,7 +196,7 @@ pub fn path_endpoint_overlays(document: &DocumentMessageHandler, shape_editor: &
|
||||
continue;
|
||||
};
|
||||
//let document_to_viewport = document.navigation_handler.calculate_offset_transform(overlay_context.size / 2., &document.document_ptz);
|
||||
let transform = document.metadata().transform_to_viewport(layer);
|
||||
let transform = document.metadata().transform_to_viewport_if_feeds(layer, &document.network_interface);
|
||||
let selected = shape_editor.selected_shape_state.get(&layer);
|
||||
let is_selected = |selected: Option<&SelectedLayerState>, point: ManipulatorPointId| selected.is_some_and(|selected| selected.is_point_selected(point));
|
||||
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
use super::utility_functions::overlay_canvas_context;
|
||||
use crate::consts::{
|
||||
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,
|
||||
COMPASS_ROSE_MAIN_RING_DIAMETER, COMPASS_ROSE_RING_INNER_DIAMETER, MANIPULATOR_GROUP_MARKER_SIZE, PIVOT_CROSSHAIR_LENGTH, PIVOT_CROSSHAIR_THICKNESS, PIVOT_DIAMETER,
|
||||
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,
|
||||
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,
|
||||
};
|
||||
use crate::messages::prelude::Message;
|
||||
use bezier_rs::{Bezier, Subpath};
|
||||
use core::borrow::Borrow;
|
||||
use core::f64::consts::{FRAC_PI_2, TAU};
|
||||
use core::f64::consts::{FRAC_PI_2, PI, TAU};
|
||||
use glam::{DAffine2, DVec2};
|
||||
use graphene_std::Color;
|
||||
use graphene_std::math::quad::Quad;
|
||||
@@ -33,12 +33,14 @@ pub enum OverlaysType {
|
||||
HoverOutline,
|
||||
SelectionOutline,
|
||||
Pivot,
|
||||
Origin,
|
||||
Path,
|
||||
Anchors,
|
||||
Handles,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Copy, Clone, Debug, serde::Serialize, serde::Deserialize, specta::Type)]
|
||||
#[serde(default)]
|
||||
pub struct OverlaysVisibilitySettings {
|
||||
pub all: bool,
|
||||
pub artboard_name: bool,
|
||||
@@ -49,6 +51,7 @@ pub struct OverlaysVisibilitySettings {
|
||||
pub hover_outline: bool,
|
||||
pub selection_outline: bool,
|
||||
pub pivot: bool,
|
||||
pub origin: bool,
|
||||
pub path: bool,
|
||||
pub anchors: bool,
|
||||
pub handles: bool,
|
||||
@@ -66,6 +69,7 @@ impl Default for OverlaysVisibilitySettings {
|
||||
hover_outline: true,
|
||||
selection_outline: true,
|
||||
pivot: true,
|
||||
origin: true,
|
||||
path: true,
|
||||
anchors: true,
|
||||
handles: true,
|
||||
@@ -110,6 +114,10 @@ impl OverlaysVisibilitySettings {
|
||||
self.all && self.pivot
|
||||
}
|
||||
|
||||
pub fn origin(&self) -> bool {
|
||||
self.all && self.origin
|
||||
}
|
||||
|
||||
pub fn path(&self) -> bool {
|
||||
self.all && self.path
|
||||
}
|
||||
@@ -423,10 +431,7 @@ impl OverlayContext {
|
||||
|
||||
pub fn draw_scale(&mut self, start: DVec2, scale: f64, radius: f64, text: &str) {
|
||||
let sign = scale.signum();
|
||||
let mut fill_color = graphene_std::Color::from_rgb_str(crate::consts::COLOR_OVERLAY_WHITE.strip_prefix('#').unwrap())
|
||||
.unwrap()
|
||||
.with_alpha(0.05)
|
||||
.to_rgba_hex_srgb();
|
||||
let mut fill_color = Color::from_rgb_str(COLOR_OVERLAY_WHITE.strip_prefix('#').unwrap()).unwrap().with_alpha(0.05).to_rgba_hex_srgb();
|
||||
fill_color.insert(0, '#');
|
||||
let fill_color = Some(fill_color.as_str());
|
||||
self.line(start + DVec2::X * radius * sign, start + DVec2::X * (radius * scale), None, None);
|
||||
@@ -463,10 +468,7 @@ impl OverlayContext {
|
||||
|
||||
// Hover ring
|
||||
if show_hover_ring {
|
||||
let mut fill_color = graphene_std::Color::from_rgb_str(COLOR_OVERLAY_BLUE.strip_prefix('#').unwrap())
|
||||
.unwrap()
|
||||
.with_alpha(0.5)
|
||||
.to_rgba_hex_srgb();
|
||||
let mut fill_color = Color::from_rgb_str(COLOR_OVERLAY_BLUE.strip_prefix('#').unwrap()).unwrap().with_alpha(0.5).to_rgba_hex_srgb();
|
||||
fill_color.insert(0, '#');
|
||||
|
||||
self.render_context.set_line_width(HOVER_RING_STROKE_WIDTH);
|
||||
@@ -550,6 +552,36 @@ impl OverlayContext {
|
||||
self.end_dpi_aware_transform();
|
||||
}
|
||||
|
||||
pub fn dowel_pin(&mut self, position: DVec2, angle: f64, color: Option<&str>) {
|
||||
let (x, y) = (position.round() - DVec2::splat(0.5)).into();
|
||||
let color = color.unwrap_or(COLOR_OVERLAY_YELLOW_DULL);
|
||||
|
||||
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);
|
||||
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 + angle, PI + angle).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 + angle, TAU + angle).expect("Failed to draw arc");
|
||||
self.render_context.close_path();
|
||||
self.render_context.set_fill_style_str(color);
|
||||
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();
|
||||
@@ -599,9 +631,11 @@ impl OverlayContext {
|
||||
pub fn outline_overlay_bezier(&mut self, bezier: Bezier, transform: DAffine2) {
|
||||
self.start_dpi_aware_transform();
|
||||
|
||||
let color = Color::from_rgb_str(COLOR_OVERLAY_BLUE.strip_prefix('#').unwrap()).unwrap().with_alpha(0.05).to_rgba_hex_srgb();
|
||||
|
||||
self.render_context.begin_path();
|
||||
self.bezier_command(bezier, transform, true);
|
||||
self.render_context.set_stroke_style_str(COLOR_OVERLAY_BLUE_50);
|
||||
self.render_context.set_stroke_style_str(&color);
|
||||
self.render_context.set_line_width(4.);
|
||||
self.render_context.stroke();
|
||||
|
||||
@@ -731,11 +765,11 @@ impl OverlayContext {
|
||||
// └──┴──┴──┴──┘
|
||||
let pixels = [(0, 0), (2, 2)];
|
||||
for &(x, y) in &pixels {
|
||||
let index = (x + y * PATTERN_WIDTH as usize) * 4;
|
||||
let index = (x + y * PATTERN_WIDTH) * 4;
|
||||
data[index..index + 4].copy_from_slice(&color.to_rgba8_srgb());
|
||||
}
|
||||
|
||||
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();
|
||||
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();
|
||||
pattern_context.put_image_data(&image_data, 0., 0.).unwrap();
|
||||
let pattern = self.render_context.create_pattern_with_offscreen_canvas(&pattern_canvas, "repeat").unwrap().unwrap();
|
||||
|
||||
@@ -780,6 +814,36 @@ impl OverlayContext {
|
||||
self.render_context.fill_text(text, 0., 0.).expect("Failed to draw the text at the calculated position");
|
||||
self.render_context.reset_transform().expect("Failed to reset the render context transform");
|
||||
}
|
||||
|
||||
pub fn translation_box(&mut self, translation: DVec2, quad: Quad, typed_string: Option<String>) {
|
||||
if translation.x.abs() > 1e-3 {
|
||||
self.dashed_line(quad.top_left(), quad.top_right(), None, None, Some(2.), Some(2.), Some(0.5));
|
||||
|
||||
let width = match typed_string {
|
||||
Some(ref typed_string) => typed_string,
|
||||
None => &format!("{:.2}", translation.x).trim_end_matches('0').trim_end_matches('.').to_string(),
|
||||
};
|
||||
let x_transform = DAffine2::from_translation((quad.top_left() + quad.top_right()) / 2.);
|
||||
self.text(width, COLOR_OVERLAY_BLUE, None, x_transform, 4., [Pivot::Middle, Pivot::End]);
|
||||
}
|
||||
|
||||
if translation.y.abs() > 1e-3 {
|
||||
self.dashed_line(quad.top_left(), quad.bottom_left(), None, None, Some(2.), Some(2.), Some(0.5));
|
||||
|
||||
let height = match typed_string {
|
||||
Some(ref typed_string) => typed_string,
|
||||
None => &format!("{:.2}", translation.y).trim_end_matches('0').trim_end_matches('.').to_string(),
|
||||
};
|
||||
let y_transform = DAffine2::from_translation((quad.top_left() + quad.bottom_left()) / 2.);
|
||||
let height_pivot = if translation.x > -1e-3 { Pivot::Start } else { Pivot::End };
|
||||
self.text(height, COLOR_OVERLAY_BLUE, None, y_transform, 3., [height_pivot, Pivot::Middle]);
|
||||
}
|
||||
|
||||
if translation.x.abs() > 1e-3 && translation.y.abs() > 1e-3 {
|
||||
self.line(quad.top_right(), quad.bottom_right(), None, None);
|
||||
self.line(quad.bottom_left(), quad.bottom_right(), None, None);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub enum Pivot {
|
||||
|
||||
+2
-1
@@ -4,9 +4,10 @@ use crate::messages::portfolio::document::node_graph::document_node_definitions:
|
||||
use crate::messages::portfolio::utility_types::PersistentData;
|
||||
use crate::messages::prelude::*;
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
#[derive(Debug, Clone, Default, ExtractField)]
|
||||
pub struct PropertiesPanelMessageHandler {}
|
||||
|
||||
#[message_handler_data]
|
||||
impl MessageHandler<PropertiesPanelMessage, (&PersistentData, PropertiesPanelMessageHandlerData<'_>)> for PropertiesPanelMessageHandler {
|
||||
fn process_message(&mut self, message: PropertiesPanelMessage, responses: &mut VecDeque<Message>, (persistent_data, data): (&PersistentData, PropertiesPanelMessageHandlerData)) {
|
||||
let PropertiesPanelMessageHandlerData {
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
use super::network_interface::NodeNetworkInterface;
|
||||
use crate::messages::portfolio::document::graph_operation::transform_utils;
|
||||
use crate::messages::portfolio::document::graph_operation::utility_types::ModifyInputsContext;
|
||||
use crate::messages::portfolio::document::utility_types::network_interface::FlowType;
|
||||
use crate::messages::tool::common_functionality::graph_modification_utils;
|
||||
use glam::{DAffine2, DVec2};
|
||||
use graph_craft::document::NodeId;
|
||||
use graphene_std::math::quad::Quad;
|
||||
@@ -16,10 +18,11 @@ use std::num::NonZeroU64;
|
||||
|
||||
// TODO: To avoid storing a stateful snapshot of some other system's state (which is easily to accidentally get out of sync),
|
||||
// TODO: it might be better to have a system that can query the state of the node network on demand.
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct DocumentMetadata {
|
||||
pub upstream_footprints: HashMap<NodeId, Footprint>,
|
||||
pub local_transforms: HashMap<NodeId, DAffine2>,
|
||||
pub first_instance_source_ids: HashMap<NodeId, Option<NodeId>>,
|
||||
pub structure: HashMap<LayerNodeIdentifier, NodeRelations>,
|
||||
pub click_targets: HashMap<LayerNodeIdentifier, Vec<ClickTarget>>,
|
||||
pub clip_targets: HashSet<NodeId>,
|
||||
@@ -28,20 +31,6 @@ pub struct DocumentMetadata {
|
||||
pub document_to_viewport: DAffine2,
|
||||
}
|
||||
|
||||
impl Default for DocumentMetadata {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
upstream_footprints: HashMap::new(),
|
||||
local_transforms: HashMap::new(),
|
||||
structure: HashMap::new(),
|
||||
vector_modify: HashMap::new(),
|
||||
click_targets: HashMap::new(),
|
||||
clip_targets: HashSet::new(),
|
||||
document_to_viewport: DAffine2::IDENTITY,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// =================================
|
||||
// DocumentMetadata: Layer iterators
|
||||
// =================================
|
||||
@@ -91,6 +80,36 @@ impl DocumentMetadata {
|
||||
footprint * local_transform
|
||||
}
|
||||
|
||||
pub fn transform_to_viewport_if_feeds(&self, layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> DAffine2 {
|
||||
// We're not allowed to convert the root parent to a node id
|
||||
if layer == LayerNodeIdentifier::ROOT_PARENT {
|
||||
return self.document_to_viewport;
|
||||
}
|
||||
|
||||
let footprint = self.upstream_footprints.get(&layer.to_node()).map(|footprint| footprint.transform).unwrap_or(self.document_to_viewport);
|
||||
|
||||
let mut use_local = true;
|
||||
let graph_layer = graph_modification_utils::NodeGraphLayer::new(layer, network_interface);
|
||||
if let Some(path_node) = graph_layer.upstream_node_id_from_name("Path") {
|
||||
if let Some(&source) = self.first_instance_source_ids.get(&layer.to_node()) {
|
||||
if !network_interface
|
||||
.upstream_flow_back_from_nodes(vec![path_node], &[], FlowType::HorizontalFlow)
|
||||
.any(|upstream| Some(upstream) == source)
|
||||
{
|
||||
use_local = false;
|
||||
info!("Local transform is invalid — using the identity for the local transform instead")
|
||||
}
|
||||
}
|
||||
}
|
||||
let local_transform = use_local.then(|| self.local_transforms.get(&layer.to_node()).copied()).flatten().unwrap_or_default();
|
||||
|
||||
footprint * local_transform
|
||||
}
|
||||
|
||||
pub fn transform_to_document_if_feeds(&self, layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> DAffine2 {
|
||||
self.document_to_viewport.inverse() * self.transform_to_viewport_if_feeds(layer, network_interface)
|
||||
}
|
||||
|
||||
pub fn transform_to_viewport_with_first_transform_node_if_group(&self, layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> DAffine2 {
|
||||
let footprint = self.upstream_footprints.get(&layer.to_node()).map(|footprint| footprint.transform).unwrap_or(self.document_to_viewport);
|
||||
let local_transform = self.local_transforms.get(&layer.to_node()).copied();
|
||||
|
||||
@@ -5,3 +5,4 @@ pub mod misc;
|
||||
pub mod network_interface;
|
||||
pub mod nodes;
|
||||
pub mod transformation;
|
||||
pub mod wires;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,7 @@
|
||||
use super::document_metadata::{DocumentMetadata, LayerNodeIdentifier};
|
||||
use super::network_interface::NodeNetworkInterface;
|
||||
use crate::messages::tool::common_functionality::graph_modification_utils;
|
||||
use glam::DVec2;
|
||||
use graph_craft::document::{NodeId, NodeNetwork};
|
||||
use serde::ser::SerializeStruct;
|
||||
|
||||
@@ -98,6 +100,22 @@ impl SelectedNodes {
|
||||
.filter(move |&layer| self.layer_visible(layer, network_interface) && !self.layer_locked(layer, network_interface))
|
||||
}
|
||||
|
||||
pub fn selected_visible_and_unlocked_layers_mean_average_origin<'a>(&'a self, network_interface: &'a NodeNetworkInterface) -> DVec2 {
|
||||
let (sum, count) = self
|
||||
.selected_visible_and_unlocked_layers(network_interface)
|
||||
.map(|layer| graph_modification_utils::get_viewport_origin(layer, network_interface))
|
||||
.fold((glam::DVec2::ZERO, 0), |(sum, count), item| (sum + item, count + 1));
|
||||
if count == 0 { DVec2::ZERO } else { sum / count as f64 }
|
||||
}
|
||||
|
||||
pub fn selected_visible_and_unlocked_median_points<'a>(&'a self, network_interface: &'a NodeNetworkInterface) -> DVec2 {
|
||||
let (sum, count) = self
|
||||
.selected_visible_and_unlocked_layers(network_interface)
|
||||
.map(|layer| graph_modification_utils::get_viewport_center(layer, network_interface))
|
||||
.fold((glam::DVec2::ZERO, 0), |(sum, count), item| (sum + item, count + 1));
|
||||
if count == 0 { DVec2::ZERO } else { sum / count as f64 }
|
||||
}
|
||||
|
||||
pub fn selected_layers<'a>(&'a self, metadata: &'a DocumentMetadata) -> impl Iterator<Item = LayerNodeIdentifier> + 'a {
|
||||
metadata.all_layers().filter(|layer| self.0.contains(&layer.to_node()))
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ use crate::messages::portfolio::document::graph_operation::transform_utils;
|
||||
use crate::messages::portfolio::document::graph_operation::utility_types::{ModifyInputsContext, TransformIn};
|
||||
use crate::messages::portfolio::document::utility_types::document_metadata::{DocumentMetadata, LayerNodeIdentifier};
|
||||
use crate::messages::prelude::*;
|
||||
use crate::messages::tool::common_functionality::graph_modification_utils;
|
||||
use crate::messages::tool::common_functionality::shape_editor::ShapeState;
|
||||
use crate::messages::tool::utility_types::ToolType;
|
||||
use glam::{DAffine2, DMat2, DVec2};
|
||||
@@ -537,17 +536,6 @@ impl<'a> Selected<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn mean_average_of_pivots(&mut self) -> DVec2 {
|
||||
let xy_summation = self
|
||||
.selected
|
||||
.iter()
|
||||
.map(|&layer| graph_modification_utils::get_viewport_pivot(layer, self.network_interface))
|
||||
.reduce(|a, b| a + b)
|
||||
.unwrap_or_default();
|
||||
|
||||
xy_summation / self.selected.len() as f64
|
||||
}
|
||||
|
||||
pub fn center_of_aabb(&mut self) -> DVec2 {
|
||||
let [min, max] = self
|
||||
.selected
|
||||
|
||||
@@ -0,0 +1,589 @@
|
||||
use crate::messages::portfolio::document::node_graph::utility_types::FrontendGraphDataType;
|
||||
use bezier_rs::{ManipulatorGroup, Subpath};
|
||||
use glam::{DVec2, IVec2};
|
||||
use graphene_std::uuid::NodeId;
|
||||
use graphene_std::vector::PointId;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
|
||||
pub struct WirePath {
|
||||
#[serde(rename = "pathString")]
|
||||
pub path_string: String,
|
||||
#[serde(rename = "dataType")]
|
||||
pub data_type: FrontendGraphDataType,
|
||||
pub thick: bool,
|
||||
pub dashed: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
|
||||
pub struct WirePathUpdate {
|
||||
pub id: NodeId,
|
||||
#[serde(rename = "inputIndex")]
|
||||
pub input_index: usize,
|
||||
// If none, then remove the wire from the map
|
||||
#[serde(rename = "wirePathUpdate")]
|
||||
pub wire_path_update: Option<WirePath>,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Default, serde::Serialize, serde::Deserialize, specta::Type)]
|
||||
pub enum GraphWireStyle {
|
||||
#[default]
|
||||
Direct = 0,
|
||||
GridAligned = 1,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for GraphWireStyle {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
GraphWireStyle::GridAligned => write!(f, "Grid-Aligned"),
|
||||
GraphWireStyle::Direct => write!(f, "Direct"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl GraphWireStyle {
|
||||
pub fn tooltip_description(&self) -> &'static str {
|
||||
match self {
|
||||
GraphWireStyle::GridAligned => "Wires follow the grid, running in straight lines between nodes",
|
||||
GraphWireStyle::Direct => "Wires bend to run at an angle directly between nodes",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_direct(&self) -> bool {
|
||||
*self == GraphWireStyle::Direct
|
||||
}
|
||||
}
|
||||
|
||||
pub fn build_vector_wire(output_position: DVec2, input_position: DVec2, vertical_out: bool, vertical_in: bool, graph_wire_style: GraphWireStyle) -> Subpath<PointId> {
|
||||
let grid_spacing = 24.;
|
||||
match graph_wire_style {
|
||||
GraphWireStyle::Direct => {
|
||||
let horizontal_gap = (output_position.x - input_position.x).abs();
|
||||
let vertical_gap = (output_position.y - input_position.y).abs();
|
||||
|
||||
let curve_length = grid_spacing;
|
||||
let curve_falloff_rate = curve_length * std::f64::consts::TAU;
|
||||
|
||||
let horizontal_curve_amount = -(2_f64.powf((-10. * horizontal_gap) / curve_falloff_rate)) + 1.;
|
||||
let vertical_curve_amount = -(2_f64.powf((-10. * vertical_gap) / curve_falloff_rate)) + 1.;
|
||||
let horizontal_curve = horizontal_curve_amount * curve_length;
|
||||
let vertical_curve = vertical_curve_amount * curve_length;
|
||||
|
||||
let locations = [
|
||||
output_position,
|
||||
DVec2::new(
|
||||
if vertical_out { output_position.x } else { output_position.x + horizontal_curve },
|
||||
if vertical_out { output_position.y - vertical_curve } else { output_position.y },
|
||||
),
|
||||
DVec2::new(
|
||||
if vertical_in { input_position.x } else { input_position.x - horizontal_curve },
|
||||
if vertical_in { input_position.y + vertical_curve } else { input_position.y },
|
||||
),
|
||||
DVec2::new(input_position.x, input_position.y),
|
||||
];
|
||||
|
||||
let smoothing = 0.5;
|
||||
let delta01 = DVec2::new((locations[1].x - locations[0].x) * smoothing, (locations[1].y - locations[0].y) * smoothing);
|
||||
let delta23 = DVec2::new((locations[3].x - locations[2].x) * smoothing, (locations[3].y - locations[2].y) * smoothing);
|
||||
|
||||
Subpath::new(
|
||||
vec![
|
||||
ManipulatorGroup {
|
||||
anchor: locations[0],
|
||||
in_handle: None,
|
||||
out_handle: None,
|
||||
id: PointId::generate(),
|
||||
},
|
||||
ManipulatorGroup {
|
||||
anchor: locations[1],
|
||||
in_handle: None,
|
||||
out_handle: Some(locations[1] + delta01),
|
||||
id: PointId::generate(),
|
||||
},
|
||||
ManipulatorGroup {
|
||||
anchor: locations[2],
|
||||
in_handle: Some(locations[2] - delta23),
|
||||
out_handle: None,
|
||||
id: PointId::generate(),
|
||||
},
|
||||
ManipulatorGroup {
|
||||
anchor: locations[3],
|
||||
in_handle: None,
|
||||
out_handle: None,
|
||||
id: PointId::generate(),
|
||||
},
|
||||
],
|
||||
false,
|
||||
)
|
||||
}
|
||||
GraphWireStyle::GridAligned => {
|
||||
let locations = straight_wire_paths(output_position, input_position, vertical_out, vertical_in);
|
||||
straight_wire_subpath(locations)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn straight_wire_paths(output_position: DVec2, input_position: DVec2, vertical_out: bool, vertical_in: bool) -> Vec<IVec2> {
|
||||
let grid_spacing = 24;
|
||||
let line_width = 2;
|
||||
|
||||
let in_x = input_position.x as i32;
|
||||
let in_y = input_position.y as i32;
|
||||
let out_x = output_position.x as i32;
|
||||
let out_y = output_position.y as i32;
|
||||
|
||||
let mid_x = (in_x + out_x) / 2 + (((in_x + out_x) / 2) % grid_spacing);
|
||||
let mid_y = (in_y + out_y) / 2 + (((in_y + out_y) / 2) % grid_spacing);
|
||||
let mid_y_alternate = (in_y + in_y) / 2 - (((in_y + in_y) / 2) % grid_spacing);
|
||||
|
||||
let x1 = out_x;
|
||||
let x2 = out_x + grid_spacing;
|
||||
let x3 = in_x - 2 * grid_spacing;
|
||||
let x4 = in_x;
|
||||
let x5 = in_x - 2 * grid_spacing + line_width;
|
||||
let x6 = out_x + grid_spacing + line_width;
|
||||
let x7 = out_x + 2 * grid_spacing + line_width;
|
||||
let x8 = in_x + line_width;
|
||||
let x9 = out_x + 2 * grid_spacing;
|
||||
let x10 = mid_x + line_width;
|
||||
let x11 = out_x - grid_spacing;
|
||||
let x12 = out_x - 4 * grid_spacing;
|
||||
let x13 = mid_x;
|
||||
let x14 = in_x + grid_spacing;
|
||||
let x15 = in_x - 4 * grid_spacing;
|
||||
let x16 = in_x + 8 * grid_spacing;
|
||||
let x17 = mid_x - 2 * line_width;
|
||||
let x18 = out_x + grid_spacing - 2 * line_width;
|
||||
let x19 = out_x - 2 * line_width;
|
||||
let x20 = mid_x - line_width;
|
||||
|
||||
let y1 = out_y;
|
||||
let y2 = out_y - grid_spacing;
|
||||
let y3 = in_y;
|
||||
let y4 = out_y - grid_spacing + 5 * line_width + 1;
|
||||
let y5 = in_y - 2 * grid_spacing;
|
||||
let y6 = out_y + 4 * line_width;
|
||||
let y7 = out_y + 5 * line_width;
|
||||
let y8 = out_y - 2 * grid_spacing + 5 * line_width + 1;
|
||||
let y9 = out_y + 6 * line_width;
|
||||
let y10 = in_y + 2 * grid_spacing;
|
||||
let y111 = in_y + grid_spacing + 6 * line_width + 1;
|
||||
let y12 = in_y + grid_spacing - 5 * line_width + 1;
|
||||
let y13 = in_y - grid_spacing;
|
||||
let y14 = in_y + grid_spacing;
|
||||
let y15 = mid_y;
|
||||
let y16 = mid_y_alternate;
|
||||
|
||||
let wire1 = vec![IVec2::new(x1, y1), IVec2::new(x1, y4), IVec2::new(x5, y4), IVec2::new(x5, y3), IVec2::new(x4, y3)];
|
||||
|
||||
let wire2 = vec![IVec2::new(x1, y1), IVec2::new(x1, y16), IVec2::new(x3, y16), IVec2::new(x3, y3), IVec2::new(x4, y3)];
|
||||
|
||||
let wire3 = vec![
|
||||
IVec2::new(x1, y1),
|
||||
IVec2::new(x1, y4),
|
||||
IVec2::new(x12, y4),
|
||||
IVec2::new(x12, y10),
|
||||
IVec2::new(x3, y10),
|
||||
IVec2::new(x3, y3),
|
||||
IVec2::new(x4, y3),
|
||||
];
|
||||
|
||||
let wire4 = vec![
|
||||
IVec2::new(x1, y1),
|
||||
IVec2::new(x1, y4),
|
||||
IVec2::new(x13, y4),
|
||||
IVec2::new(x13, y10),
|
||||
IVec2::new(x3, y10),
|
||||
IVec2::new(x3, y3),
|
||||
IVec2::new(x4, y3),
|
||||
];
|
||||
|
||||
if out_y == in_y && out_x > in_x && (vertical_out || !vertical_in) {
|
||||
return vec![IVec2::new(x1, y1), IVec2::new(x2, y1), IVec2::new(x2, y2), IVec2::new(x3, y2), IVec2::new(x3, y3), IVec2::new(x4, y3)];
|
||||
}
|
||||
|
||||
// `outConnector` point and `inConnector` point lying on the same horizontal grid line and `outConnector` point lies to the right of `inConnector` point
|
||||
if out_y == in_y && out_x > in_x && (vertical_out || !vertical_in) {
|
||||
return vec![IVec2::new(x1, y1), IVec2::new(x2, y1), IVec2::new(x2, y2), IVec2::new(x3, y2), IVec2::new(x3, y3), IVec2::new(x4, y3)];
|
||||
};
|
||||
|
||||
// Handle straight lines
|
||||
if out_y == in_y || (out_x == in_x && vertical_out) {
|
||||
return vec![IVec2::new(x1, y1), IVec2::new(x4, y3)];
|
||||
};
|
||||
|
||||
// Handle standard right-angle paths
|
||||
// Start vertical, then horizontal
|
||||
|
||||
// `outConnector` point lies to the left of `inConnector` point
|
||||
if vertical_out && in_x > out_x {
|
||||
// `outConnector` point lies above `inConnector` point
|
||||
if out_y < in_y {
|
||||
// `outConnector` point lies on the vertical grid line 4 units to the left of `inConnector` point point
|
||||
if -4 * grid_spacing <= out_x - in_x && out_x - in_x < -3 * grid_spacing {
|
||||
return wire1;
|
||||
};
|
||||
|
||||
// `outConnector` point lying on vertical grid lines 3 and 2 units to the left of `inConnector` point
|
||||
if -3 * grid_spacing <= out_x - in_x && out_x - in_x <= -grid_spacing {
|
||||
if -2 * grid_spacing <= out_y - in_y && out_y - in_y <= -grid_spacing {
|
||||
return vec![IVec2::new(x1, y1), IVec2::new(x1, y2), IVec2::new(x2, y2), IVec2::new(x2, y3), IVec2::new(x4, y3)];
|
||||
};
|
||||
|
||||
if -grid_spacing <= out_y - in_y && out_y - in_y <= 0 {
|
||||
return vec![IVec2::new(x1, y1), IVec2::new(x1, y4), IVec2::new(x6, y4), IVec2::new(x6, y3), IVec2::new(x4, y3)];
|
||||
};
|
||||
|
||||
return vec![
|
||||
IVec2::new(x1, y1),
|
||||
IVec2::new(x1, y4),
|
||||
IVec2::new(x7, y4),
|
||||
IVec2::new(x7, y5),
|
||||
IVec2::new(x3, y5),
|
||||
IVec2::new(x3, y3),
|
||||
IVec2::new(x4, y3),
|
||||
];
|
||||
}
|
||||
|
||||
// `outConnector` point lying on vertical grid line 1 units to the left of `inConnector` point
|
||||
if -grid_spacing < out_x - in_x && out_x - in_x <= 0 {
|
||||
// `outConnector` point lying on horizontal grid line 1 unit above `inConnector` point
|
||||
if -2 * grid_spacing <= out_y - in_y && out_y - in_y <= -grid_spacing {
|
||||
return vec![IVec2::new(x1, y6), IVec2::new(x2, y6), IVec2::new(x8, y3)];
|
||||
};
|
||||
|
||||
// `outConnector` point lying on the same horizontal grid line as `inConnector` point
|
||||
if -grid_spacing <= out_y - in_y && out_y - in_y <= 0 {
|
||||
return vec![IVec2::new(x1, y7), IVec2::new(x4, y3)];
|
||||
};
|
||||
|
||||
return vec![
|
||||
IVec2::new(x1, y1),
|
||||
IVec2::new(x1, y2),
|
||||
IVec2::new(x9, y2),
|
||||
IVec2::new(x9, y5),
|
||||
IVec2::new(x3, y5),
|
||||
IVec2::new(x3, y3),
|
||||
IVec2::new(x4, y3),
|
||||
];
|
||||
}
|
||||
|
||||
return vec![IVec2::new(x1, y1), IVec2::new(x1, y4), IVec2::new(x10, y4), IVec2::new(x10, y3), IVec2::new(x4, y3)];
|
||||
}
|
||||
|
||||
// `outConnector` point lies below `inConnector` point
|
||||
// `outConnector` point lying on vertical grid line 1 unit to the left of `inConnector` point
|
||||
if -grid_spacing <= out_x - in_x && out_x - in_x <= 0 {
|
||||
// `outConnector` point lying on the horizontal grid lines 1 and 2 units below the `inConnector` point
|
||||
if 0 <= out_y - in_y && out_y - in_y <= 2 * grid_spacing {
|
||||
return vec![IVec2::new(x1, y6), IVec2::new(x11, y6), IVec2::new(x11, y3), IVec2::new(x4, y3)];
|
||||
};
|
||||
|
||||
return wire2;
|
||||
}
|
||||
|
||||
return vec![IVec2::new(x1, y1), IVec2::new(x1, y3), IVec2::new(x4, y3)];
|
||||
}
|
||||
|
||||
// `outConnector` point lies to the right of `inConnector` point
|
||||
if vertical_out && in_x <= out_x {
|
||||
// `outConnector` point lying on any horizontal grid line above `inConnector` point
|
||||
if out_y < in_y {
|
||||
// `outConnector` point lying on horizontal grid line 1 unit above `inConnector` point
|
||||
if -2 * grid_spacing < out_y - in_y && out_y - in_y <= -grid_spacing {
|
||||
return wire1;
|
||||
};
|
||||
|
||||
// `outConnector` point lying on the same horizontal grid line as `inConnector` point
|
||||
if -grid_spacing < out_y - in_y && out_y - in_y <= 0 {
|
||||
return vec![IVec2::new(x1, y1), IVec2::new(x1, y8), IVec2::new(x5, y8), IVec2::new(x5, y3), IVec2::new(x4, y3)];
|
||||
};
|
||||
|
||||
// `outConnector` point lying on vertical grid lines 1 and 2 units to the right of `inConnector` point
|
||||
if grid_spacing <= out_x - in_x && out_x - in_x <= 3 * grid_spacing {
|
||||
return vec![
|
||||
IVec2::new(x1, y1),
|
||||
IVec2::new(x1, y4),
|
||||
IVec2::new(x9, y4),
|
||||
IVec2::new(x9, y5),
|
||||
IVec2::new(x3, y5),
|
||||
IVec2::new(x3, y3),
|
||||
IVec2::new(x4, y3),
|
||||
];
|
||||
}
|
||||
|
||||
return vec![
|
||||
IVec2::new(x1, y1),
|
||||
IVec2::new(x1, y4),
|
||||
IVec2::new(x10, y4),
|
||||
IVec2::new(x10, y5),
|
||||
IVec2::new(x5, y5),
|
||||
IVec2::new(x5, y3),
|
||||
IVec2::new(x4, y3),
|
||||
];
|
||||
}
|
||||
|
||||
// `outConnector` point lies below `inConnector` point
|
||||
if out_y - in_y <= grid_spacing {
|
||||
// `outConnector` point lies on the horizontal grid line 1 unit below the `inConnector` Point
|
||||
if 0 <= out_x - in_x && out_x - in_x <= 13 * grid_spacing {
|
||||
return vec![IVec2::new(x1, y9), IVec2::new(x3, y9), IVec2::new(x3, y3), IVec2::new(x4, y3)];
|
||||
};
|
||||
|
||||
if 13 < out_x - in_x && out_x - in_x <= 18 * grid_spacing {
|
||||
return wire3;
|
||||
};
|
||||
|
||||
return wire4;
|
||||
}
|
||||
|
||||
// `outConnector` point lies on the horizontal grid line 2 units below `outConnector` point
|
||||
if grid_spacing <= out_y - in_y && out_y - in_y <= 2 * grid_spacing {
|
||||
if 0 <= out_x - in_x && out_x - in_x <= 13 * grid_spacing {
|
||||
return vec![IVec2::new(x1, y7), IVec2::new(x5, y7), IVec2::new(x5, y3), IVec2::new(x4, y3)];
|
||||
};
|
||||
|
||||
if 13 < out_x - in_x && out_x - in_x <= 18 * grid_spacing {
|
||||
return wire3;
|
||||
};
|
||||
|
||||
return wire4;
|
||||
}
|
||||
|
||||
// 0 to 4 units below the `outConnector` Point
|
||||
if out_y - in_y <= 4 * grid_spacing {
|
||||
return wire1;
|
||||
};
|
||||
|
||||
return wire2;
|
||||
}
|
||||
|
||||
// Start horizontal, then vertical
|
||||
if vertical_in {
|
||||
// when `outConnector` lies below `inConnector`
|
||||
if out_y > in_y {
|
||||
// `out_x` lies to the left of `in_x`
|
||||
if out_x < in_x {
|
||||
return vec![IVec2::new(x1, y1), IVec2::new(x4, y1), IVec2::new(x4, y3)];
|
||||
};
|
||||
|
||||
// `out_x` lies to the right of `in_x`
|
||||
if out_y - in_y <= grid_spacing {
|
||||
// `outConnector` point directly below `inConnector` point
|
||||
if 0 <= out_x - in_x && out_x - in_x <= grid_spacing {
|
||||
return vec![IVec2::new(x1, y1), IVec2::new(x14, y1), IVec2::new(x14, y2), IVec2::new(x4, y2), IVec2::new(x4, y3)];
|
||||
};
|
||||
|
||||
// `outConnector` point lies below `inConnector` point and strictly to the right of `inConnector` point
|
||||
return vec![IVec2::new(x1, y1), IVec2::new(x2, y1), IVec2::new(x2, y111), IVec2::new(x4, y111), IVec2::new(x4, y3)];
|
||||
}
|
||||
|
||||
return vec![IVec2::new(x1, y1), IVec2::new(x2, y1), IVec2::new(x2, y2), IVec2::new(x4, y2), IVec2::new(x4, y3)];
|
||||
}
|
||||
|
||||
// `out_y` lies on or above the `in_y` point
|
||||
if -6 * grid_spacing < in_x - out_x && in_x - out_x < 4 * grid_spacing {
|
||||
// edge case: `outConnector` point lying on vertical grid lines ranging from 4 units to left to 5 units to right of `inConnector` point
|
||||
if -grid_spacing < in_x - out_x && in_x - out_x < 4 * grid_spacing {
|
||||
return vec![
|
||||
IVec2::new(x1, y1),
|
||||
IVec2::new(x2, y1),
|
||||
IVec2::new(x2, y2),
|
||||
IVec2::new(x15, y2),
|
||||
IVec2::new(x15, y12),
|
||||
IVec2::new(x4, y12),
|
||||
IVec2::new(x4, y3),
|
||||
];
|
||||
}
|
||||
|
||||
return vec![IVec2::new(x1, y1), IVec2::new(x16, y1), IVec2::new(x16, y12), IVec2::new(x4, y12), IVec2::new(x4, y3)];
|
||||
}
|
||||
|
||||
// left of edge case: `outConnector` point lying on vertical grid lines more than 4 units to left of `inConnector` point
|
||||
if 4 * grid_spacing < in_x - out_x {
|
||||
return vec![IVec2::new(x1, y1), IVec2::new(x17, y1), IVec2::new(x17, y12), IVec2::new(x4, y12), IVec2::new(x4, y3)];
|
||||
};
|
||||
|
||||
// right of edge case: `outConnector` point lying on the vertical grid lines more than 5 units to right of `inConnector` point
|
||||
if 6 * grid_spacing > in_x - out_x {
|
||||
return vec![IVec2::new(x1, y1), IVec2::new(x18, y1), IVec2::new(x18, y12), IVec2::new(x4, y12), IVec2::new(x4, y3)];
|
||||
};
|
||||
}
|
||||
|
||||
// Both horizontal - use horizontal middle point
|
||||
// When `inConnector` point is one of the two closest diagonally opposite points
|
||||
if 0 <= in_x - out_x && in_x - out_x <= grid_spacing && in_y - out_y >= -grid_spacing && in_y - out_y <= grid_spacing {
|
||||
return vec![IVec2::new(x19, y1), IVec2::new(x19, y3), IVec2::new(x4, y3)];
|
||||
}
|
||||
|
||||
// When `inConnector` point lies on the horizontal line 1 unit above and below the `outConnector` point
|
||||
if -grid_spacing <= out_y - in_y && out_y - in_y <= grid_spacing && out_x > in_x {
|
||||
// Horizontal line above `out_y`
|
||||
if in_y < out_y {
|
||||
return vec![IVec2::new(x1, y1), IVec2::new(x2, y1), IVec2::new(x2, y13), IVec2::new(x3, y13), IVec2::new(x3, y3), IVec2::new(x4, y3)];
|
||||
};
|
||||
|
||||
// Horizontal line below `out_y`
|
||||
return vec![IVec2::new(x1, y1), IVec2::new(x2, y1), IVec2::new(x2, y14), IVec2::new(x3, y14), IVec2::new(x3, y3), IVec2::new(x4, y3)];
|
||||
}
|
||||
|
||||
// `outConnector` point to the right of `inConnector` point
|
||||
if out_x > in_x - grid_spacing {
|
||||
return vec![
|
||||
IVec2::new(x1, y1),
|
||||
IVec2::new(x18, y1),
|
||||
IVec2::new(x18, y15),
|
||||
IVec2::new(x5, y15),
|
||||
IVec2::new(x5, y3),
|
||||
IVec2::new(x4, y3),
|
||||
];
|
||||
};
|
||||
|
||||
// When `inConnector` point lies on the vertical grid line two units to the right of `outConnector` point
|
||||
if grid_spacing <= in_x - out_x && in_x - out_x <= 2 * grid_spacing {
|
||||
return vec![IVec2::new(x1, y1), IVec2::new(x18, y1), IVec2::new(x18, y3), IVec2::new(x4, y3)];
|
||||
};
|
||||
|
||||
vec![IVec2::new(x1, y1), IVec2::new(x20, y1), IVec2::new(x20, y3), IVec2::new(x4, y3)]
|
||||
}
|
||||
|
||||
fn straight_wire_subpath(locations: Vec<IVec2>) -> Subpath<PointId> {
|
||||
if locations.is_empty() {
|
||||
return Subpath::new(Vec::new(), false);
|
||||
}
|
||||
|
||||
if locations.len() == 2 {
|
||||
return Subpath::new(
|
||||
vec![
|
||||
ManipulatorGroup {
|
||||
anchor: locations[0].into(),
|
||||
in_handle: None,
|
||||
out_handle: None,
|
||||
id: PointId::generate(),
|
||||
},
|
||||
ManipulatorGroup {
|
||||
anchor: locations[1].into(),
|
||||
in_handle: None,
|
||||
out_handle: None,
|
||||
id: PointId::generate(),
|
||||
},
|
||||
],
|
||||
false,
|
||||
);
|
||||
}
|
||||
|
||||
let corner_radius = 10;
|
||||
|
||||
// Create path with rounded corners
|
||||
let mut path = vec![ManipulatorGroup {
|
||||
anchor: locations[0].into(),
|
||||
in_handle: None,
|
||||
out_handle: None,
|
||||
id: PointId::generate(),
|
||||
}];
|
||||
|
||||
for i in 1..(locations.len() - 1) {
|
||||
let prev = locations[i - 1];
|
||||
let curr = locations[i];
|
||||
let next = locations[i + 1];
|
||||
|
||||
let corner_start = IVec2::new(
|
||||
curr.x
|
||||
+ if curr.x == prev.x {
|
||||
0
|
||||
} else if prev.x > curr.x {
|
||||
corner_radius
|
||||
} else {
|
||||
-corner_radius
|
||||
},
|
||||
curr.y
|
||||
+ if curr.y == prev.y {
|
||||
0
|
||||
} else if prev.y > curr.y {
|
||||
corner_radius
|
||||
} else {
|
||||
-corner_radius
|
||||
},
|
||||
);
|
||||
|
||||
let corner_start_mid = IVec2::new(
|
||||
curr.x
|
||||
+ if curr.x == prev.x {
|
||||
0
|
||||
} else if prev.x > curr.x {
|
||||
corner_radius / 2
|
||||
} else {
|
||||
-corner_radius / 2
|
||||
},
|
||||
curr.y
|
||||
+ if curr.y == prev.y {
|
||||
0
|
||||
} else {
|
||||
match prev.y > curr.y {
|
||||
true => corner_radius / 2,
|
||||
false => -corner_radius / 2,
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
let corner_end = IVec2::new(
|
||||
curr.x
|
||||
+ if curr.x == next.x {
|
||||
0
|
||||
} else if next.x > curr.x {
|
||||
corner_radius
|
||||
} else {
|
||||
-corner_radius
|
||||
},
|
||||
curr.y
|
||||
+ if curr.y == next.y {
|
||||
0
|
||||
} else if next.y > curr.y {
|
||||
corner_radius
|
||||
} else {
|
||||
-corner_radius
|
||||
},
|
||||
);
|
||||
|
||||
let corner_end_mid = IVec2::new(
|
||||
curr.x
|
||||
+ if curr.x == next.x {
|
||||
0
|
||||
} else if next.x > curr.x {
|
||||
corner_radius / 2
|
||||
} else {
|
||||
-corner_radius / 2
|
||||
},
|
||||
curr.y
|
||||
+ if curr.y == next.y {
|
||||
0
|
||||
} else if next.y > curr.y {
|
||||
10 / 2
|
||||
} else {
|
||||
-corner_radius / 2
|
||||
},
|
||||
);
|
||||
|
||||
path.extend(vec![
|
||||
ManipulatorGroup {
|
||||
anchor: corner_start.into(),
|
||||
in_handle: None,
|
||||
out_handle: Some(corner_start_mid.into()),
|
||||
id: PointId::generate(),
|
||||
},
|
||||
ManipulatorGroup {
|
||||
anchor: corner_end.into(),
|
||||
in_handle: Some(corner_end_mid.into()),
|
||||
out_handle: None,
|
||||
id: PointId::generate(),
|
||||
},
|
||||
])
|
||||
}
|
||||
|
||||
path.push(ManipulatorGroup {
|
||||
anchor: (*locations.last().unwrap()).into(),
|
||||
in_handle: None,
|
||||
out_handle: None,
|
||||
id: PointId::generate(),
|
||||
});
|
||||
Subpath::new(path, false)
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -6,7 +6,7 @@ use crate::messages::portfolio::document::utility_types::misc::{AlignAggregate,
|
||||
use crate::messages::prelude::*;
|
||||
use graphene_std::path_bool::BooleanOperation;
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
#[derive(Debug, Clone, Default, ExtractField)]
|
||||
pub struct MenuBarMessageHandler {
|
||||
pub has_active_document: bool,
|
||||
pub canvas_tilted: bool,
|
||||
@@ -21,6 +21,7 @@ pub struct MenuBarMessageHandler {
|
||||
pub reset_node_definitions_on_open: bool,
|
||||
}
|
||||
|
||||
#[message_handler_data]
|
||||
impl MessageHandler<MenuBarMessage, ()> for MenuBarMessageHandler {
|
||||
fn process_message(&mut self, message: MenuBarMessage, responses: &mut VecDeque<Message>, _data: ()) {
|
||||
match message {
|
||||
|
||||
@@ -12,6 +12,7 @@ use crate::messages::layout::utility_types::widget_prelude::*;
|
||||
use crate::messages::portfolio::document::DocumentMessageData;
|
||||
use crate::messages::portfolio::document::graph_operation::utility_types::TransformIn;
|
||||
use crate::messages::portfolio::document::utility_types::clipboards::{Clipboard, CopyBufferEntry, INTERNAL_CLIPBOARD_COUNT};
|
||||
use crate::messages::portfolio::document::utility_types::network_interface::OutputConnector;
|
||||
use crate::messages::portfolio::document::utility_types::nodes::SelectedNodes;
|
||||
use crate::messages::portfolio::document_migration::*;
|
||||
use crate::messages::preferences::SelectionMode;
|
||||
@@ -24,6 +25,7 @@ use graphene_std::renderer::Quad;
|
||||
use graphene_std::text::Font;
|
||||
use std::vec;
|
||||
|
||||
#[derive(ExtractField)]
|
||||
pub struct PortfolioMessageData<'a> {
|
||||
pub ipp: &'a InputPreprocessorMessageHandler,
|
||||
pub preferences: &'a PreferencesMessageHandler,
|
||||
@@ -34,7 +36,7 @@ pub struct PortfolioMessageData<'a> {
|
||||
pub animation: &'a AnimationMessageHandler,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
#[derive(Debug, Default, ExtractField)]
|
||||
pub struct PortfolioMessageHandler {
|
||||
menu_bar_message_handler: MenuBarMessageHandler,
|
||||
pub documents: HashMap<DocumentId, DocumentMessageHandler>,
|
||||
@@ -51,6 +53,7 @@ pub struct PortfolioMessageHandler {
|
||||
pub reset_node_definitions_on_open: bool,
|
||||
}
|
||||
|
||||
#[message_handler_data]
|
||||
impl MessageHandler<PortfolioMessage, PortfolioMessageData<'_>> for PortfolioMessageHandler {
|
||||
fn process_message(&mut self, message: PortfolioMessage, responses: &mut VecDeque<Message>, data: PortfolioMessageData) {
|
||||
let PortfolioMessageData {
|
||||
@@ -426,6 +429,43 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageData<'_>> for PortfolioMes
|
||||
// Upgrade the document's nodes to be compatible with the latest version
|
||||
document_migration_upgrades(&mut document, reset_node_definitions_on_open);
|
||||
|
||||
// Ensure each node has the metadata for its inputs
|
||||
for (node_id, node, path) in document.network_interface.document_network().clone().recursive_nodes() {
|
||||
document.network_interface.validate_input_metadata(node_id, node, &path);
|
||||
document.network_interface.validate_display_name_metadata(node_id, &path);
|
||||
document.network_interface.validate_output_names(node_id, node, &path);
|
||||
}
|
||||
|
||||
// Ensure layers are positioned as stacks if they are upstream siblings of another layer
|
||||
document.network_interface.load_structure();
|
||||
let all_layers = LayerNodeIdentifier::ROOT_PARENT.descendants(document.network_interface.document_metadata()).collect::<Vec<_>>();
|
||||
for layer in all_layers {
|
||||
let Some((downstream_node, input_index)) = document
|
||||
.network_interface
|
||||
.outward_wires(&[])
|
||||
.and_then(|outward_wires| outward_wires.get(&OutputConnector::node(layer.to_node(), 0)))
|
||||
.and_then(|outward_wires| outward_wires.first())
|
||||
.and_then(|input_connector| input_connector.node_id().map(|node_id| (node_id, input_connector.input_index())))
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
|
||||
// If the downstream node is a layer and the input is the first input and the current layer is not in a stack
|
||||
if input_index == 0 && document.network_interface.is_layer(&downstream_node, &[]) && !document.network_interface.is_stack(&layer.to_node(), &[]) {
|
||||
// Ensure the layer is horizontally aligned with the downstream layer to prevent changing the layout of old files
|
||||
let (Some(layer_position), Some(downstream_position)) =
|
||||
(document.network_interface.position(&layer.to_node(), &[]), document.network_interface.position(&downstream_node, &[]))
|
||||
else {
|
||||
log::error!("Could not get position for layer {:?} or downstream node {} when opening file", layer.to_node(), downstream_node);
|
||||
continue;
|
||||
};
|
||||
|
||||
if layer_position.x == downstream_position.x {
|
||||
document.network_interface.set_stack_position_calculated_offset(&layer.to_node(), &downstream_node, &[]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Set the save state of the document based on what's given to us by the caller to this message
|
||||
document.set_auto_save_state(document_is_auto_saved);
|
||||
document.set_save_state(document_is_saved);
|
||||
@@ -709,7 +749,7 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageData<'_>> for PortfolioMes
|
||||
}
|
||||
|
||||
let Some(document) = self.documents.get_mut(&document_id) else {
|
||||
warn!("Tried to read non existant document");
|
||||
warn!("Tried to read non existent document");
|
||||
return;
|
||||
};
|
||||
if !document.is_loaded {
|
||||
|
||||
@@ -15,7 +15,7 @@ use std::any::Any;
|
||||
use std::sync::Arc;
|
||||
|
||||
/// The spreadsheet UI allows for instance data to be previewed.
|
||||
#[derive(Default, Debug, Clone)]
|
||||
#[derive(Default, Debug, Clone, ExtractField)]
|
||||
pub struct SpreadsheetMessageHandler {
|
||||
/// Sets whether or not the spreadsheet is drawn.
|
||||
pub spreadsheet_view_open: bool,
|
||||
@@ -25,6 +25,7 @@ pub struct SpreadsheetMessageHandler {
|
||||
viewing_vector_data_domain: VectorDataDomain,
|
||||
}
|
||||
|
||||
#[message_handler_data]
|
||||
impl MessageHandler<SpreadsheetMessage, ()> for SpreadsheetMessageHandler {
|
||||
fn process_message(&mut self, message: SpreadsheetMessage, responses: &mut VecDeque<Message>, _data: ()) {
|
||||
match message {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::messages::portfolio::document::node_graph::utility_types::GraphWireStyle;
|
||||
use crate::messages::portfolio::document::utility_types::wires::GraphWireStyle;
|
||||
use crate::messages::preferences::SelectionMode;
|
||||
use crate::messages::prelude::*;
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
use crate::consts::VIEWPORT_ZOOM_WHEEL_RATE;
|
||||
use crate::messages::input_mapper::key_mapping::MappingVariant;
|
||||
use crate::messages::portfolio::document::node_graph::utility_types::GraphWireStyle;
|
||||
use crate::messages::portfolio::document::utility_types::wires::GraphWireStyle;
|
||||
use crate::messages::preferences::SelectionMode;
|
||||
use crate::messages::prelude::*;
|
||||
use graph_craft::wasm_application_io::EditorPreferences;
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, serde::Serialize, serde::Deserialize, specta::Type)]
|
||||
#[derive(Debug, PartialEq, Clone, serde::Serialize, serde::Deserialize, specta::Type, ExtractField)]
|
||||
pub struct PreferencesMessageHandler {
|
||||
pub selection_mode: SelectionMode,
|
||||
pub zoom_with_scroll: bool,
|
||||
@@ -44,6 +44,7 @@ impl Default for PreferencesMessageHandler {
|
||||
}
|
||||
}
|
||||
|
||||
#[message_handler_data]
|
||||
impl MessageHandler<PreferencesMessage, ()> for PreferencesMessageHandler {
|
||||
fn process_message(&mut self, message: PreferencesMessage, responses: &mut VecDeque<Message>, _data: ()) {
|
||||
match message {
|
||||
@@ -86,7 +87,8 @@ impl MessageHandler<PreferencesMessage, ()> for PreferencesMessageHandler {
|
||||
}
|
||||
PreferencesMessage::GraphWireStyle { style } => {
|
||||
self.graph_wire_style = style;
|
||||
responses.add(NodeGraphMessage::SendGraph);
|
||||
responses.add(NodeGraphMessage::UnloadWires);
|
||||
responses.add(NodeGraphMessage::SendWires);
|
||||
}
|
||||
PreferencesMessage::ViewportZoomWheelRate { rate } => {
|
||||
self.viewport_zoom_wheel_rate = rate;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// Root
|
||||
pub use crate::utility_traits::{ActionList, AsMessage, MessageHandler, ToDiscriminant, TransitiveChild};
|
||||
|
||||
pub use crate::utility_traits::{ActionList, AsMessage, HierarchicalTree, MessageHandler, ToDiscriminant, TransitiveChild};
|
||||
pub use crate::utility_types::{DebugMessageTree, MessageData};
|
||||
// Message, MessageData, MessageDiscriminant, MessageHandler
|
||||
pub use crate::messages::animation::{AnimationMessage, AnimationMessageDiscriminant, AnimationMessageHandler};
|
||||
pub use crate::messages::broadcast::{BroadcastMessage, BroadcastMessageDiscriminant, BroadcastMessageHandler};
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
use crate::consts::{COMPASS_ROSE_ARROW_CLICK_TARGET_ANGLE, COMPASS_ROSE_HOVER_RING_DIAMETER, COMPASS_ROSE_RING_INNER_DIAMETER};
|
||||
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
|
||||
use crate::messages::prelude::DocumentMessageHandler;
|
||||
use glam::{DAffine2, DVec2};
|
||||
use std::f64::consts::FRAC_PI_2;
|
||||
@@ -10,25 +9,32 @@ pub struct CompassRose {
|
||||
}
|
||||
|
||||
impl CompassRose {
|
||||
fn get_layer_pivot_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
|
||||
}
|
||||
pub fn refresh_position(&mut self, document: &DocumentMessageHandler) {
|
||||
let selected_nodes = document.network_interface.selected_nodes();
|
||||
let mut layers = selected_nodes.selected_visible_and_unlocked_layers(&document.network_interface);
|
||||
let selected = document.network_interface.selected_nodes();
|
||||
|
||||
let Some(first) = layers.next() else { return };
|
||||
let count = layers.count() + 1;
|
||||
let transform = if count == 1 {
|
||||
Self::get_layer_pivot_transform(first, document)
|
||||
} else {
|
||||
let [min, max] = document.selected_visible_and_unlock_layers_bounding_box_viewport().unwrap_or([DVec2::ZERO, DVec2::ONE]);
|
||||
DAffine2::from_translation(min) * DAffine2::from_scale(max - min)
|
||||
};
|
||||
if !selected.has_selected_nodes() {
|
||||
return;
|
||||
}
|
||||
|
||||
let transform = selected
|
||||
.selected_visible_and_unlocked_layers(&document.network_interface)
|
||||
.find(|layer| !document.network_interface.is_artboard(&layer.to_node(), &[]))
|
||||
.map(|layer| document.metadata().transform_to_viewport_with_first_transform_node_if_group(layer, &document.network_interface))
|
||||
.unwrap_or_default();
|
||||
|
||||
let bounds = document
|
||||
.network_interface
|
||||
.selected_nodes()
|
||||
.selected_visible_and_unlocked_layers(&document.network_interface)
|
||||
.filter_map(|layer| {
|
||||
document
|
||||
.metadata()
|
||||
.bounding_box_with_transform(layer, transform.inverse() * document.metadata().transform_to_viewport(layer))
|
||||
})
|
||||
.reduce(graphene_std::renderer::Quad::combine_bounds);
|
||||
|
||||
let [min, max] = bounds.unwrap_or([DVec2::ZERO, DVec2::ONE]);
|
||||
let transform = transform * DAffine2::from_translation(min) * DAffine2::from_scale(max - min);
|
||||
|
||||
self.compass_center = transform.transform_point2(DVec2::splat(0.5));
|
||||
}
|
||||
|
||||
@@ -5,9 +5,9 @@ use crate::messages::portfolio::document::utility_types::network_interface::{Flo
|
||||
use crate::messages::prelude::*;
|
||||
use bezier_rs::Subpath;
|
||||
use glam::DVec2;
|
||||
use graph_craft::concrete;
|
||||
use graph_craft::document::value::TaggedValue;
|
||||
use graph_craft::document::{NodeId, NodeInput};
|
||||
use graph_craft::{ProtoNodeIdentifier, concrete};
|
||||
use graphene_std::Color;
|
||||
use graphene_std::NodeInputDecleration;
|
||||
use graphene_std::raster::BlendMode;
|
||||
@@ -243,20 +243,26 @@ pub fn new_custom(id: NodeId, nodes: Vec<(NodeId, NodeTemplate)>, parent: LayerN
|
||||
LayerNodeIdentifier::new_unchecked(id)
|
||||
}
|
||||
|
||||
/// Locate the final pivot from the transform (TODO: decide how the pivot should actually work)
|
||||
pub fn get_pivot(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option<DVec2> {
|
||||
let pivot_node_input_index = 5;
|
||||
if let TaggedValue::DVec2(pivot) = NodeGraphLayer::new(layer, network_interface).find_input("Transform", pivot_node_input_index)? {
|
||||
Some(*pivot)
|
||||
/// Locate the origin of the transform node
|
||||
pub fn get_origin(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option<DVec2> {
|
||||
use graphene_std::transform_nodes::transform::TranslateInput;
|
||||
|
||||
if let TaggedValue::DVec2(origin) = NodeGraphLayer::new(layer, network_interface).find_input("Transform", TranslateInput::INDEX)? {
|
||||
Some(*origin)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_viewport_pivot(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> DVec2 {
|
||||
pub fn get_viewport_origin(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> DVec2 {
|
||||
let origin = get_origin(layer, network_interface).unwrap_or_default();
|
||||
network_interface.document_metadata().document_to_viewport.transform_point2(origin)
|
||||
}
|
||||
|
||||
pub fn get_viewport_center(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)
|
||||
let center = DVec2::splat(0.5);
|
||||
network_interface.document_metadata().transform_to_viewport(layer).transform_point2(min + (max - min) * center)
|
||||
}
|
||||
|
||||
/// Get the current gradient of a layer from the closest "Fill" node.
|
||||
@@ -420,14 +426,14 @@ impl<'a> NodeGraphLayer<'a> {
|
||||
}
|
||||
|
||||
/// Node id of a protonode if it exists in the layer's primary flow
|
||||
pub fn upstream_node_id_from_protonode(&self, protonode_identifier: &'static str) -> Option<NodeId> {
|
||||
pub fn upstream_node_id_from_protonode(&self, protonode_identifier: ProtoNodeIdentifier) -> Option<NodeId> {
|
||||
self.horizontal_layer_flow()
|
||||
// Take until a different layer is reached
|
||||
.take_while(|&node_id| node_id == self.layer_node || !self.network_interface.is_layer(&node_id, &[]))
|
||||
.find(move |node_id| {
|
||||
.find(|node_id| {
|
||||
self.network_interface
|
||||
.implementation(node_id, &[])
|
||||
.is_some_and(move |implementation| *implementation == graph_craft::document::DocumentNodeImplementation::proto(protonode_identifier))
|
||||
.is_some_and(|implementation| *implementation == graph_craft::document::DocumentNodeImplementation::ProtoNode(protonode_identifier.clone()))
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -1,26 +1,184 @@
|
||||
//! Handler for the pivot overlay visible on the selected layer(s) whilst using the Select tool which controls the center of rotation/scale and origin of the layer.
|
||||
//! Handler for the pivot overlay visible on the selected layer(s) whilst using the Select tool which controls the center of rotation/scale.
|
||||
|
||||
use super::graph_modification_utils;
|
||||
use crate::consts::PIVOT_DIAMETER;
|
||||
use crate::messages::portfolio::document::overlays::utility_types::OverlayContext;
|
||||
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
|
||||
use crate::messages::prelude::*;
|
||||
use crate::messages::tool::common_functionality::graph_modification_utils;
|
||||
use crate::messages::tool::tool_messages::path_tool::PathOptionsUpdate;
|
||||
use crate::messages::tool::tool_messages::select_tool::SelectOptionsUpdate;
|
||||
use crate::messages::tool::tool_messages::tool_prelude::*;
|
||||
use glam::{DAffine2, DVec2};
|
||||
use graphene_std::transform::ReferencePoint;
|
||||
use std::collections::VecDeque;
|
||||
use graphene_std::{transform::ReferencePoint, vector::ManipulatorPointId};
|
||||
use std::fmt;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub fn pin_pivot_widget(active: bool, enabled: bool, source: PivotToolSource) -> WidgetHolder {
|
||||
IconButton::new(if active { "PinActive" } else { "PinInactive" }, 24)
|
||||
.tooltip(String::from(if active { "Unpin Custom Pivot" } else { "Pin Custom Pivot" }) + "\n\nUnless pinned, the pivot will return to its prior reference point when a new selection is made.")
|
||||
.disabled(!enabled)
|
||||
.on_update(move |_| match source {
|
||||
PivotToolSource::Select => SelectToolMessage::SelectOptions(SelectOptionsUpdate::TogglePivotPinned).into(),
|
||||
PivotToolSource::Path => PathToolMessage::UpdateOptions(PathOptionsUpdate::TogglePivotPinned).into(),
|
||||
})
|
||||
.widget_holder()
|
||||
}
|
||||
|
||||
pub fn pivot_reference_point_widget(disabled: bool, reference_point: ReferencePoint, source: PivotToolSource) -> WidgetHolder {
|
||||
ReferencePointInput::new(reference_point)
|
||||
.tooltip("Custom Pivot Reference Point\n\nPlaces the pivot at a corner, edge, or center of the selection bounds, unless it is dragged elsewhere.")
|
||||
.disabled(disabled)
|
||||
.on_update(move |pivot_input: &ReferencePointInput| match source {
|
||||
PivotToolSource::Select => SelectToolMessage::SetPivot { position: pivot_input.value }.into(),
|
||||
PivotToolSource::Path => PathToolMessage::SetPivot { position: pivot_input.value }.into(),
|
||||
})
|
||||
.widget_holder()
|
||||
}
|
||||
|
||||
pub fn pivot_gizmo_type_widget(state: PivotGizmoState, source: PivotToolSource) -> Vec<WidgetHolder> {
|
||||
let gizmo_type_entries = [PivotGizmoType::Pivot, PivotGizmoType::Average, PivotGizmoType::Active]
|
||||
.iter()
|
||||
.map(|gizmo_type| {
|
||||
MenuListEntry::new(format!("{gizmo_type:?}")).label(gizmo_type.to_string()).on_commit({
|
||||
let value = source.clone();
|
||||
move |_| match value {
|
||||
PivotToolSource::Select => SelectToolMessage::SelectOptions(SelectOptionsUpdate::PivotGizmoType(*gizmo_type)).into(),
|
||||
PivotToolSource::Path => PathToolMessage::UpdateOptions(PathOptionsUpdate::PivotGizmoType(*gizmo_type)).into(),
|
||||
}
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
|
||||
vec![
|
||||
CheckboxInput::new(!state.disabled)
|
||||
.tooltip(
|
||||
"Pivot Gizmo\n\
|
||||
\n\
|
||||
Enabled: the chosen gizmo type is shown and used to control rotation and scaling.\n\
|
||||
Disabled: rotation and scaling occurs about the center of the selection bounds.",
|
||||
)
|
||||
.on_update(move |optional_input: &CheckboxInput| match source {
|
||||
PivotToolSource::Select => SelectToolMessage::SelectOptions(SelectOptionsUpdate::TogglePivotGizmoType(optional_input.checked)).into(),
|
||||
PivotToolSource::Path => PathToolMessage::UpdateOptions(PathOptionsUpdate::TogglePivotGizmoType(optional_input.checked)).into(),
|
||||
})
|
||||
.widget_holder(),
|
||||
Separator::new(SeparatorType::Related).widget_holder(),
|
||||
DropdownInput::new(vec![gizmo_type_entries])
|
||||
.selected_index(Some(match state.gizmo_type {
|
||||
PivotGizmoType::Pivot => 0,
|
||||
PivotGizmoType::Average => 1,
|
||||
PivotGizmoType::Active => 2,
|
||||
}))
|
||||
.tooltip(
|
||||
"Pivot Gizmo Type\n\
|
||||
\n\
|
||||
Selects which gizmo type is shown and used as the center of rotation/scaling transformations.\n\
|
||||
\n\
|
||||
Custom Pivot: rotates and scales relative to the selection bounds, or elsewhere if dragged.\n\
|
||||
Origin (Average Point): rotates and scales about the average point of all selected layer origins.\n\
|
||||
Origin (Active Object): rotates and scales about the origin of the most recently selected layer.",
|
||||
)
|
||||
.disabled(state.disabled)
|
||||
.widget_holder(),
|
||||
]
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Clone, Debug, Default, serde::Serialize, serde::Deserialize)]
|
||||
pub enum PivotToolSource {
|
||||
Path,
|
||||
#[default]
|
||||
Select,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Clone, Debug, Default, serde::Serialize, serde::Deserialize)]
|
||||
pub struct PivotGizmo {
|
||||
pub pivot: Pivot,
|
||||
pub state: PivotGizmoState,
|
||||
pub layer: Option<LayerNodeIdentifier>,
|
||||
pub point: Option<ManipulatorPointId>,
|
||||
}
|
||||
|
||||
impl PivotGizmo {
|
||||
pub fn position(&self, document: &DocumentMessageHandler) -> DVec2 {
|
||||
let network = &document.network_interface;
|
||||
(!self.state.disabled)
|
||||
.then_some({
|
||||
match self.state.gizmo_type {
|
||||
PivotGizmoType::Average => Some(network.selected_nodes().selected_visible_and_unlocked_layers_mean_average_origin(network)),
|
||||
PivotGizmoType::Pivot => self.pivot.pivot,
|
||||
PivotGizmoType::Active => self.layer.map(|layer| graph_modification_utils::get_viewport_origin(layer, network)),
|
||||
}
|
||||
})
|
||||
.flatten()
|
||||
.unwrap_or_else(|| self.pivot.transform_from_normalized.transform_point2(DVec2::splat(0.5)))
|
||||
}
|
||||
|
||||
pub fn recalculate_transform(&mut self, document: &DocumentMessageHandler) -> DAffine2 {
|
||||
self.pivot.recalculate_pivot(document);
|
||||
self.pivot.transform_from_normalized
|
||||
}
|
||||
|
||||
pub fn pin_active(&self) -> bool {
|
||||
self.pivot.pinned && self.state.is_pivot_type()
|
||||
}
|
||||
|
||||
pub fn pivot_disconnected(&self) -> bool {
|
||||
self.pivot.old_pivot_position == ReferencePoint::None
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, PartialEq, Eq, Clone, Copy, Debug, Hash, serde::Serialize, serde::Deserialize, specta::Type)]
|
||||
pub enum PivotGizmoType {
|
||||
// Pivot
|
||||
#[default]
|
||||
Pivot,
|
||||
// Origin
|
||||
Average,
|
||||
Active,
|
||||
// TODO: Add "Individual"
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Clone, Copy, Default, Debug, Hash, serde::Serialize, serde::Deserialize, specta::Type)]
|
||||
pub struct PivotGizmoState {
|
||||
pub disabled: bool,
|
||||
pub gizmo_type: PivotGizmoType,
|
||||
}
|
||||
|
||||
impl PivotGizmoState {
|
||||
pub fn is_pivot_type(&self) -> bool {
|
||||
self.gizmo_type == PivotGizmoType::Pivot || self.disabled
|
||||
}
|
||||
|
||||
pub fn is_pivot(&self) -> bool {
|
||||
self.gizmo_type == PivotGizmoType::Pivot && !self.disabled
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for PivotGizmoType {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
PivotGizmoType::Pivot => write!(f, "Custom Pivot"),
|
||||
PivotGizmoType::Average => write!(f, "Origin (Average Point)"),
|
||||
PivotGizmoType::Active => write!(f, "Origin (Active Object)"),
|
||||
// TODO: Add "Origin (Individual)"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]
|
||||
pub struct Pivot {
|
||||
/// Pivot between (0,0) and (1,1)
|
||||
normalized_pivot: DVec2,
|
||||
/// Transform to get from normalized pivot to viewspace
|
||||
transform_from_normalized: DAffine2,
|
||||
/// The viewspace pivot position (if applicable)
|
||||
pivot: Option<DVec2>,
|
||||
pub transform_from_normalized: DAffine2,
|
||||
/// The viewspace pivot position
|
||||
pub pivot: Option<DVec2>,
|
||||
/// The old pivot position in the GUI, used to reduce refreshes of the document bar
|
||||
old_pivot_position: ReferencePoint,
|
||||
pub old_pivot_position: ReferencePoint,
|
||||
/// The last ReferencePoint which wasn't none
|
||||
pub last_non_none_reference_point: ReferencePoint,
|
||||
/// Used to enable and disable the pivot
|
||||
active: bool,
|
||||
pub pinned: bool,
|
||||
/// Had selected_visible_and_unlocked_layers
|
||||
pub empty: bool,
|
||||
}
|
||||
|
||||
impl Default for Pivot {
|
||||
@@ -30,84 +188,62 @@ impl Default for Pivot {
|
||||
transform_from_normalized: Default::default(),
|
||||
pivot: Default::default(),
|
||||
old_pivot_position: ReferencePoint::Center,
|
||||
active: true,
|
||||
last_non_none_reference_point: ReferencePoint::Center,
|
||||
pinned: false,
|
||||
empty: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Pivot {
|
||||
/// Calculates the transform that gets from normalized pivot to viewspace.
|
||||
fn get_layer_pivot_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 pivot position and transform.
|
||||
fn recalculate_pivot(&mut self, document: &DocumentMessageHandler) {
|
||||
if !self.active {
|
||||
pub fn recalculate_pivot(&mut self, document: &DocumentMessageHandler) {
|
||||
let selected = document.network_interface.selected_nodes();
|
||||
self.empty = !selected.has_selected_nodes();
|
||||
if !selected.has_selected_nodes() {
|
||||
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
|
||||
let transform = selected
|
||||
.selected_visible_and_unlocked_layers(&document.network_interface)
|
||||
.find(|layer| !document.network_interface.is_artboard(&layer.to_node(), &[]))
|
||||
.map(|layer| document.metadata().transform_to_viewport_with_first_transform_node_if_group(layer, &document.network_interface))
|
||||
.unwrap_or_default();
|
||||
|
||||
let bounds = document
|
||||
.network_interface
|
||||
.selected_nodes()
|
||||
.selected_visible_and_unlocked_layers(&document.network_interface)
|
||||
.filter_map(|layer| {
|
||||
document
|
||||
.metadata()
|
||||
.bounding_box_with_transform(layer, transform.inverse() * document.metadata().transform_to_viewport(layer))
|
||||
})
|
||||
.reduce(graphene_std::renderer::Quad::combine_bounds);
|
||||
|
||||
let [min, max] = bounds.unwrap_or([DVec2::ZERO, DVec2::ONE]);
|
||||
self.transform_from_normalized = transform * DAffine2::from_translation(min) * DAffine2::from_scale(max - min);
|
||||
|
||||
if self.old_pivot_position != ReferencePoint::None {
|
||||
self.pivot = Some(self.transform_from_normalized.transform_point2(self.normalized_pivot));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn recalculate_pivot_for_layer(&mut self, document: &DocumentMessageHandler, bounds: Option<[DVec2; 2]>) {
|
||||
let selected = document.network_interface.selected_nodes();
|
||||
if !selected.has_selected_nodes() {
|
||||
self.normalized_pivot = DVec2::splat(0.5);
|
||||
self.pivot = 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_pivot = graph_modification_utils::get_pivot(first, &document.network_interface).unwrap_or(DVec2::splat(0.5));
|
||||
self.normalized_pivot = normalized_pivot;
|
||||
self.transform_from_normalized = Self::get_layer_pivot_transform(first, document);
|
||||
self.pivot = Some(self.transform_from_normalized.transform_point2(normalized_pivot));
|
||||
} else {
|
||||
// If more than one layer is selected we use the AABB with the mean of the pivots
|
||||
let xy_summation = document
|
||||
.network_interface
|
||||
.selected_nodes()
|
||||
.selected_visible_and_unlocked_layers(&document.network_interface)
|
||||
.map(|layer| graph_modification_utils::get_viewport_pivot(layer, &document.network_interface))
|
||||
.reduce(|a, b| a + b)
|
||||
.unwrap_or_default();
|
||||
|
||||
let pivot = xy_summation / selected_layers_count as f64;
|
||||
self.pivot = Some(pivot);
|
||||
let [min, max] = document.selected_visible_and_unlock_layers_bounding_box_viewport().unwrap_or([DVec2::ZERO, DVec2::ONE]);
|
||||
self.normalized_pivot = (pivot - min) / (max - min);
|
||||
|
||||
self.transform_from_normalized = DAffine2::from_translation(min) * DAffine2::from_scale(max - min);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_pivot(&mut self, document: &DocumentMessageHandler, overlay_context: &mut OverlayContext, draw_data: Option<(f64,)>) {
|
||||
if !overlay_context.visibility_settings.pivot() {
|
||||
self.active = false;
|
||||
return;
|
||||
} else {
|
||||
self.active = true;
|
||||
}
|
||||
|
||||
self.recalculate_pivot(document);
|
||||
if let (Some(pivot), Some(data)) = (self.pivot, draw_data) {
|
||||
overlay_context.pivot(pivot, data.0);
|
||||
}
|
||||
let [min, max] = bounds.unwrap_or([DVec2::ZERO, DVec2::ONE]);
|
||||
self.transform_from_normalized = DAffine2::from_translation(min) * DAffine2::from_scale(max - min);
|
||||
self.pivot = Some(self.transform_from_normalized.transform_point2(self.normalized_pivot));
|
||||
}
|
||||
|
||||
/// Answers if the pivot widget has changed (so we should refresh the tool bar at the top of the canvas).
|
||||
pub fn should_refresh_pivot_position(&mut self) -> bool {
|
||||
if !self.active {
|
||||
return false;
|
||||
}
|
||||
|
||||
let new = self.to_pivot_position();
|
||||
let should_refresh = new != self.old_pivot_position;
|
||||
self.old_pivot_position = new;
|
||||
@@ -118,37 +254,24 @@ impl Pivot {
|
||||
self.normalized_pivot.into()
|
||||
}
|
||||
|
||||
/// Sets the viewport position of the pivot for all selected layers.
|
||||
pub fn set_viewport_position(&self, position: DVec2, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>) {
|
||||
if !self.active {
|
||||
/// Sets the viewport position of the pivot.
|
||||
pub fn set_viewport_position(&mut self, position: DVec2) {
|
||||
if self.transform_from_normalized.matrix2.determinant().abs() <= f64::EPSILON {
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
for layer in document.network_interface.selected_nodes().selected_visible_and_unlocked_layers(&document.network_interface) {
|
||||
let transform = Self::get_layer_pivot_transform(layer, document);
|
||||
// Only update the pivot when computed position is finite.
|
||||
if transform.matrix2.determinant().abs() <= f64::EPSILON {
|
||||
return;
|
||||
};
|
||||
let pivot = transform.inverse().transform_point2(position);
|
||||
responses.add(GraphOperationMessage::TransformSetPivot { layer, pivot });
|
||||
}
|
||||
self.normalized_pivot = self.transform_from_normalized.inverse().transform_point2(position);
|
||||
self.pivot = Some(position);
|
||||
}
|
||||
|
||||
/// Set the pivot 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);
|
||||
/// Set the pivot using a normalized position.
|
||||
pub fn set_normalized_position(&mut self, position: DVec2) {
|
||||
self.normalized_pivot = position;
|
||||
self.pivot = Some(self.transform_from_normalized.transform_point2(position));
|
||||
}
|
||||
|
||||
/// Answers if the pointer is currently positioned over the pivot.
|
||||
pub fn is_over(&self, mouse: DVec2) -> bool {
|
||||
if !self.active {
|
||||
return false;
|
||||
}
|
||||
self.pivot.filter(|&pivot| mouse.distance_squared(pivot) < (PIVOT_DIAMETER / 2.).powi(2)).is_some()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,6 +96,14 @@ impl SelectedLayerState {
|
||||
self.selected_segments.remove(&segment);
|
||||
}
|
||||
|
||||
pub fn deselect_all_points_in_layer(&mut self) {
|
||||
self.selected_points.clear();
|
||||
}
|
||||
|
||||
pub fn deselect_all_segments_in_layer(&mut self) {
|
||||
self.selected_segments.clear();
|
||||
}
|
||||
|
||||
pub fn clear_points(&mut self) {
|
||||
self.selected_points.clear();
|
||||
}
|
||||
@@ -204,15 +212,15 @@ impl ClosestSegment {
|
||||
self.bezier_point_to_viewport
|
||||
}
|
||||
|
||||
pub fn closest_point(&self, document_metadata: &DocumentMetadata) -> DVec2 {
|
||||
let transform = document_metadata.transform_to_viewport(self.layer);
|
||||
pub fn closest_point(&self, document_metadata: &DocumentMetadata, network_interface: &NodeNetworkInterface) -> DVec2 {
|
||||
let transform = document_metadata.transform_to_viewport_if_feeds(self.layer, network_interface);
|
||||
let bezier_point = self.bezier.evaluate(TValue::Parametric(self.t));
|
||||
transform.transform_point2(bezier_point)
|
||||
}
|
||||
|
||||
/// Updates this [`ClosestSegment`] with the viewport-space location of the closest point on the segment to the given mouse position.
|
||||
pub fn update_closest_point(&mut self, document_metadata: &DocumentMetadata, mouse_position: DVec2) {
|
||||
let transform = document_metadata.transform_to_viewport(self.layer);
|
||||
pub fn update_closest_point(&mut self, document_metadata: &DocumentMetadata, network_interface: &NodeNetworkInterface, mouse_position: DVec2) {
|
||||
let transform = document_metadata.transform_to_viewport_if_feeds(self.layer, network_interface);
|
||||
let layer_mouse_pos = transform.inverse().transform_point2(mouse_position);
|
||||
|
||||
let t = self.bezier.project(layer_mouse_pos).clamp(0., 1.);
|
||||
@@ -231,9 +239,9 @@ impl ClosestSegment {
|
||||
tolerance.powi(2) < self.distance_squared(mouse_position)
|
||||
}
|
||||
|
||||
pub fn handle_positions(&self, document_metadata: &DocumentMetadata) -> (Option<DVec2>, Option<DVec2>) {
|
||||
pub fn handle_positions(&self, document_metadata: &DocumentMetadata, network_interface: &NodeNetworkInterface) -> (Option<DVec2>, Option<DVec2>) {
|
||||
// Transform to viewport space
|
||||
let transform = document_metadata.transform_to_viewport(self.layer);
|
||||
let transform = document_metadata.transform_to_viewport_if_feeds(self.layer, network_interface);
|
||||
|
||||
// Split the Bezier at the parameter `t`
|
||||
let [first, second] = self.bezier.split(TValue::Parametric(self.t));
|
||||
@@ -299,7 +307,7 @@ impl ClosestSegment {
|
||||
}
|
||||
|
||||
pub fn calculate_perp(&self, document: &DocumentMessageHandler) -> DVec2 {
|
||||
let tangent = if let (Some(handle1), Some(handle2)) = self.handle_positions(document.metadata()) {
|
||||
let tangent = if let (Some(handle1), Some(handle2)) = self.handle_positions(document.metadata(), &document.network_interface) {
|
||||
(handle1 - handle2).try_normalize()
|
||||
} else {
|
||||
let [first_point, last_point] = self.points();
|
||||
@@ -331,7 +339,7 @@ impl ClosestSegment {
|
||||
break_colinear_molding: bool,
|
||||
temporary_adjacent_handles_while_molding: Option<[Option<HandleId>; 2]>,
|
||||
) -> Option<[Option<HandleId>; 2]> {
|
||||
let transform = document.metadata().transform_to_viewport(self.layer);
|
||||
let transform = document.metadata().transform_to_viewport_if_feeds(self.layer, &document.network_interface);
|
||||
|
||||
let start = self.bezier.start;
|
||||
let end = self.bezier.end;
|
||||
@@ -388,6 +396,10 @@ impl ClosestSegment {
|
||||
|
||||
// TODO Consider keeping a list of selected manipulators to minimize traversals of the layers
|
||||
impl ShapeState {
|
||||
pub fn is_selected_layer(&self, layer: LayerNodeIdentifier) -> bool {
|
||||
self.selected_shape_state.contains_key(&layer)
|
||||
}
|
||||
|
||||
pub fn is_point_ignored(&self, point: &ManipulatorPointId) -> bool {
|
||||
(point.as_handle().is_some() && self.ignore_handles) || (point.as_anchor().is_some() && self.ignore_anchors)
|
||||
}
|
||||
@@ -495,7 +507,7 @@ impl ShapeState {
|
||||
continue;
|
||||
};
|
||||
|
||||
let to_document = document.metadata().transform_to_document(*layer);
|
||||
let to_document = document.metadata().transform_to_document_if_feeds(*layer, &document.network_interface);
|
||||
|
||||
for &selected in &state.selected_points {
|
||||
let source = match selected {
|
||||
@@ -552,7 +564,11 @@ impl ShapeState {
|
||||
let already_selected = selected_shape_state.is_point_selected(manipulator_point_id);
|
||||
|
||||
// Offset to snap the selected point to the cursor
|
||||
let offset = mouse_position - network_interface.document_metadata().transform_to_viewport(layer).transform_point2(point_position);
|
||||
let offset = mouse_position
|
||||
- network_interface
|
||||
.document_metadata()
|
||||
.transform_to_viewport_if_feeds(layer, network_interface)
|
||||
.transform_point2(point_position);
|
||||
|
||||
// This is selecting the manipulator only for now, next to generalize to points
|
||||
|
||||
@@ -609,7 +625,11 @@ impl ShapeState {
|
||||
let already_selected = selected_shape_state.is_point_selected(manipulator_point_id);
|
||||
|
||||
// Offset to snap the selected point to the cursor
|
||||
let offset = mouse_position - network_interface.document_metadata().transform_to_viewport(layer).transform_point2(point_position);
|
||||
let offset = mouse_position
|
||||
- network_interface
|
||||
.document_metadata()
|
||||
.transform_to_viewport_if_feeds(layer, network_interface)
|
||||
.transform_point2(point_position);
|
||||
|
||||
// Gather current selection information
|
||||
let points = self
|
||||
@@ -637,11 +657,11 @@ impl ShapeState {
|
||||
}
|
||||
|
||||
/// Selects all anchors connected to the selected subpath, and deselects all handles, for the given layer.
|
||||
pub fn select_connected_anchors(&mut self, document: &DocumentMessageHandler, layer: LayerNodeIdentifier, mouse: DVec2) {
|
||||
pub fn select_connected(&mut self, document: &DocumentMessageHandler, layer: LayerNodeIdentifier, mouse: DVec2, points: bool, segments: bool) {
|
||||
let Some(vector_data) = document.network_interface.compute_modified_vector(layer) else {
|
||||
return;
|
||||
};
|
||||
let to_viewport = document.metadata().transform_to_viewport(layer);
|
||||
let to_viewport = document.metadata().transform_to_viewport_if_feeds(layer, &document.network_interface);
|
||||
let layer_mouse = to_viewport.inverse().transform_point2(mouse);
|
||||
let state = self.selected_shape_state.entry(layer).or_default();
|
||||
|
||||
@@ -655,18 +675,39 @@ impl ShapeState {
|
||||
}
|
||||
}
|
||||
state.clear_points();
|
||||
|
||||
if selected_stack.is_empty() {
|
||||
// Fall back on just selecting all points in the layer
|
||||
for &point in vector_data.point_domain.ids() {
|
||||
state.select_point(ManipulatorPointId::Anchor(point))
|
||||
// Fall back on just selecting all points/segments in the layer
|
||||
if points {
|
||||
for &point in vector_data.point_domain.ids() {
|
||||
state.select_point(ManipulatorPointId::Anchor(point));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Select all connected points
|
||||
while let Some(point) = selected_stack.pop() {
|
||||
let anchor_point = ManipulatorPointId::Anchor(point);
|
||||
if !state.is_point_selected(anchor_point) {
|
||||
state.select_point(anchor_point);
|
||||
selected_stack.extend(vector_data.connected_points(point));
|
||||
if segments {
|
||||
for &segment in vector_data.segment_domain.ids() {
|
||||
state.select_segment(segment);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
let mut connected_points = HashSet::new();
|
||||
|
||||
while let Some(point) = selected_stack.pop() {
|
||||
if !connected_points.contains(&point) {
|
||||
connected_points.insert(point);
|
||||
selected_stack.extend(vector_data.connected_points(point));
|
||||
}
|
||||
}
|
||||
|
||||
if points {
|
||||
connected_points.iter().for_each(|point| state.select_point(ManipulatorPointId::Anchor(*point)));
|
||||
}
|
||||
|
||||
if segments {
|
||||
for (id, _, start, end) in vector_data.segment_bezier_iter() {
|
||||
if connected_points.contains(&start) || connected_points.contains(&end) {
|
||||
state.select_segment(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -842,7 +883,7 @@ impl ShapeState {
|
||||
}
|
||||
|
||||
let vector_data = network_interface.compute_modified_vector(layer)?;
|
||||
let transform = network_interface.document_metadata().transform_to_document(layer).inverse();
|
||||
let transform = network_interface.document_metadata().transform_to_document_if_feeds(layer, network_interface).inverse();
|
||||
let position = transform.transform_point2(new_position);
|
||||
let current_position = point.get_position(&vector_data)?;
|
||||
let delta = position - current_position;
|
||||
@@ -993,7 +1034,7 @@ impl ShapeState {
|
||||
let Some(vector_data) = document.network_interface.compute_modified_vector(layer) else {
|
||||
continue;
|
||||
};
|
||||
let transform = document.metadata().transform_to_document(layer);
|
||||
let transform = document.metadata().transform_to_document_if_feeds(layer, &document.network_interface);
|
||||
|
||||
for &point in layer_state.selected_points.iter() {
|
||||
let Some(handles) = point.get_handle_pair(&vector_data) else { continue };
|
||||
@@ -1038,7 +1079,7 @@ impl ShapeState {
|
||||
|
||||
let mut normalized = handle_directions[0].and_then(|a| handle_directions[1].and_then(|b| (a - b).try_normalize()));
|
||||
|
||||
if normalized.is_none() {
|
||||
if normalized.is_none() || handle_directions.iter().any(|&d| d.is_some_and(|d| d.length_squared() < f64::EPSILON * 1e5)) {
|
||||
handle_directions = anchor_positions.map(|relative_anchor| relative_anchor.map(|relative_anchor| (relative_anchor - anchor) / 3.));
|
||||
normalized = handle_directions[0].and_then(|a| handle_directions[1].and_then(|b| (a - b).try_normalize()))
|
||||
}
|
||||
@@ -1083,8 +1124,8 @@ impl ShapeState {
|
||||
|
||||
let opposing_handles = handle_lengths.as_ref().and_then(|handle_lengths| handle_lengths.get(&layer));
|
||||
|
||||
let transform_to_viewport_space = document.metadata().transform_to_viewport(layer);
|
||||
let transform_to_document_space = document.metadata().transform_to_document(layer);
|
||||
let transform_to_viewport_space = document.metadata().transform_to_viewport_if_feeds(layer, &document.network_interface);
|
||||
let transform_to_document_space = document.metadata().transform_to_document_if_feeds(layer, &document.network_interface);
|
||||
let delta_transform = if in_viewport_space {
|
||||
transform_to_viewport_space
|
||||
} else {
|
||||
@@ -1177,7 +1218,7 @@ impl ShapeState {
|
||||
.iter()
|
||||
.filter_map(|(&layer, state)| {
|
||||
let vector_data = document.network_interface.compute_modified_vector(layer)?;
|
||||
let transform = document.metadata().transform_to_document(layer);
|
||||
let transform = document.metadata().transform_to_document_if_feeds(layer, &document.network_interface);
|
||||
let opposing_handle_lengths = vector_data
|
||||
.colinear_manipulators
|
||||
.iter()
|
||||
@@ -1542,7 +1583,7 @@ impl ShapeState {
|
||||
let mut manipulator_point = None;
|
||||
|
||||
let vector_data = network_interface.compute_modified_vector(layer)?;
|
||||
let viewspace = network_interface.document_metadata().transform_to_viewport(layer);
|
||||
let viewspace = network_interface.document_metadata().transform_to_viewport_if_feeds(layer, network_interface);
|
||||
|
||||
// Handles
|
||||
for (segment_id, bezier, _, _) in vector_data.segment_bezier_iter() {
|
||||
@@ -1578,7 +1619,7 @@ impl ShapeState {
|
||||
|
||||
/// Find the `t` value along the path segment we have clicked upon, together with that segment ID.
|
||||
fn closest_segment(&self, network_interface: &NodeNetworkInterface, layer: LayerNodeIdentifier, position: glam::DVec2, tolerance: f64) -> Option<ClosestSegment> {
|
||||
let transform = network_interface.document_metadata().transform_to_viewport(layer);
|
||||
let transform = network_interface.document_metadata().transform_to_viewport_if_feeds(layer, network_interface);
|
||||
let layer_pos = transform.inverse().transform_point2(position);
|
||||
|
||||
let tolerance = tolerance + 0.5;
|
||||
@@ -1752,7 +1793,7 @@ impl ShapeState {
|
||||
pub fn flip_smooth_sharp(&self, network_interface: &NodeNetworkInterface, target: glam::DVec2, tolerance: f64, responses: &mut VecDeque<Message>) -> bool {
|
||||
let mut process_layer = |layer| {
|
||||
let vector_data = network_interface.compute_modified_vector(layer)?;
|
||||
let transform_to_screenspace = network_interface.document_metadata().transform_to_viewport(layer);
|
||||
let transform_to_screenspace = network_interface.document_metadata().transform_to_viewport_if_feeds(layer, network_interface);
|
||||
|
||||
let mut result = None;
|
||||
let mut closest_distance_squared = tolerance * tolerance;
|
||||
@@ -1856,7 +1897,7 @@ impl ShapeState {
|
||||
|
||||
let vector_data = network_interface.compute_modified_vector(layer);
|
||||
let Some(vector_data) = vector_data else { continue };
|
||||
let transform = network_interface.document_metadata().transform_to_viewport(layer);
|
||||
let transform = network_interface.document_metadata().transform_to_viewport_if_feeds(layer, network_interface);
|
||||
|
||||
assert_eq!(vector_data.segment_domain.ids().len(), vector_data.start_point().count());
|
||||
assert_eq!(vector_data.segment_domain.ids().len(), vector_data.end_point().count());
|
||||
|
||||
@@ -77,7 +77,7 @@ mod test_ellipse {
|
||||
layers
|
||||
.filter_map(|layer| {
|
||||
let node_graph_layer = NodeGraphLayer::new(layer, &document.network_interface);
|
||||
let ellipse_node = node_graph_layer.upstream_node_id_from_protonode(ellipse::protonode_identifier())?;
|
||||
let ellipse_node = node_graph_layer.upstream_node_id_from_protonode(ellipse::IDENTIFIER)?;
|
||||
Some(ResolvedEllipse {
|
||||
radius_x: instrumented.grab_protonode_input::<ellipse::RadiusXInput>(&vec![ellipse_node], &editor.runtime).unwrap(),
|
||||
radius_y: instrumented.grab_protonode_input::<ellipse::RadiusYInput>(&vec![ellipse_node], &editor.runtime).unwrap(),
|
||||
|
||||
@@ -393,6 +393,7 @@ pub fn transforming_transform_cage(
|
||||
input: &InputPreprocessorMessageHandler,
|
||||
responses: &mut VecDeque<Message>,
|
||||
layers_dragging: &mut Vec<LayerNodeIdentifier>,
|
||||
center_of_transformation: Option<DVec2>,
|
||||
) -> (bool, bool, bool) {
|
||||
let dragging_bounds = bounding_box_manager.as_mut().and_then(|bounding_box| {
|
||||
let edges = bounding_box.check_selected_edges(input.mouse.position);
|
||||
@@ -429,17 +430,12 @@ pub fn transforming_transform_cage(
|
||||
}
|
||||
});
|
||||
|
||||
let mut selected = Selected::new(
|
||||
&mut bounds.original_transforms,
|
||||
&mut bounds.center_of_transformation,
|
||||
layers_dragging,
|
||||
responses,
|
||||
&document.network_interface,
|
||||
None,
|
||||
&ToolType::Select,
|
||||
None,
|
||||
);
|
||||
bounds.center_of_transformation = selected.mean_average_of_pivots();
|
||||
bounds.center_of_transformation = center_of_transformation.unwrap_or_else(|| {
|
||||
document
|
||||
.network_interface
|
||||
.selected_nodes()
|
||||
.selected_visible_and_unlocked_layers_mean_average_origin(&document.network_interface)
|
||||
});
|
||||
|
||||
// Check if we're hovering over a skew triangle
|
||||
let edges = bounds.check_selected_edges(input.mouse.position);
|
||||
@@ -469,18 +465,12 @@ pub fn transforming_transform_cage(
|
||||
}
|
||||
});
|
||||
|
||||
let mut selected = Selected::new(
|
||||
&mut bounds.original_transforms,
|
||||
&mut bounds.center_of_transformation,
|
||||
&selected,
|
||||
responses,
|
||||
&document.network_interface,
|
||||
None,
|
||||
&ToolType::Select,
|
||||
None,
|
||||
);
|
||||
|
||||
bounds.center_of_transformation = selected.mean_average_of_pivots();
|
||||
bounds.center_of_transformation = center_of_transformation.unwrap_or_else(|| {
|
||||
document
|
||||
.network_interface
|
||||
.selected_nodes()
|
||||
.selected_visible_and_unlocked_layers_mean_average_origin(&document.network_interface)
|
||||
});
|
||||
}
|
||||
|
||||
*layers_dragging = selected;
|
||||
|
||||
@@ -12,6 +12,7 @@ use graphene_std::raster::color::Color;
|
||||
|
||||
const ARTBOARD_OVERLAY_PROVIDER: OverlayProvider = |context| DocumentMessage::DrawArtboardOverlays(context).into();
|
||||
|
||||
#[derive(ExtractField)]
|
||||
pub struct ToolMessageData<'a> {
|
||||
pub document_id: DocumentId,
|
||||
pub document: &'a mut DocumentMessageHandler,
|
||||
@@ -21,7 +22,7 @@ pub struct ToolMessageData<'a> {
|
||||
pub preferences: &'a PreferencesMessageHandler,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
#[derive(Debug, Default, ExtractField)]
|
||||
pub struct ToolMessageHandler {
|
||||
pub tool_state: ToolFsmState,
|
||||
pub transform_layer_handler: TransformLayerMessageHandler,
|
||||
@@ -29,6 +30,7 @@ pub struct ToolMessageHandler {
|
||||
pub tool_is_active: bool,
|
||||
}
|
||||
|
||||
#[message_handler_data]
|
||||
impl MessageHandler<ToolMessage, ToolMessageData<'_>> for ToolMessageHandler {
|
||||
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, data: ToolMessageData) {
|
||||
let ToolMessageData {
|
||||
@@ -181,6 +183,11 @@ impl MessageHandler<ToolMessage, ToolMessageData<'_>> for ToolMessageHandler {
|
||||
send: Box::new(TransformLayerMessage::SelectionChanged.into()),
|
||||
});
|
||||
|
||||
responses.add(BroadcastMessage::SubscribeEvent {
|
||||
on: BroadcastEvent::SelectionChanged,
|
||||
send: Box::new(SelectToolMessage::SyncHistory.into()),
|
||||
});
|
||||
|
||||
self.tool_is_active = true;
|
||||
|
||||
let tool_data = &mut self.tool_state.tool_data;
|
||||
|
||||
@@ -13,7 +13,7 @@ use crate::messages::tool::common_functionality::transformation_cage::*;
|
||||
use graph_craft::document::NodeId;
|
||||
use graphene_std::renderer::Quad;
|
||||
|
||||
#[derive(Default)]
|
||||
#[derive(Default, ExtractField)]
|
||||
pub struct ArtboardTool {
|
||||
fsm_state: ArtboardToolFsmState,
|
||||
data: ArtboardToolData,
|
||||
@@ -48,6 +48,7 @@ impl ToolMetadata for ArtboardTool {
|
||||
}
|
||||
}
|
||||
|
||||
#[message_handler_data]
|
||||
impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for ArtboardTool {
|
||||
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) {
|
||||
self.fsm_state.process_event(message, &mut self.data, tool_data, &(), responses, false);
|
||||
@@ -567,7 +568,7 @@ mod test_artboard {
|
||||
Ok(instrumented) => instrumented,
|
||||
Err(e) => panic!("Failed to evaluate graph: {}", e),
|
||||
};
|
||||
instrumented.grab_all_input::<graphene_std::append_artboard::ArtboardInput>(&editor.runtime).collect()
|
||||
instrumented.grab_all_input::<graphene_std::graphic_element::append_artboard::ArtboardInput>(&editor.runtime).collect()
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use super::tool_prelude::*;
|
||||
use crate::consts::DEFAULT_BRUSH_SIZE;
|
||||
use crate::messages::portfolio::document::graph_operation::transform_utils::{get_current_normalized_pivot, get_current_transform};
|
||||
use crate::messages::portfolio::document::graph_operation::transform_utils::get_current_transform;
|
||||
use crate::messages::portfolio::document::node_graph::document_node_definitions::resolve_document_node_type;
|
||||
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
|
||||
use crate::messages::portfolio::document::utility_types::network_interface::FlowType;
|
||||
@@ -20,7 +20,7 @@ pub enum DrawMode {
|
||||
Restore,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
#[derive(Default, ExtractField)]
|
||||
pub struct BrushTool {
|
||||
fsm_state: BrushToolFsmState,
|
||||
data: BrushToolData,
|
||||
@@ -185,6 +185,7 @@ impl LayoutHolder for BrushTool {
|
||||
}
|
||||
}
|
||||
|
||||
#[message_handler_data]
|
||||
impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for BrushTool {
|
||||
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) {
|
||||
let ToolMessage::Brush(BrushToolMessage::UpdateOptions(action)) = message else {
|
||||
@@ -286,9 +287,7 @@ impl BrushToolData {
|
||||
}
|
||||
|
||||
if *reference == Some("Transform".to_string()) {
|
||||
let upstream = document.metadata().upstream_transform(node_id);
|
||||
let pivot = DAffine2::from_translation(upstream.transform_point2(get_current_normalized_pivot(&node.inputs)));
|
||||
self.transform = pivot * get_current_transform(&node.inputs) * pivot.inverse() * self.transform;
|
||||
self.transform = get_current_transform(&node.inputs) * self.transform;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use super::tool_prelude::*;
|
||||
use crate::messages::tool::utility_types::DocumentToolData;
|
||||
|
||||
#[derive(Default)]
|
||||
#[derive(Default, ExtractField)]
|
||||
pub struct EyedropperTool {
|
||||
fsm_state: EyedropperToolFsmState,
|
||||
data: EyedropperToolData,
|
||||
@@ -39,6 +39,7 @@ impl LayoutHolder for EyedropperTool {
|
||||
}
|
||||
}
|
||||
|
||||
#[message_handler_data]
|
||||
impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for EyedropperTool {
|
||||
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) {
|
||||
self.fsm_state.process_event(message, &mut self.data, tool_data, &(), responses, true);
|
||||
|
||||
@@ -3,7 +3,7 @@ use crate::messages::portfolio::document::overlays::utility_types::OverlayContex
|
||||
use crate::messages::tool::common_functionality::graph_modification_utils::NodeGraphLayer;
|
||||
use graphene_std::vector::style::Fill;
|
||||
|
||||
#[derive(Default)]
|
||||
#[derive(Default, ExtractField)]
|
||||
pub struct FillTool {
|
||||
fsm_state: FillToolFsmState,
|
||||
}
|
||||
@@ -41,6 +41,7 @@ impl LayoutHolder for FillTool {
|
||||
}
|
||||
}
|
||||
|
||||
#[message_handler_data]
|
||||
impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for FillTool {
|
||||
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) {
|
||||
self.fsm_state.process_event(message, &mut (), tool_data, &(), responses, true);
|
||||
|
||||
@@ -13,7 +13,7 @@ use graphene_std::Color;
|
||||
use graphene_std::vector::VectorModificationType;
|
||||
use graphene_std::vector::{PointId, SegmentId};
|
||||
|
||||
#[derive(Default)]
|
||||
#[derive(Default, ExtractField)]
|
||||
pub struct FreehandTool {
|
||||
fsm_state: FreehandToolFsmState,
|
||||
data: FreehandToolData,
|
||||
@@ -116,6 +116,7 @@ impl LayoutHolder for FreehandTool {
|
||||
}
|
||||
}
|
||||
|
||||
#[message_handler_data]
|
||||
impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for FreehandTool {
|
||||
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) {
|
||||
let ToolMessage::Freehand(FreehandToolMessage::UpdateOptions(action)) = message else {
|
||||
|
||||
@@ -7,7 +7,7 @@ use crate::messages::tool::common_functionality::graph_modification_utils::{Node
|
||||
use crate::messages::tool::common_functionality::snapping::SnapManager;
|
||||
use graphene_std::vector::style::{Fill, Gradient, GradientType};
|
||||
|
||||
#[derive(Default)]
|
||||
#[derive(Default, ExtractField)]
|
||||
pub struct GradientTool {
|
||||
fsm_state: GradientToolFsmState,
|
||||
data: GradientToolData,
|
||||
@@ -53,6 +53,7 @@ impl ToolMetadata for GradientTool {
|
||||
}
|
||||
}
|
||||
|
||||
#[message_handler_data]
|
||||
impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for GradientTool {
|
||||
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) {
|
||||
let ToolMessage::Gradient(GradientToolMessage::UpdateOptions(action)) = message else {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use super::tool_prelude::*;
|
||||
|
||||
#[derive(Default)]
|
||||
#[derive(Default, ExtractField)]
|
||||
pub struct NavigateTool {
|
||||
fsm_state: NavigateToolFsmState,
|
||||
tool_data: NavigateToolData,
|
||||
@@ -38,6 +38,7 @@ impl LayoutHolder for NavigateTool {
|
||||
}
|
||||
}
|
||||
|
||||
#[message_handler_data]
|
||||
impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for NavigateTool {
|
||||
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) {
|
||||
self.fsm_state.process_event(message, &mut self.tool_data, tool_data, &(), responses, true);
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
use super::select_tool::extend_lasso;
|
||||
use super::tool_prelude::*;
|
||||
use crate::consts::{
|
||||
COLOR_OVERLAY_BLUE, COLOR_OVERLAY_GREEN, COLOR_OVERLAY_RED, DRAG_DIRECTION_MODE_DETERMINATION_THRESHOLD, DRAG_THRESHOLD, HANDLE_ROTATE_SNAP_ANGLE, SEGMENT_INSERTION_DISTANCE,
|
||||
SEGMENT_OVERLAY_SIZE, SELECTION_THRESHOLD, SELECTION_TOLERANCE,
|
||||
COLOR_OVERLAY_BLUE, COLOR_OVERLAY_GREEN, COLOR_OVERLAY_RED, DOUBLE_CLICK_MILLISECONDS, DRAG_DIRECTION_MODE_DETERMINATION_THRESHOLD, DRAG_THRESHOLD, HANDLE_ROTATE_SNAP_ANGLE,
|
||||
SEGMENT_INSERTION_DISTANCE, SEGMENT_OVERLAY_SIZE, SELECTION_THRESHOLD, SELECTION_TOLERANCE,
|
||||
};
|
||||
use crate::messages::portfolio::document::overlays::utility_functions::{path_overlays, selected_segments};
|
||||
use crate::messages::portfolio::document::overlays::utility_types::{DrawHandles, OverlayContext};
|
||||
@@ -11,18 +11,20 @@ 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, SelectedPointsInfo, SelectionChange, SelectionShape, SelectionShapeType, ShapeState,
|
||||
ClosestSegment, ManipulatorAngle, OpposingHandleLengths, SelectedLayerState, SelectedPointsInfo, SelectionChange, SelectionShape, SelectionShapeType, ShapeState,
|
||||
};
|
||||
use crate::messages::tool::common_functionality::snapping::{SnapCache, SnapCandidatePoint, SnapConstraint, SnapData, SnapManager};
|
||||
use crate::messages::tool::common_functionality::utility_functions::{calculate_segment_angle, find_two_param_best_approximate};
|
||||
use bezier_rs::{Bezier, TValue};
|
||||
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;
|
||||
|
||||
#[derive(Default)]
|
||||
#[derive(Default, ExtractField)]
|
||||
pub struct PathTool {
|
||||
fsm_state: PathToolFsmState,
|
||||
tool_data: PathToolData,
|
||||
@@ -58,7 +60,10 @@ pub enum PathToolMessage {
|
||||
},
|
||||
Escape,
|
||||
ClosePath,
|
||||
FlipSmoothSharp,
|
||||
DoubleClick {
|
||||
extend_selection: Key,
|
||||
shrink_selection: Key,
|
||||
},
|
||||
GRS {
|
||||
// Should be `Key::KeyG` (Grab), `Key::KeyR` (Rotate), or `Key::KeyS` (Scale)
|
||||
key: Key,
|
||||
@@ -103,6 +108,9 @@ pub enum PathToolMessage {
|
||||
SelectedPointYChanged {
|
||||
new_y: f64,
|
||||
},
|
||||
SetPivot {
|
||||
position: ReferencePoint,
|
||||
},
|
||||
SwapSelectedHandles,
|
||||
UpdateOptions(PathOptionsUpdate),
|
||||
UpdateSelectedPointsStatus {
|
||||
@@ -138,6 +146,9 @@ pub enum PathOptionsUpdate {
|
||||
OverlayModeType(PathOverlayMode),
|
||||
PointEditingMode { enabled: bool },
|
||||
SegmentEditingMode { enabled: bool },
|
||||
PivotGizmoType(PivotGizmoType),
|
||||
TogglePivotGizmoType(bool),
|
||||
TogglePivotPinned,
|
||||
}
|
||||
|
||||
impl ToolMetadata for PathTool {
|
||||
@@ -252,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,
|
||||
@@ -265,13 +290,22 @@ 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,
|
||||
],
|
||||
}]))
|
||||
}
|
||||
}
|
||||
|
||||
#[message_handler_data]
|
||||
impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for PathTool {
|
||||
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) {
|
||||
let updating_point = message == ToolMessage::Path(PathToolMessage::SelectedPointUpdated);
|
||||
@@ -290,6 +324,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);
|
||||
@@ -319,7 +376,7 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for PathToo
|
||||
fn actions(&self) -> ActionList {
|
||||
match self.fsm_state {
|
||||
PathToolFsmState::Ready => actions!(PathToolMessageDiscriminant;
|
||||
FlipSmoothSharp,
|
||||
DoubleClick,
|
||||
MouseDown,
|
||||
Delete,
|
||||
NudgeSelectedPoints,
|
||||
@@ -334,7 +391,7 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for PathToo
|
||||
PathToolFsmState::Dragging(_) => actions!(PathToolMessageDiscriminant;
|
||||
Escape,
|
||||
RightClick,
|
||||
FlipSmoothSharp,
|
||||
DoubleClick,
|
||||
DragStop,
|
||||
PointerMove,
|
||||
Delete,
|
||||
@@ -343,7 +400,7 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for PathToo
|
||||
SwapSelectedHandles,
|
||||
),
|
||||
PathToolFsmState::Drawing { .. } => actions!(PathToolMessageDiscriminant;
|
||||
FlipSmoothSharp,
|
||||
DoubleClick,
|
||||
DragStop,
|
||||
PointerMove,
|
||||
Delete,
|
||||
@@ -359,12 +416,6 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for PathToo
|
||||
Escape,
|
||||
RightClick
|
||||
),
|
||||
PathToolFsmState::MoldingSegment => actions!(PathToolMessageDiscriminant;
|
||||
PointerMove,
|
||||
DragStop,
|
||||
RightClick,
|
||||
Escape,
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -416,7 +467,6 @@ enum PathToolFsmState {
|
||||
selection_shape: SelectionShapeType,
|
||||
},
|
||||
SlidingPoint,
|
||||
MoldingSegment,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
@@ -447,6 +497,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,
|
||||
@@ -462,6 +514,8 @@ struct PathToolData {
|
||||
adjacent_anchor_offset: Option<DVec2>,
|
||||
sliding_point_info: Option<SlidingPointInfo>,
|
||||
started_drawing_from_inside: bool,
|
||||
first_selected_with_single_click: bool,
|
||||
stored_selection: Option<HashMap<LayerNodeIdentifier, SelectedLayerState>>,
|
||||
}
|
||||
|
||||
impl PathToolData {
|
||||
@@ -544,8 +598,9 @@ impl PathToolData {
|
||||
|
||||
self.drag_start_pos = input.mouse.position;
|
||||
|
||||
if !self.saved_points_before_anchor_convert_smooth_sharp.is_empty() && (input.time - self.last_click_time > 500) {
|
||||
if input.time - self.last_click_time > DOUBLE_CLICK_MILLISECONDS {
|
||||
self.saved_points_before_anchor_convert_smooth_sharp.clear();
|
||||
self.stored_selection = None;
|
||||
}
|
||||
|
||||
self.last_click_time = input.time;
|
||||
@@ -675,30 +730,30 @@ impl PathToolData {
|
||||
responses.add(OverlaysMessage::Draw);
|
||||
PathToolFsmState::Dragging(self.dragging_state)
|
||||
} else {
|
||||
let handle1 = ManipulatorPointId::PrimaryHandle(segment.segment());
|
||||
let handle2 = ManipulatorPointId::EndHandle(segment.segment());
|
||||
if let Some(vector_data) = document.network_interface.compute_modified_vector(segment.layer()) {
|
||||
if let (Some(pos1), Some(pos2)) = (handle1.get_position(&vector_data), handle2.get_position(&vector_data)) {
|
||||
self.molding_info = Some((pos1, pos2))
|
||||
}
|
||||
}
|
||||
PathToolFsmState::MoldingSegment
|
||||
let start_pos = segment.bezier().start;
|
||||
let end_pos = segment.bezier().end;
|
||||
|
||||
let [pos1, pos2] = match segment.bezier().handles {
|
||||
BezierHandles::Cubic { handle_start, handle_end } => [handle_start, handle_end],
|
||||
BezierHandles::Quadratic { handle } => [handle, end_pos],
|
||||
BezierHandles::Linear => [start_pos + (end_pos - start_pos) / 3., end_pos + (start_pos - end_pos) / 3.],
|
||||
};
|
||||
self.molding_info = Some((pos1, pos2));
|
||||
PathToolFsmState::Dragging(self.dragging_state)
|
||||
}
|
||||
}
|
||||
// We didn't find a segment, so consider selecting the nearest shape instead and start drawing
|
||||
// If no other layers are selected and this is a single-click, then also select the layer (exception)
|
||||
else if let Some(layer) = document.click(input) {
|
||||
shape_editor.deselect_all_points();
|
||||
shape_editor.deselect_all_segments();
|
||||
if extend_selection {
|
||||
responses.add(NodeGraphMessage::SelectedNodesAdd { nodes: vec![layer.to_node()] });
|
||||
} else {
|
||||
if shape_editor.selected_shape_state.is_empty() {
|
||||
self.first_selected_with_single_click = true;
|
||||
responses.add(NodeGraphMessage::SelectedNodesSet { nodes: vec![layer.to_node()] });
|
||||
}
|
||||
self.drag_start_pos = input.mouse.position;
|
||||
self.previous_mouse_position = document.metadata().document_to_viewport.inverse().transform_point2(input.mouse.position);
|
||||
|
||||
self.started_drawing_from_inside = true;
|
||||
|
||||
self.drag_start_pos = input.mouse.position;
|
||||
self.previous_mouse_position = document.metadata().document_to_viewport.inverse().transform_point2(input.mouse.position);
|
||||
|
||||
let selection_shape = if lasso_select { SelectionShapeType::Lasso } else { SelectionShapeType::Box };
|
||||
PathToolFsmState::Drawing { selection_shape }
|
||||
}
|
||||
@@ -719,7 +774,7 @@ impl PathToolData {
|
||||
let Some(vector_data) = document.network_interface.compute_modified_vector(layer) else {
|
||||
continue;
|
||||
};
|
||||
let transform = document.metadata().transform_to_document(layer);
|
||||
let transform = document.metadata().transform_to_document_if_feeds(layer, &document.network_interface);
|
||||
|
||||
let mut layer_manipulators = HashSet::with_hasher(NoHashBuilder);
|
||||
for point in state.selected_points() {
|
||||
@@ -829,7 +884,7 @@ impl PathToolData {
|
||||
let selected_handle = selection.selected_points().next()?.as_handle()?;
|
||||
let handle_id = selected_handle.to_manipulator_point();
|
||||
|
||||
let layer_to_document = document.metadata().transform_to_document(*layer);
|
||||
let layer_to_document = document.metadata().transform_to_document_if_feeds(*layer, &document.network_interface);
|
||||
let vector_data = document.network_interface.compute_modified_vector(*layer)?;
|
||||
|
||||
let handle_position_local = selected_handle.to_manipulator_point().get_position(&vector_data)?;
|
||||
@@ -871,7 +926,7 @@ impl PathToolData {
|
||||
let anchor = handle_id.get_anchor(&vector_data);
|
||||
let (angle, anchor_position) = calculate_adjacent_anchor_tangent(handle_id, anchor, adjacent_anchor, &vector_data);
|
||||
|
||||
let layer_to_document = document.metadata().transform_to_document(*layer);
|
||||
let layer_to_document = document.metadata().transform_to_document_if_feeds(*layer, &document.network_interface);
|
||||
|
||||
self.adjacent_anchor_offset = handle_id
|
||||
.get_anchor_position(&vector_data)
|
||||
@@ -1018,7 +1073,7 @@ impl PathToolData {
|
||||
}
|
||||
// If already hovering on a segment, then recalculate its closest point
|
||||
else if let Some(closest_segment) = &mut self.segment {
|
||||
closest_segment.update_closest_point(document.metadata(), position);
|
||||
closest_segment.update_closest_point(document.metadata(), &document.network_interface, position);
|
||||
|
||||
if closest_segment.too_far(position, SEGMENT_INSERTION_DISTANCE) {
|
||||
self.segment = None;
|
||||
@@ -1085,7 +1140,7 @@ impl PathToolData {
|
||||
let layer = sliding_point_info.layer;
|
||||
|
||||
let Some(vector_data) = network_interface.compute_modified_vector(layer) else { return };
|
||||
let transform = network_interface.document_metadata().transform_to_viewport(layer);
|
||||
let transform = network_interface.document_metadata().transform_to_viewport_if_feeds(layer, network_interface);
|
||||
let layer_pos = transform.inverse().transform_point2(target_position);
|
||||
|
||||
let segments = sliding_point_info.connected_segments;
|
||||
@@ -1316,6 +1371,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 {
|
||||
@@ -1328,6 +1393,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
|
||||
@@ -1344,6 +1413,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)) => {
|
||||
@@ -1413,7 +1485,7 @@ impl Fsm for PathToolFsmState {
|
||||
|
||||
if let Some(closest_segment) = &tool_data.segment {
|
||||
if tool_options.path_editing_mode.segment_editing_mode {
|
||||
let transform = document.metadata().transform_to_viewport(closest_segment.layer());
|
||||
let transform = document.metadata().transform_to_viewport_if_feeds(closest_segment.layer(), &document.network_interface);
|
||||
|
||||
overlay_context.outline_overlay_bezier(closest_segment.bezier(), transform);
|
||||
|
||||
@@ -1431,7 +1503,7 @@ impl Fsm for PathToolFsmState {
|
||||
}
|
||||
} else {
|
||||
let perp = closest_segment.calculate_perp(document);
|
||||
let point = closest_segment.closest_point(document.metadata());
|
||||
let point = closest_segment.closest_point(document.metadata(), &document.network_interface);
|
||||
|
||||
// Draw an X on the segment
|
||||
if tool_data.delete_segment_pressed {
|
||||
@@ -1501,7 +1573,6 @@ impl Fsm for PathToolFsmState {
|
||||
}
|
||||
}
|
||||
Self::SlidingPoint => {}
|
||||
Self::MoldingSegment => {}
|
||||
}
|
||||
|
||||
responses.add(PathToolMessage::SelectedPointUpdated);
|
||||
@@ -1557,7 +1628,9 @@ impl Fsm for PathToolFsmState {
|
||||
},
|
||||
) => {
|
||||
tool_data.previous_mouse_position = document.metadata().document_to_viewport.inverse().transform_point2(input.mouse.position);
|
||||
|
||||
tool_data.started_drawing_from_inside = false;
|
||||
tool_data.stored_selection = None;
|
||||
|
||||
if selection_shape == SelectionShapeType::Lasso {
|
||||
extend_lasso(&mut tool_data.lasso_polygon, input.mouse.position);
|
||||
@@ -1604,21 +1677,35 @@ impl Fsm for PathToolFsmState {
|
||||
break_colinear_molding,
|
||||
},
|
||||
) => {
|
||||
let mut selected_only_handles = true;
|
||||
|
||||
let selected_points = shape_editor.selected_points();
|
||||
|
||||
for point in selected_points {
|
||||
if matches!(point, ManipulatorPointId::Anchor(_)) {
|
||||
selected_only_handles = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
let selected_only_handles = !shape_editor.selected_points().any(|point| matches!(point, ManipulatorPointId::Anchor(_)));
|
||||
tool_data.stored_selection = None;
|
||||
|
||||
if !tool_data.saved_points_before_handle_drag.is_empty() && (tool_data.drag_start_pos.distance(input.mouse.position) > DRAG_THRESHOLD) && (selected_only_handles) {
|
||||
tool_data.handle_drag_toggle = true;
|
||||
}
|
||||
|
||||
if tool_data.drag_start_pos.distance(input.mouse.position) > DRAG_THRESHOLD {
|
||||
tool_data.molding_segment = true;
|
||||
}
|
||||
|
||||
let break_molding = input.keyboard.get(break_colinear_molding as usize);
|
||||
|
||||
// Logic for molding segment
|
||||
if let Some(segment) = &mut tool_data.segment {
|
||||
if let Some(molding_segment_handles) = tool_data.molding_info {
|
||||
tool_data.temporary_adjacent_handles_while_molding = segment.mold_handle_positions(
|
||||
document,
|
||||
responses,
|
||||
molding_segment_handles,
|
||||
input.mouse.position,
|
||||
break_molding,
|
||||
tool_data.temporary_adjacent_handles_while_molding,
|
||||
);
|
||||
}
|
||||
|
||||
return PathToolFsmState::Dragging(tool_data.dragging_state);
|
||||
}
|
||||
|
||||
let anchor_and_handle_toggled = input.keyboard.get(move_anchor_with_handles as usize);
|
||||
let initial_press = anchor_and_handle_toggled && !tool_data.select_anchor_toggled;
|
||||
let released_from_toggle = tool_data.select_anchor_toggled && !anchor_and_handle_toggled;
|
||||
@@ -1694,39 +1781,11 @@ impl Fsm for PathToolFsmState {
|
||||
tool_data.slide_point(input.mouse.position, responses, &document.network_interface, shape_editor);
|
||||
PathToolFsmState::SlidingPoint
|
||||
}
|
||||
(PathToolFsmState::MoldingSegment, PathToolMessage::PointerMove { break_colinear_molding, .. }) => {
|
||||
if tool_data.drag_start_pos.distance(input.mouse.position) > DRAG_THRESHOLD {
|
||||
tool_data.molding_segment = true;
|
||||
}
|
||||
|
||||
let break_colinear_molding = input.keyboard.get(break_colinear_molding as usize);
|
||||
|
||||
// Logic for molding segment
|
||||
if let Some(segment) = &mut tool_data.segment {
|
||||
if let Some(molding_segment_handles) = tool_data.molding_info {
|
||||
tool_data.temporary_adjacent_handles_while_molding = segment.mold_handle_positions(
|
||||
document,
|
||||
responses,
|
||||
molding_segment_handles,
|
||||
input.mouse.position,
|
||||
break_colinear_molding,
|
||||
tool_data.temporary_adjacent_handles_while_molding,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
PathToolFsmState::MoldingSegment
|
||||
}
|
||||
(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);
|
||||
|
||||
@@ -1847,6 +1906,9 @@ impl Fsm for PathToolFsmState {
|
||||
tool_data.saved_points_before_handle_drag.clear();
|
||||
tool_data.handle_drag_toggle = false;
|
||||
}
|
||||
tool_data.molding_info = None;
|
||||
tool_data.molding_segment = false;
|
||||
tool_data.temporary_adjacent_handles_while_molding = None;
|
||||
tool_data.angle_locked = false;
|
||||
responses.add(DocumentMessage::AbortTransaction);
|
||||
tool_data.snap_manager.cleanup(responses);
|
||||
@@ -1864,17 +1926,6 @@ impl Fsm for PathToolFsmState {
|
||||
|
||||
PathToolFsmState::Ready
|
||||
}
|
||||
(PathToolFsmState::MoldingSegment, PathToolMessage::Escape | PathToolMessage::RightClick) => {
|
||||
// Undo the molding and go back to the state before
|
||||
tool_data.molding_info = None;
|
||||
tool_data.molding_segment = false;
|
||||
tool_data.temporary_adjacent_handles_while_molding = None;
|
||||
|
||||
responses.add(DocumentMessage::AbortTransaction);
|
||||
tool_data.snap_manager.cleanup(responses);
|
||||
|
||||
PathToolFsmState::Ready
|
||||
}
|
||||
// Mouse up
|
||||
(PathToolFsmState::Drawing { selection_shape }, PathToolMessage::DragStop { extend_selection, shrink_selection }) => {
|
||||
let extend_selection = input.keyboard.get(extend_selection as usize);
|
||||
@@ -1895,12 +1946,16 @@ impl Fsm for PathToolFsmState {
|
||||
SelectionMode::Directional => tool_data.calculate_selection_mode_from_direction(document.metadata()),
|
||||
selection_mode => selection_mode,
|
||||
};
|
||||
tool_data.started_drawing_from_inside = false;
|
||||
|
||||
if tool_data.drag_start_pos.distance(previous_mouse) < 1e-8 {
|
||||
// If click happens inside of a shape then don't set selected nodes to empty
|
||||
if document.click(input).is_none() {
|
||||
responses.add(NodeGraphMessage::SelectedNodesSet { nodes: vec![] });
|
||||
// Clicked inside or outside the shape then deselect all of the points/segments
|
||||
if document.click(input).is_some() && tool_data.stored_selection.is_none() {
|
||||
tool_data.stored_selection = Some(shape_editor.selected_shape_state.clone());
|
||||
}
|
||||
|
||||
shape_editor.deselect_all_points();
|
||||
shape_editor.deselect_all_segments();
|
||||
} else {
|
||||
match selection_shape {
|
||||
SelectionShapeType::Box => {
|
||||
@@ -2072,8 +2127,8 @@ impl Fsm for PathToolFsmState {
|
||||
shape_editor.delete_point_and_break_path(document, responses);
|
||||
PathToolFsmState::Ready
|
||||
}
|
||||
(_, PathToolMessage::FlipSmoothSharp) => {
|
||||
// Double-clicked on a point
|
||||
(_, PathToolMessage::DoubleClick { extend_selection, shrink_selection }) => {
|
||||
// Double-clicked on a point (flip smooth/sharp behavior)
|
||||
let nearest_point = shape_editor.find_nearest_point_indices(&document.network_interface, input.mouse.position, SELECTION_THRESHOLD);
|
||||
if nearest_point.is_some() {
|
||||
// Flip the selected point between smooth and sharp
|
||||
@@ -2090,13 +2145,70 @@ impl Fsm for PathToolFsmState {
|
||||
|
||||
return PathToolFsmState::Ready;
|
||||
}
|
||||
|
||||
// Double-clicked on a filled region
|
||||
if let Some(layer) = document.click(input) {
|
||||
// Select all points in the layer
|
||||
shape_editor.select_connected_anchors(document, layer, input.mouse.position);
|
||||
else if let Some(layer) = document.click(input) {
|
||||
let extend_selection = input.keyboard.get(extend_selection as usize);
|
||||
let shrink_selection = input.keyboard.get(shrink_selection as usize);
|
||||
|
||||
if shape_editor.is_selected_layer(layer) {
|
||||
if extend_selection && !tool_data.first_selected_with_single_click {
|
||||
responses.add(NodeGraphMessage::SelectedNodesRemove { nodes: vec![layer.to_node()] });
|
||||
|
||||
if let Some(selection) = &tool_data.stored_selection {
|
||||
let mut selection = selection.clone();
|
||||
selection.remove(&layer);
|
||||
shape_editor.selected_shape_state = selection;
|
||||
tool_data.stored_selection = None;
|
||||
}
|
||||
} else if shrink_selection && !tool_data.first_selected_with_single_click {
|
||||
// Only deselect all the points of the double clicked layer
|
||||
if let Some(selection) = &tool_data.stored_selection {
|
||||
let selection = selection.clone();
|
||||
shape_editor.selected_shape_state = selection;
|
||||
tool_data.stored_selection = None;
|
||||
}
|
||||
|
||||
let state = shape_editor.selected_shape_state.get_mut(&layer).expect("No state for selected layer");
|
||||
state.deselect_all_points_in_layer();
|
||||
state.deselect_all_segments_in_layer();
|
||||
} else if !tool_data.first_selected_with_single_click {
|
||||
// Select according to the selected editing mode
|
||||
let point_editing_mode = tool_options.path_editing_mode.point_editing_mode;
|
||||
let segment_editing_mode = tool_options.path_editing_mode.segment_editing_mode;
|
||||
shape_editor.select_connected(document, layer, input.mouse.position, point_editing_mode, segment_editing_mode);
|
||||
|
||||
// Select all the other layers back again
|
||||
if let Some(selection) = &tool_data.stored_selection {
|
||||
let mut selection = selection.clone();
|
||||
selection.remove(&layer);
|
||||
|
||||
for (layer, state) in selection {
|
||||
shape_editor.selected_shape_state.insert(layer, state);
|
||||
}
|
||||
tool_data.stored_selection = None;
|
||||
}
|
||||
}
|
||||
|
||||
// If it was the very first click without there being an existing selection,
|
||||
// then the single-click behavior and double-click behavior should not collide
|
||||
tool_data.first_selected_with_single_click = false;
|
||||
} else if extend_selection {
|
||||
responses.add(NodeGraphMessage::SelectedNodesAdd { nodes: vec![layer.to_node()] });
|
||||
|
||||
if let Some(selection) = &tool_data.stored_selection {
|
||||
shape_editor.selected_shape_state = selection.clone();
|
||||
tool_data.stored_selection = None;
|
||||
}
|
||||
} else {
|
||||
responses.add(NodeGraphMessage::SelectedNodesSet { nodes: vec![layer.to_node()] });
|
||||
}
|
||||
|
||||
responses.add(OverlaysMessage::Draw);
|
||||
}
|
||||
// Double clicked on the background
|
||||
else {
|
||||
responses.add(NodeGraphMessage::SelectedNodesSet { nodes: vec![] });
|
||||
}
|
||||
|
||||
PathToolFsmState::Ready
|
||||
}
|
||||
@@ -2163,6 +2275,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,
|
||||
}
|
||||
}
|
||||
@@ -2235,7 +2359,10 @@ fn get_selection_status(network_interface: &NodeNetworkInterface, shape_state: &
|
||||
return SelectionStatus::None;
|
||||
};
|
||||
|
||||
let coordinates = network_interface.document_metadata().transform_to_document(layer).transform_point2(local_position);
|
||||
let coordinates = network_interface
|
||||
.document_metadata()
|
||||
.transform_to_document_if_feeds(layer, network_interface)
|
||||
.transform_point2(local_position);
|
||||
let manipulator_angle = if vector_data.colinear(point) { ManipulatorAngle::Colinear } else { ManipulatorAngle::Free };
|
||||
|
||||
return SelectionStatus::One(SingleSelectedPoint {
|
||||
@@ -2529,7 +2656,40 @@ fn update_dynamic_hints(
|
||||
dragging_hint_data.0.push(HintGroup(hold_group));
|
||||
}
|
||||
|
||||
dragging_hint_data
|
||||
if tool_data.molding_segment {
|
||||
let mut has_colinear_anchors = false;
|
||||
|
||||
if let Some(segment) = &tool_data.segment {
|
||||
let handle1 = HandleId::primary(segment.segment());
|
||||
let handle2 = HandleId::end(segment.segment());
|
||||
|
||||
if let Some(vector_data) = document.network_interface.compute_modified_vector(segment.layer()) {
|
||||
let other_handle1 = vector_data.other_colinear_handle(handle1);
|
||||
let other_handle2 = vector_data.other_colinear_handle(handle2);
|
||||
if other_handle1.is_some() || other_handle2.is_some() {
|
||||
has_colinear_anchors = true;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
let handles_stored = if let Some(other_handles) = tool_data.temporary_adjacent_handles_while_molding {
|
||||
other_handles[0].is_some() || other_handles[1].is_some()
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
let molding_disable_possible = has_colinear_anchors || handles_stored;
|
||||
|
||||
let mut molding_hints = vec![HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, ""), HintInfo::keys([Key::Escape], "Cancel").prepend_slash()])];
|
||||
|
||||
if molding_disable_possible {
|
||||
molding_hints.push(HintGroup(vec![HintInfo::keys([Key::Alt], "Break Colinear Handles")]));
|
||||
}
|
||||
|
||||
HintData(molding_hints)
|
||||
} else {
|
||||
dragging_hint_data
|
||||
}
|
||||
}
|
||||
PathToolFsmState::Drawing { .. } => HintData(vec![
|
||||
HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, ""), HintInfo::keys([Key::Escape], "Cancel").prepend_slash()]),
|
||||
@@ -2539,38 +2699,6 @@ fn update_dynamic_hints(
|
||||
HintInfo::keys([Key::Alt], "Subtract").prepend_plus(),
|
||||
]),
|
||||
]),
|
||||
PathToolFsmState::MoldingSegment => {
|
||||
let mut has_colinear_anchors = false;
|
||||
|
||||
if let Some(segment) = &tool_data.segment {
|
||||
let handle1 = HandleId::primary(segment.segment());
|
||||
let handle2 = HandleId::end(segment.segment());
|
||||
|
||||
if let Some(vector_data) = document.network_interface.compute_modified_vector(segment.layer()) {
|
||||
let other_handle1 = vector_data.other_colinear_handle(handle1);
|
||||
let other_handle2 = vector_data.other_colinear_handle(handle2);
|
||||
if other_handle1.is_some() || other_handle2.is_some() {
|
||||
has_colinear_anchors = true;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
let handles_stored = if let Some(other_handles) = tool_data.temporary_adjacent_handles_while_molding {
|
||||
other_handles[0].is_some() || other_handles[1].is_some()
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
let molding_disable_possible = has_colinear_anchors || handles_stored;
|
||||
|
||||
let mut molding_hints = vec![HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, ""), HintInfo::keys([Key::Escape], "Cancel").prepend_slash()])];
|
||||
|
||||
if molding_disable_possible {
|
||||
molding_hints.push(HintGroup(vec![HintInfo::keys([Key::Alt], "Break Colinear Handles")]));
|
||||
}
|
||||
|
||||
HintData(molding_hints)
|
||||
}
|
||||
PathToolFsmState::SlidingPoint => HintData(vec![HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, ""), HintInfo::keys([Key::Escape], "Cancel").prepend_slash()])]),
|
||||
};
|
||||
responses.add(FrontendMessage::UpdateInputHints { hint_data });
|
||||
|
||||
@@ -17,7 +17,7 @@ use graphene_std::Color;
|
||||
use graphene_std::vector::{HandleId, ManipulatorPointId, NoHashBuilder, SegmentId, StrokeId, VectorData};
|
||||
use graphene_std::vector::{PointId, VectorModificationType};
|
||||
|
||||
#[derive(Default)]
|
||||
#[derive(Default, ExtractField)]
|
||||
pub struct PenTool {
|
||||
fsm_state: PenToolFsmState,
|
||||
tool_data: PenToolData,
|
||||
@@ -186,6 +186,7 @@ impl LayoutHolder for PenTool {
|
||||
}
|
||||
}
|
||||
|
||||
#[message_handler_data]
|
||||
impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for PenTool {
|
||||
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) {
|
||||
let ToolMessage::Pen(PenToolMessage::UpdateOptions(action)) = message else {
|
||||
|
||||
@@ -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::*;
|
||||
@@ -28,7 +29,7 @@ use graphene_std::renderer::Rect;
|
||||
use graphene_std::transform::ReferencePoint;
|
||||
use std::fmt;
|
||||
|
||||
#[derive(Default)]
|
||||
#[derive(Default, ExtractField)]
|
||||
pub struct SelectTool {
|
||||
fsm_state: SelectToolFsmState,
|
||||
tool_data: SelectToolData,
|
||||
@@ -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;
|
||||
@@ -242,16 +272,46 @@ impl LayoutHolder for SelectTool {
|
||||
}
|
||||
}
|
||||
|
||||
#[message_handler_data]
|
||||
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 +383,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 +392,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 +560,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 +791,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 +973,15 @@ impl Fsm for SelectToolFsmState {
|
||||
(SelectionShapeType::Lasso, _) => overlay_context.polygon(polygon, None, fill_color),
|
||||
}
|
||||
}
|
||||
|
||||
if let Self::Dragging { .. } = self {
|
||||
let quad = Quad::from_box([tool_data.drag_start, tool_data.drag_current]);
|
||||
let document_start = document.metadata().document_to_viewport.inverse().transform_point2(quad.top_left());
|
||||
let document_current = document.metadata().document_to_viewport.inverse().transform_point2(quad.bottom_right());
|
||||
|
||||
overlay_context.translation_box(document_current - document_start, quad, None);
|
||||
}
|
||||
|
||||
self
|
||||
}
|
||||
(_, SelectToolMessage::EditLayer) => {
|
||||
@@ -868,7 +1013,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 +1029,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 +1060,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 +1086,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 +1106,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 +1252,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 +1300,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 +1440,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 +1601,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 +1867,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 {
|
||||
|
||||
@@ -578,7 +578,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) {
|
||||
|
||||
@@ -14,7 +14,7 @@ use graph_craft::document::{NodeId, NodeInput};
|
||||
use graphene_std::Color;
|
||||
use graphene_std::vector::{PointId, SegmentId, VectorModificationType};
|
||||
|
||||
#[derive(Default)]
|
||||
#[derive(Default, ExtractField)]
|
||||
pub struct SplineTool {
|
||||
fsm_state: SplineToolFsmState,
|
||||
tool_data: SplineToolData,
|
||||
@@ -123,6 +123,7 @@ impl LayoutHolder for SplineTool {
|
||||
}
|
||||
}
|
||||
|
||||
#[message_handler_data]
|
||||
impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for SplineTool {
|
||||
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) {
|
||||
let ToolMessage::Spline(SplineToolMessage::UpdateOptions(action)) = message else {
|
||||
|
||||
@@ -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::*;
|
||||
@@ -21,7 +20,7 @@ use graphene_std::renderer::Quad;
|
||||
use graphene_std::text::{Font, FontCache, TypesettingConfig, lines_clipping, load_font};
|
||||
use graphene_std::vector::style::Fill;
|
||||
|
||||
#[derive(Default)]
|
||||
#[derive(Default, ExtractField)]
|
||||
pub struct TextTool {
|
||||
fsm_state: TextToolFsmState,
|
||||
tool_data: TextToolData,
|
||||
@@ -171,6 +170,7 @@ impl LayoutHolder for TextTool {
|
||||
}
|
||||
}
|
||||
|
||||
#[message_handler_data]
|
||||
impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for TextTool {
|
||||
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) {
|
||||
let ToolMessage::Text(TextToolMessage::UpdateOptions(action)) = message else {
|
||||
@@ -283,7 +283,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 +525,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();
|
||||
|
||||
@@ -2,6 +2,7 @@ use crate::messages::input_mapper::utility_types::input_keyboard::Key;
|
||||
use crate::messages::portfolio::document::overlays::utility_types::OverlayContext;
|
||||
use crate::messages::portfolio::document::utility_types::transformation::TransformType;
|
||||
use crate::messages::prelude::*;
|
||||
use crate::messages::tool::common_functionality::pivot::PivotGizmo;
|
||||
use glam::DVec2;
|
||||
|
||||
#[impl_message(Message, ToolMessage, TransformLayer)]
|
||||
@@ -29,4 +30,5 @@ pub enum TransformLayerMessage {
|
||||
TypeDecimalPoint,
|
||||
TypeDigit { digit: u8 },
|
||||
TypeNegate,
|
||||
SetPivotGizmo { pivot_gizmo: PivotGizmo },
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ use crate::messages::portfolio::document::utility_types::document_metadata::Laye
|
||||
use crate::messages::portfolio::document::utility_types::misc::PTZ;
|
||||
use crate::messages::portfolio::document::utility_types::transformation::{Axis, OriginalTransforms, Selected, TransformOperation, TransformType, Typing};
|
||||
use crate::messages::prelude::*;
|
||||
use crate::messages::tool::common_functionality::pivot::{PivotGizmo, PivotGizmoType};
|
||||
use crate::messages::tool::common_functionality::shape_editor::ShapeState;
|
||||
use crate::messages::tool::tool_messages::tool_prelude::Key;
|
||||
use crate::messages::tool::utility_types::{ToolData, ToolType};
|
||||
@@ -20,7 +21,7 @@ const TRANSFORM_GRS_OVERLAY_PROVIDER: OverlayProvider = |context| TransformLayer
|
||||
const SLOW_KEY: Key = Key::Shift;
|
||||
const INCREMENTS_KEY: Key = Key::Control;
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
#[derive(Debug, Clone, Default, ExtractField)]
|
||||
pub struct TransformLayerMessageHandler {
|
||||
pub transform_operation: TransformOperation,
|
||||
|
||||
@@ -34,8 +35,11 @@ pub struct TransformLayerMessageHandler {
|
||||
start_mouse: ViewportPosition,
|
||||
|
||||
original_transforms: OriginalTransforms,
|
||||
pivot_gizmo: PivotGizmo,
|
||||
pivot: ViewportPosition,
|
||||
|
||||
path_bounds: Option<[DVec2; 2]>,
|
||||
|
||||
local_pivot: DocumentPosition,
|
||||
local_mouse_start: DocumentPosition,
|
||||
grab_target: DocumentPosition,
|
||||
@@ -61,27 +65,64 @@ impl TransformLayerMessageHandler {
|
||||
}
|
||||
}
|
||||
|
||||
fn calculate_pivot(selected_points: &Vec<&ManipulatorPointId>, vector_data: &VectorData, viewspace: DAffine2, get_location: impl Fn(&ManipulatorPointId) -> Option<DVec2>) -> Option<(DVec2, DVec2)> {
|
||||
fn calculate_pivot(
|
||||
document: &DocumentMessageHandler,
|
||||
selected_points: &Vec<&ManipulatorPointId>,
|
||||
vector_data: &VectorData,
|
||||
viewspace: DAffine2,
|
||||
get_location: impl Fn(&ManipulatorPointId) -> Option<DVec2>,
|
||||
gizmo: &mut PivotGizmo,
|
||||
) -> (Option<(DVec2, DVec2)>, Option<[DVec2; 2]>) {
|
||||
let average_position = || {
|
||||
let mut point_count = 0_usize;
|
||||
selected_points.iter().filter_map(|p| get_location(p)).inspect(|_| point_count += 1).sum::<DVec2>() / point_count as f64
|
||||
};
|
||||
let bounds = selected_points.iter().filter_map(|p| get_location(p)).fold(None, |acc: Option<[DVec2; 2]>, point| {
|
||||
if let Some([mut min, mut max]) = acc {
|
||||
min.x = min.x.min(point.x);
|
||||
min.y = min.y.min(point.y);
|
||||
max.x = max.x.max(point.x);
|
||||
max.y = max.y.max(point.y);
|
||||
Some([min, max])
|
||||
} else {
|
||||
Some([point, point])
|
||||
}
|
||||
});
|
||||
gizmo.pivot.recalculate_pivot_for_layer(document, bounds);
|
||||
let position = || {
|
||||
(if !gizmo.state.disabled {
|
||||
match gizmo.state.gizmo_type {
|
||||
PivotGizmoType::Average => None,
|
||||
PivotGizmoType::Active => gizmo.point.and_then(|p| get_location(&p)),
|
||||
PivotGizmoType::Pivot => gizmo.pivot.pivot,
|
||||
}
|
||||
} else {
|
||||
None
|
||||
})
|
||||
.unwrap_or_else(average_position)
|
||||
};
|
||||
let [point] = selected_points.as_slice() else {
|
||||
// Handle the case where there are multiple points
|
||||
let mut point_count = 0;
|
||||
let average_position = selected_points.iter().filter_map(|p| get_location(p)).inspect(|_| point_count += 1).sum::<DVec2>() / point_count as f64;
|
||||
|
||||
return Some((average_position, average_position));
|
||||
let position = position();
|
||||
return (Some((position, position)), bounds);
|
||||
};
|
||||
|
||||
match point {
|
||||
ManipulatorPointId::PrimaryHandle(_) | ManipulatorPointId::EndHandle(_) => {
|
||||
// Get the anchor position and transform it to the pivot
|
||||
let pivot_pos = point.get_anchor_position(vector_data).map(|anchor_position| viewspace.transform_point2(anchor_position))?;
|
||||
let target = viewspace.transform_point2(point.get_position(vector_data)?);
|
||||
Some((pivot_pos, target))
|
||||
let (Some(pivot_position), Some(position)) = (
|
||||
point.get_anchor_position(vector_data).map(|anchor_position| viewspace.transform_point2(anchor_position)),
|
||||
point.get_position(vector_data),
|
||||
) else {
|
||||
return (None, None);
|
||||
};
|
||||
let target = viewspace.transform_point2(position);
|
||||
(Some((pivot_position, target)), None)
|
||||
}
|
||||
_ => {
|
||||
// Calculate the average position of all selected points
|
||||
let mut point_count = 0;
|
||||
let average_position = selected_points.iter().filter_map(|p| get_location(p)).inspect(|_| point_count += 1).sum::<DVec2>() / point_count as f64;
|
||||
Some((average_position, average_position))
|
||||
let position = position();
|
||||
(Some((position, position)), bounds)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -134,6 +175,26 @@ fn update_colinear_handles(selected_layers: &[LayerNodeIdentifier], document: &D
|
||||
}
|
||||
|
||||
type TransformData<'a> = (&'a DocumentMessageHandler, &'a InputPreprocessorMessageHandler, &'a ToolData, &'a mut ShapeState);
|
||||
|
||||
pub fn custom_data() -> MessageData {
|
||||
MessageData::new(
|
||||
String::from("TransformData<'a>"),
|
||||
// TODO: When <https://github.com/dtolnay/proc-macro2/issues/503> is resolved and released,
|
||||
// TODO: use <https://doc.rust-lang.org/stable/proc_macro/struct.Span.html#method.line> to get
|
||||
// TODO: the line number instead of hardcoding it to the magic number on the following lines
|
||||
// TODO: which points to the line of the `type TransformData<'a> = ...` definition above.
|
||||
// TODO: Also, utilize the line number in the actual output, since it is currently unused.
|
||||
vec![
|
||||
(String::from("&'a DocumentMessageHandler"), 177),
|
||||
(String::from("&'a InputPreprocessorMessageHandler"), 177),
|
||||
(String::from("&'a ToolData"), 177),
|
||||
(String::from("&'a mut ShapeState"), 177),
|
||||
],
|
||||
file!(),
|
||||
)
|
||||
}
|
||||
|
||||
#[message_handler_data(CustomData)]
|
||||
impl MessageHandler<TransformLayerMessage, TransformData<'_>> for TransformLayerMessageHandler {
|
||||
fn process_message(&mut self, message: TransformLayerMessage, responses: &mut VecDeque<Message>, (document, input, tool_data, shape_editor): TransformData) {
|
||||
let using_path_tool = tool_data.active_tool_type == ToolType::Path;
|
||||
@@ -177,18 +238,17 @@ impl MessageHandler<TransformLayerMessage, TransformData<'_>> for TransformLayer
|
||||
}
|
||||
|
||||
if !using_path_tool || !using_shape_tool {
|
||||
*selected.pivot = selected.mean_average_of_pivots();
|
||||
self.pivot_gizmo.recalculate_transform(document);
|
||||
*selected.pivot = self.pivot_gizmo.position(document);
|
||||
self.local_pivot = document.metadata().document_to_viewport.inverse().transform_point2(*selected.pivot);
|
||||
self.grab_target = document.metadata().document_to_viewport.inverse().transform_point2(selected.mean_average_of_pivots());
|
||||
self.grab_target = self.local_pivot;
|
||||
}
|
||||
// Here vector data from all layers is not considered which can be a problem in pivot calculation
|
||||
else if let Some(vector_data) = selected_layers.first().and_then(|&layer| document.network_interface.compute_modified_vector(layer)) {
|
||||
*selected.original_transforms = OriginalTransforms::default();
|
||||
|
||||
let viewspace = document.metadata().transform_to_viewport(selected_layers[0]);
|
||||
|
||||
let selected_segments = shape_editor.selected_segments().collect::<HashSet<_>>();
|
||||
|
||||
let mut affected_points = shape_editor.selected_points().copied().collect::<Vec<_>>();
|
||||
|
||||
for (segment_id, _, start, end) in vector_data.segment_bezier_iter() {
|
||||
@@ -201,8 +261,16 @@ impl MessageHandler<TransformLayerMessage, TransformData<'_>> for TransformLayer
|
||||
let affected_point_refs = affected_points.iter().collect();
|
||||
|
||||
let get_location = |point: &&ManipulatorPointId| point.get_position(&vector_data).map(|position| viewspace.transform_point2(position));
|
||||
if let Some((new_pivot, grab_target)) = calculate_pivot(&affected_point_refs, &vector_data, viewspace, |point: &ManipulatorPointId| get_location(&point)) {
|
||||
if let (Some((new_pivot, grab_target)), bounds) = calculate_pivot(
|
||||
document,
|
||||
&affected_point_refs,
|
||||
&vector_data,
|
||||
viewspace,
|
||||
|point: &ManipulatorPointId| get_location(&point),
|
||||
&mut self.pivot_gizmo,
|
||||
) {
|
||||
*selected.pivot = new_pivot;
|
||||
self.path_bounds = bounds;
|
||||
|
||||
self.local_pivot = document_to_viewport.inverse().transform_point2(*selected.pivot);
|
||||
self.grab_target = document_to_viewport.inverse().transform_point2(grab_target);
|
||||
@@ -228,116 +296,93 @@ impl MessageHandler<TransformLayerMessage, TransformData<'_>> for TransformLayer
|
||||
return;
|
||||
}
|
||||
|
||||
for layer in document.metadata().all_layers() {
|
||||
if !document.network_interface.is_artboard(&layer.to_node(), &[]) {
|
||||
continue;
|
||||
};
|
||||
let viewport_box = input.viewport_bounds.size();
|
||||
let axis_constraint = self.transform_operation.axis_constraint();
|
||||
|
||||
let viewport_box = input.viewport_bounds.size();
|
||||
let axis_constraint = self.transform_operation.axis_constraint();
|
||||
let format_rounded = |value: f64, precision: usize| {
|
||||
if self.typing.digits.is_empty() || !self.transform_operation.can_begin_typing() {
|
||||
format!("{:.*}", precision, value).trim_end_matches('0').trim_end_matches('.').to_string()
|
||||
} else {
|
||||
self.typing.string.clone()
|
||||
}
|
||||
};
|
||||
|
||||
let format_rounded = |value: f64, precision: usize| {
|
||||
if self.typing.digits.is_empty() || !self.transform_operation.can_begin_typing() {
|
||||
format!("{:.*}", precision, value).trim_end_matches('0').trim_end_matches('.').to_string()
|
||||
// TODO: Ensure removing this and adding this doesn't change the position of layers under PTZ ops
|
||||
// responses.add(TransformLayerMessage::PointerMove {
|
||||
// slow_key: SLOW_KEY,
|
||||
// increments_key: INCREMENTS_KEY,
|
||||
// });
|
||||
|
||||
match self.transform_operation {
|
||||
TransformOperation::None => (),
|
||||
TransformOperation::Grabbing(translation) => {
|
||||
let translation = translation.to_dvec(self.initial_transform, self.increments);
|
||||
let viewport_translate = document_to_viewport.transform_vector2(translation);
|
||||
let pivot = document_to_viewport.transform_point2(self.grab_target);
|
||||
let quad = Quad::from_box([pivot, pivot + viewport_translate]);
|
||||
|
||||
responses.add(SelectToolMessage::PivotShift {
|
||||
offset: Some(viewport_translate),
|
||||
flush: false,
|
||||
});
|
||||
|
||||
let typed_string = (!self.typing.digits.is_empty() && self.transform_operation.can_begin_typing()).then(|| self.typing.string.clone());
|
||||
overlay_context.translation_box(translation, quad, typed_string);
|
||||
}
|
||||
TransformOperation::Scaling(scale) => {
|
||||
let scale = scale.to_f64(self.increments);
|
||||
let text = format!("{}x", format_rounded(scale, 3));
|
||||
let pivot = document_to_viewport.transform_point2(self.local_pivot);
|
||||
let start_mouse = document_to_viewport.transform_point2(self.local_mouse_start);
|
||||
let local_edge = start_mouse - pivot;
|
||||
let local_edge = project_edge_to_quad(local_edge, &self.layer_bounding_box, self.local, axis_constraint);
|
||||
let boundary_point = pivot + local_edge * scale.min(1.);
|
||||
let end_point = pivot + local_edge * scale.max(1.);
|
||||
|
||||
if scale > 0. {
|
||||
overlay_context.dashed_line(pivot, boundary_point, None, None, Some(2.), Some(2.), Some(0.5));
|
||||
}
|
||||
overlay_context.line(boundary_point, end_point, None, None);
|
||||
|
||||
let transform = DAffine2::from_translation(boundary_point.midpoint(pivot) + local_edge.perp().normalize_or(DVec2::X) * local_edge.element_product().signum() * 24.);
|
||||
overlay_context.text(&text, COLOR_OVERLAY_BLUE, None, transform, 16., [Pivot::Middle, Pivot::Middle]);
|
||||
}
|
||||
TransformOperation::Rotating(rotation) => {
|
||||
let angle = rotation.to_f64(self.increments);
|
||||
let pivot = document_to_viewport.transform_point2(self.local_pivot);
|
||||
let start_mouse = document_to_viewport.transform_point2(self.local_mouse_start);
|
||||
let offset_angle = if self.grs_pen_handle {
|
||||
self.handle - self.last_point
|
||||
} else if using_path_tool {
|
||||
start_mouse - pivot
|
||||
} else {
|
||||
self.typing.string.clone()
|
||||
}
|
||||
};
|
||||
|
||||
// TODO: Ensure removing this and adding this doesn't change the position of layers under PTZ ops
|
||||
// responses.add(TransformLayerMessage::PointerMove {
|
||||
// slow_key: SLOW_KEY,
|
||||
// increments_key: INCREMENTS_KEY,
|
||||
// });
|
||||
|
||||
match self.transform_operation {
|
||||
TransformOperation::None => (),
|
||||
TransformOperation::Grabbing(translation) => {
|
||||
let translation = translation.to_dvec(self.initial_transform, self.increments);
|
||||
let viewport_translate = document_to_viewport.transform_vector2(translation);
|
||||
let pivot = document_to_viewport.transform_point2(self.grab_target);
|
||||
let quad = Quad::from_box([pivot, pivot + viewport_translate]).0;
|
||||
let e1 = (self.layer_bounding_box.0[1] - self.layer_bounding_box.0[0]).normalize_or(DVec2::X);
|
||||
|
||||
if matches!(axis_constraint, Axis::Both | Axis::X) && translation.x != 0. {
|
||||
let end = if self.local { (quad[1] - quad[0]).rotate(e1) + quad[0] } else { quad[1] };
|
||||
overlay_context.dashed_line(quad[0], end, None, None, Some(2.), Some(2.), Some(0.5));
|
||||
|
||||
let x_transform = DAffine2::from_translation((quad[0] + end) / 2.);
|
||||
overlay_context.text(&format_rounded(translation.x, 3), COLOR_OVERLAY_BLUE, None, x_transform, 4., [Pivot::Middle, Pivot::End]);
|
||||
}
|
||||
|
||||
if matches!(axis_constraint, Axis::Both | Axis::Y) && translation.y != 0. {
|
||||
let end = if self.local { (quad[3] - quad[0]).rotate(e1) + quad[0] } else { quad[3] };
|
||||
overlay_context.dashed_line(quad[0], end, None, None, Some(2.), Some(2.), Some(0.5));
|
||||
let x_parameter = viewport_translate.x.clamp(-1., 1.);
|
||||
let y_transform = DAffine2::from_translation((quad[0] + end) / 2. + x_parameter * DVec2::X * 0.);
|
||||
let pivot_selection = if x_parameter >= -1e-3 { Pivot::Start } else { Pivot::End };
|
||||
if axis_constraint != Axis::Both || self.typing.digits.is_empty() || !self.transform_operation.can_begin_typing() {
|
||||
overlay_context.text(&format_rounded(translation.y, 2), COLOR_OVERLAY_BLUE, None, y_transform, 3., [pivot_selection, Pivot::Middle]);
|
||||
}
|
||||
}
|
||||
|
||||
if matches!(axis_constraint, Axis::Both) && translation.x != 0. && translation.y != 0. {
|
||||
overlay_context.line(quad[1], quad[2], None, None);
|
||||
overlay_context.line(quad[3], quad[2], None, None);
|
||||
}
|
||||
}
|
||||
TransformOperation::Scaling(scale) => {
|
||||
let scale = scale.to_f64(self.increments);
|
||||
let text = format!("{}x", format_rounded(scale, 3));
|
||||
let pivot = document_to_viewport.transform_point2(self.local_pivot);
|
||||
let start_mouse = document_to_viewport.transform_point2(self.local_mouse_start);
|
||||
let local_edge = start_mouse - pivot;
|
||||
let local_edge = project_edge_to_quad(local_edge, &self.layer_bounding_box, self.local, axis_constraint);
|
||||
let boundary_point = pivot + local_edge * scale.min(1.);
|
||||
let end_point = pivot + local_edge * scale.max(1.);
|
||||
|
||||
if scale > 0. {
|
||||
overlay_context.dashed_line(pivot, boundary_point, None, None, Some(2.), Some(2.), Some(0.5));
|
||||
}
|
||||
overlay_context.line(boundary_point, end_point, None, None);
|
||||
|
||||
let transform = DAffine2::from_translation(boundary_point.midpoint(pivot) + local_edge.perp().normalize_or(DVec2::X) * local_edge.element_product().signum() * 24.);
|
||||
overlay_context.text(&text, COLOR_OVERLAY_BLUE, None, transform, 16., [Pivot::Middle, Pivot::Middle]);
|
||||
}
|
||||
TransformOperation::Rotating(rotation) => {
|
||||
let angle = rotation.to_f64(self.increments);
|
||||
let pivot = document_to_viewport.transform_point2(self.local_pivot);
|
||||
let start_mouse = document_to_viewport.transform_point2(self.local_mouse_start);
|
||||
let offset_angle = if self.grs_pen_handle {
|
||||
self.handle - self.last_point
|
||||
} else if using_path_tool {
|
||||
start_mouse - pivot
|
||||
} else {
|
||||
self.layer_bounding_box.top_right() - self.layer_bounding_box.top_right()
|
||||
};
|
||||
let tilt_offset = document.document_ptz.unmodified_tilt();
|
||||
let offset_angle = offset_angle.to_angle() + tilt_offset;
|
||||
let width = viewport_box.max_element();
|
||||
let radius = start_mouse.distance(pivot);
|
||||
let arc_radius = ANGLE_MEASURE_RADIUS_FACTOR * width;
|
||||
let radius = radius.clamp(ARC_MEASURE_RADIUS_FACTOR_RANGE.0 * width, ARC_MEASURE_RADIUS_FACTOR_RANGE.1 * width);
|
||||
let angle_in_degrees = angle.to_degrees();
|
||||
let display_angle = if angle_in_degrees.is_sign_positive() {
|
||||
angle_in_degrees - (angle_in_degrees / 360.).floor() * 360.
|
||||
} else if angle_in_degrees.is_sign_negative() {
|
||||
angle_in_degrees - ((angle_in_degrees / 360.).floor() + 1.) * 360.
|
||||
} else {
|
||||
angle_in_degrees
|
||||
};
|
||||
let text = format!("{}°", format_rounded(display_angle, 2));
|
||||
let text_texture_width = overlay_context.get_width(&text) / 2.;
|
||||
let text_texture_height = 12.;
|
||||
let text_angle_on_unit_circle = DVec2::from_angle((angle % TAU) / 2. + offset_angle);
|
||||
let text_texture_position = DVec2::new(
|
||||
(arc_radius + 4. + text_texture_width) * text_angle_on_unit_circle.x,
|
||||
(arc_radius + text_texture_height) * text_angle_on_unit_circle.y,
|
||||
);
|
||||
let transform = DAffine2::from_translation(text_texture_position + pivot);
|
||||
overlay_context.draw_angle(pivot, radius, arc_radius, offset_angle, angle);
|
||||
overlay_context.text(&text, COLOR_OVERLAY_BLUE, None, transform, 16., [Pivot::Middle, Pivot::Middle]);
|
||||
}
|
||||
self.layer_bounding_box.top_right() - self.layer_bounding_box.top_right()
|
||||
};
|
||||
let tilt_offset = document.document_ptz.unmodified_tilt();
|
||||
let offset_angle = offset_angle.to_angle() + tilt_offset;
|
||||
let width = viewport_box.max_element();
|
||||
let radius = start_mouse.distance(pivot);
|
||||
let arc_radius = ANGLE_MEASURE_RADIUS_FACTOR * width;
|
||||
let radius = radius.clamp(ARC_MEASURE_RADIUS_FACTOR_RANGE.0 * width, ARC_MEASURE_RADIUS_FACTOR_RANGE.1 * width);
|
||||
let angle_in_degrees = angle.to_degrees();
|
||||
let display_angle = if angle_in_degrees.is_sign_positive() {
|
||||
angle_in_degrees - (angle_in_degrees / 360.).floor() * 360.
|
||||
} else if angle_in_degrees.is_sign_negative() {
|
||||
angle_in_degrees - ((angle_in_degrees / 360.).floor() + 1.) * 360.
|
||||
} else {
|
||||
angle_in_degrees
|
||||
};
|
||||
let text = format!("{}°", format_rounded(display_angle, 2));
|
||||
let text_texture_width = overlay_context.get_width(&text) / 2.;
|
||||
let text_texture_height = 12.;
|
||||
let text_angle_on_unit_circle = DVec2::from_angle((angle % TAU) / 2. + offset_angle);
|
||||
let text_texture_position = DVec2::new(
|
||||
(arc_radius + 4. + text_texture_width) * text_angle_on_unit_circle.x,
|
||||
(arc_radius + text_texture_height) * text_angle_on_unit_circle.y,
|
||||
);
|
||||
let transform = DAffine2::from_translation(text_texture_position + pivot);
|
||||
overlay_context.draw_angle(pivot, radius, arc_radius, offset_angle, angle);
|
||||
overlay_context.text(&text, COLOR_OVERLAY_BLUE, None, transform, 16., [Pivot::Middle, Pivot::Middle]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -364,6 +409,8 @@ impl MessageHandler<TransformLayerMessage, TransformData<'_>> for TransformLayer
|
||||
responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||
}
|
||||
|
||||
responses.add(SelectToolMessage::PivotShift { offset: None, flush: true });
|
||||
|
||||
if final_transform {
|
||||
responses.add(OverlaysMessage::RemoveProvider(TRANSFORM_GRS_OVERLAY_PROVIDER));
|
||||
}
|
||||
@@ -487,6 +534,7 @@ impl MessageHandler<TransformLayerMessage, TransformData<'_>> for TransformLayer
|
||||
responses.add(ToolMessage::UpdateHints);
|
||||
}
|
||||
|
||||
responses.add(SelectToolMessage::PivotShift { offset: None, flush: false });
|
||||
responses.add(OverlaysMessage::RemoveProvider(TRANSFORM_GRS_OVERLAY_PROVIDER));
|
||||
}
|
||||
TransformLayerMessage::ConstrainX => {
|
||||
@@ -694,6 +742,9 @@ impl MessageHandler<TransformLayerMessage, TransformData<'_>> for TransformLayer
|
||||
self.initial_transform,
|
||||
)
|
||||
}
|
||||
TransformLayerMessage::SetPivotGizmo { pivot_gizmo } => {
|
||||
self.pivot_gizmo = pivot_gizmo;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ use graphene_std::text::FontCache;
|
||||
use std::borrow::Cow;
|
||||
use std::fmt::{self, Debug};
|
||||
|
||||
#[derive(ExtractField)]
|
||||
pub struct ToolActionHandlerData<'a> {
|
||||
pub document: &'a mut DocumentMessageHandler,
|
||||
pub document_id: DocumentId,
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
use crate::messages::prelude::*;
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
#[derive(Debug, Clone, Default, ExtractField)]
|
||||
pub struct WorkspaceMessageHandler {
|
||||
node_graph_visible: bool,
|
||||
}
|
||||
|
||||
#[message_handler_data]
|
||||
impl MessageHandler<WorkspaceMessage, ()> for WorkspaceMessageHandler {
|
||||
fn process_message(&mut self, message: WorkspaceMessage, _responses: &mut VecDeque<Message>, _data: ()) {
|
||||
match message {
|
||||
|
||||
@@ -413,6 +413,7 @@ mod test {
|
||||
use super::*;
|
||||
use crate::messages::portfolio::document::utility_types::network_interface::NodeNetworkInterface;
|
||||
use crate::test_utils::test_prelude::{self, NodeGraphLayer};
|
||||
use graph_craft::ProtoNodeIdentifier;
|
||||
use graph_craft::document::NodeNetwork;
|
||||
use graphene_std::Context;
|
||||
use graphene_std::NodeInputDecleration;
|
||||
@@ -422,7 +423,7 @@ mod test {
|
||||
/// Stores all of the monitor nodes that have been attached to a graph
|
||||
#[derive(Default)]
|
||||
pub struct Instrumented {
|
||||
protonodes_by_name: HashMap<String, Vec<Vec<Vec<NodeId>>>>,
|
||||
protonodes_by_name: HashMap<ProtoNodeIdentifier, Vec<Vec<Vec<NodeId>>>>,
|
||||
protonodes_by_path: HashMap<Vec<NodeId>, Vec<Vec<NodeId>>>,
|
||||
}
|
||||
|
||||
@@ -449,7 +450,7 @@ mod test {
|
||||
}
|
||||
if let DocumentNodeImplementation::ProtoNode(identifier) = &mut node.implementation {
|
||||
path.push(*id);
|
||||
self.protonodes_by_name.entry(identifier.name.to_string()).or_default().push(monitor_node_ids.clone());
|
||||
self.protonodes_by_name.entry(identifier.clone()).or_default().push(monitor_node_ids.clone());
|
||||
self.protonodes_by_path.insert(path.clone(), monitor_node_ids);
|
||||
path.pop();
|
||||
}
|
||||
@@ -457,7 +458,7 @@ mod test {
|
||||
for (input, monitor_id) in monitor_nodes {
|
||||
let monitor_node = DocumentNode {
|
||||
inputs: vec![input],
|
||||
implementation: DocumentNodeImplementation::proto("graphene_core::memo::MonitorNode"),
|
||||
implementation: DocumentNodeImplementation::ProtoNode(graphene_std::memo::monitor::IDENTIFIER),
|
||||
manual_composition: Some(graph_craft::generic!(T)),
|
||||
skip_deduplication: true,
|
||||
..Default::default()
|
||||
@@ -495,7 +496,7 @@ mod test {
|
||||
Input::Result: Send + Sync + Clone + 'static,
|
||||
{
|
||||
self.protonodes_by_name
|
||||
.get(Input::identifier())
|
||||
.get(&Input::identifier())
|
||||
.map_or([].as_slice(), |x| x.as_slice())
|
||||
.iter()
|
||||
.filter_map(|inputs| inputs.get(Input::INDEX))
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
use super::*;
|
||||
use crate::messages::frontend::utility_types::{ExportBounds, FileType};
|
||||
use glam::{DAffine2, DVec2};
|
||||
use graph_craft::concrete;
|
||||
use graph_craft::document::value::TaggedValue;
|
||||
use graph_craft::document::{NodeId, NodeNetwork};
|
||||
use graph_craft::graphene_compiler::Compiler;
|
||||
use graph_craft::proto::GraphErrors;
|
||||
use graph_craft::wasm_application_io::EditorPreferences;
|
||||
use graph_craft::{ProtoNodeIdentifier, concrete};
|
||||
use graphene_std::Context;
|
||||
use graphene_std::application_io::{NodeGraphUpdateMessage, NodeGraphUpdateSender, RenderConfig};
|
||||
use graphene_std::instances::Instance;
|
||||
@@ -46,7 +46,7 @@ pub struct NodeRuntime {
|
||||
inspect_state: Option<InspectState>,
|
||||
|
||||
/// Mapping of the fully-qualified node paths to their preprocessor substitutions.
|
||||
substitutions: HashMap<String, DocumentNode>,
|
||||
substitutions: HashMap<ProtoNodeIdentifier, DocumentNode>,
|
||||
|
||||
// TODO: Remove, it doesn't need to be persisted anymore
|
||||
/// The current renders of the thumbnails for layer nodes.
|
||||
@@ -435,7 +435,7 @@ impl InspectState {
|
||||
|
||||
let monitor_node = DocumentNode {
|
||||
inputs: vec![NodeInput::node(inspect_node, 0)], // Connect to the primary output of the inspect node
|
||||
implementation: DocumentNodeImplementation::proto("graphene_core::memo::MonitorNode"),
|
||||
implementation: DocumentNodeImplementation::ProtoNode(graphene_std::memo::monitor::IDENTIFIER),
|
||||
manual_composition: Some(graph_craft::generic!(T)),
|
||||
skip_deduplication: true,
|
||||
..Default::default()
|
||||
|
||||
@@ -172,9 +172,10 @@ impl EditorTestUtils {
|
||||
pub fn get_node<'a, T: InputAccessor<'a, DocumentNode>>(&'a self) -> impl Iterator<Item = T> + 'a {
|
||||
self.active_document()
|
||||
.network_interface
|
||||
.iter_recursive()
|
||||
.inspect(|node| println!("{:#?}", node.1.implementation))
|
||||
.filter_map(move |(_, document)| T::new_with_source(document))
|
||||
.document_network()
|
||||
.recursive_nodes()
|
||||
.inspect(|(_, node, _)| println!("{:#?}", node.implementation))
|
||||
.filter_map(move |(_, document, _)| T::new_with_source(document))
|
||||
}
|
||||
|
||||
pub async fn move_mouse(&mut self, x: f64, y: f64, modifier_keys: ModifierKeys, mouse_keys: MouseKeys) {
|
||||
@@ -300,7 +301,7 @@ pub trait FrontendMessageTestUtils {
|
||||
|
||||
impl FrontendMessageTestUtils for FrontendMessage {
|
||||
fn check_node_graph_error(&self) {
|
||||
let FrontendMessage::UpdateNodeGraph { nodes, .. } = self else { return };
|
||||
let FrontendMessage::UpdateNodeGraphNodes { nodes, .. } = self else { return };
|
||||
|
||||
for node in nodes {
|
||||
if let Some(error) = &node.errors {
|
||||
|
||||
@@ -45,3 +45,19 @@ pub trait TransitiveChild: Into<Self::Parent> + Into<Self::TopParent> {
|
||||
pub trait Hint {
|
||||
fn hints(&self) -> HashMap<String, String>;
|
||||
}
|
||||
|
||||
pub trait HierarchicalTree {
|
||||
fn build_message_tree() -> DebugMessageTree;
|
||||
|
||||
fn message_handler_data_str() -> MessageData {
|
||||
MessageData::new(String::new(), Vec::new(), "")
|
||||
}
|
||||
|
||||
fn message_handler_str() -> MessageData {
|
||||
MessageData::new(String::new(), Vec::new(), "")
|
||||
}
|
||||
|
||||
fn path() -> &'static str {
|
||||
""
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
#[derive(Debug)]
|
||||
pub struct MessageData {
|
||||
name: String,
|
||||
fields: Vec<(String, usize)>,
|
||||
path: &'static str,
|
||||
}
|
||||
|
||||
impl MessageData {
|
||||
pub fn new(name: String, fields: Vec<(String, usize)>, path: &'static str) -> MessageData {
|
||||
MessageData { name, fields, path }
|
||||
}
|
||||
|
||||
pub fn name(&self) -> &str {
|
||||
&self.name
|
||||
}
|
||||
|
||||
pub fn fields(&self) -> &Vec<(String, usize)> {
|
||||
&self.fields
|
||||
}
|
||||
|
||||
pub fn path(&self) -> &'static str {
|
||||
self.path
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct DebugMessageTree {
|
||||
name: String,
|
||||
variants: Option<Vec<DebugMessageTree>>,
|
||||
message_handler: Option<MessageData>,
|
||||
message_handler_data: Option<MessageData>,
|
||||
path: &'static str,
|
||||
}
|
||||
|
||||
impl DebugMessageTree {
|
||||
pub fn new(name: &str) -> DebugMessageTree {
|
||||
DebugMessageTree {
|
||||
name: name.to_string(),
|
||||
variants: None,
|
||||
message_handler: None,
|
||||
message_handler_data: None,
|
||||
path: "",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_path(&mut self, path: &'static str) {
|
||||
self.path = path;
|
||||
}
|
||||
|
||||
pub fn add_variant(&mut self, variant: DebugMessageTree) {
|
||||
if let Some(variants) = &mut self.variants {
|
||||
variants.push(variant);
|
||||
} else {
|
||||
self.variants = Some(vec![variant]);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_message_handler_data_field(&mut self, message_handler_data: MessageData) {
|
||||
self.message_handler_data = Some(message_handler_data);
|
||||
}
|
||||
|
||||
pub fn add_message_handler_field(&mut self, message_handler: MessageData) {
|
||||
self.message_handler = Some(message_handler);
|
||||
}
|
||||
|
||||
pub fn name(&self) -> &str {
|
||||
&self.name
|
||||
}
|
||||
|
||||
pub fn path(&self) -> &'static str {
|
||||
self.path
|
||||
}
|
||||
|
||||
pub fn variants(&self) -> Option<&Vec<DebugMessageTree>> {
|
||||
self.variants.as_ref()
|
||||
}
|
||||
|
||||
pub fn message_handler_data_fields(&self) -> Option<&MessageData> {
|
||||
self.message_handler_data.as_ref()
|
||||
}
|
||||
|
||||
pub fn message_handler_fields(&self) -> Option<&MessageData> {
|
||||
self.message_handler.as_ref()
|
||||
}
|
||||
|
||||
pub fn has_message_handler_data_fields(&self) -> bool {
|
||||
match self.message_handler_data_fields() {
|
||||
Some(_) => true,
|
||||
None => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn has_message_handler_fields(&self) -> bool {
|
||||
match self.message_handler_fields() {
|
||||
Some(_) => true,
|
||||
None => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user