mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-24 18:08:12 +08:00
Merge branch 'master' into merge_point
This commit is contained in:
@@ -106,6 +106,7 @@ pub const HIDE_HANDLE_DISTANCE: f64 = 3.;
|
||||
pub const HANDLE_ROTATE_SNAP_ANGLE: f64 = 15.;
|
||||
pub const SEGMENT_INSERTION_DISTANCE: f64 = 5.;
|
||||
pub const SEGMENT_OVERLAY_SIZE: f64 = 10.;
|
||||
pub const SEGMENT_SELECTED_THICKNESS: f64 = 3.;
|
||||
pub const HANDLE_LENGTH_FACTOR: f64 = 0.5;
|
||||
|
||||
// PEN TOOL
|
||||
@@ -152,7 +153,7 @@ pub const COLOR_OVERLAY_BLACK_75: &str = "#000000bf";
|
||||
pub const DEFAULT_DOCUMENT_NAME: &str = "Untitled Document";
|
||||
pub const FILE_SAVE_SUFFIX: &str = ".graphite";
|
||||
pub const MAX_UNDO_HISTORY_LEN: usize = 100; // TODO: Add this to user preferences
|
||||
pub const AUTO_SAVE_TIMEOUT_SECONDS: u64 = 15;
|
||||
pub const AUTO_SAVE_TIMEOUT_SECONDS: u64 = 1;
|
||||
|
||||
// INPUT
|
||||
pub const DOUBLE_CLICK_MILLISECONDS: u64 = 500;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use crate::messages::debug::utility_types::MessageLoggingVerbosity;
|
||||
use crate::messages::defer::DeferMessageContext;
|
||||
use crate::messages::dialog::DialogMessageContext;
|
||||
use crate::messages::layout::layout_message_handler::LayoutMessageContext;
|
||||
use crate::messages::prelude::*;
|
||||
@@ -133,12 +134,16 @@ impl Dispatcher {
|
||||
self.message_handlers.debug_message_handler.process_message(message, &mut queue, ());
|
||||
}
|
||||
Message::Defer(message) => {
|
||||
self.message_handlers.defer_message_handler.process_message(message, &mut queue, ());
|
||||
let context = DeferMessageContext {
|
||||
portfolio: &self.message_handlers.portfolio_message_handler,
|
||||
};
|
||||
self.message_handlers.defer_message_handler.process_message(message, &mut queue, context);
|
||||
}
|
||||
Message::Dialog(message) => {
|
||||
let context = DialogMessageContext {
|
||||
portfolio: &self.message_handlers.portfolio_message_handler,
|
||||
preferences: &self.message_handlers.preferences_message_handler,
|
||||
viewport_bounds: &self.message_handlers.input_preprocessor_message_handler.viewport_bounds,
|
||||
};
|
||||
self.message_handlers.dialog_message_handler.process_message(message, &mut queue, context);
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ use crate::messages::prelude::*;
|
||||
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]
|
||||
pub enum DeferMessage {
|
||||
SetGraphSubmissionIndex(u64),
|
||||
TriggerGraphRun(u64),
|
||||
TriggerGraphRun(u64, DocumentId),
|
||||
AfterGraphRun { messages: Vec<Message> },
|
||||
TriggerNavigationReady,
|
||||
AfterNavigationReady { messages: Vec<Message> },
|
||||
|
||||
@@ -1,18 +1,25 @@
|
||||
use crate::messages::prelude::*;
|
||||
|
||||
#[derive(ExtractField)]
|
||||
pub struct DeferMessageContext<'a> {
|
||||
pub portfolio: &'a PortfolioMessageHandler,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, ExtractField)]
|
||||
pub struct DeferMessageHandler {
|
||||
after_graph_run: Vec<(u64, Message)>,
|
||||
after_graph_run: HashMap<DocumentId, Vec<(u64, Message)>>,
|
||||
after_viewport_resize: Vec<Message>,
|
||||
current_graph_submission_id: u64,
|
||||
}
|
||||
|
||||
#[message_handler_data]
|
||||
impl MessageHandler<DeferMessage, ()> for DeferMessageHandler {
|
||||
fn process_message(&mut self, message: DeferMessage, responses: &mut VecDeque<Message>, _: ()) {
|
||||
impl MessageHandler<DeferMessage, DeferMessageContext<'_>> for DeferMessageHandler {
|
||||
fn process_message(&mut self, message: DeferMessage, responses: &mut VecDeque<Message>, context: DeferMessageContext) {
|
||||
match message {
|
||||
DeferMessage::AfterGraphRun { mut messages } => {
|
||||
self.after_graph_run.extend(messages.drain(..).map(|m| (self.current_graph_submission_id, m)));
|
||||
let after_graph_run = self.after_graph_run.entry(context.portfolio.active_document_id.unwrap_or(DocumentId(0))).or_default();
|
||||
after_graph_run.extend(messages.drain(..).map(|m| (self.current_graph_submission_id, m)));
|
||||
responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||
}
|
||||
DeferMessage::AfterNavigationReady { messages } => {
|
||||
self.after_viewport_resize.extend_from_slice(&messages);
|
||||
@@ -20,16 +27,22 @@ impl MessageHandler<DeferMessage, ()> for DeferMessageHandler {
|
||||
DeferMessage::SetGraphSubmissionIndex(execution_id) => {
|
||||
self.current_graph_submission_id = execution_id + 1;
|
||||
}
|
||||
DeferMessage::TriggerGraphRun(execution_id) => {
|
||||
if self.after_graph_run.is_empty() {
|
||||
DeferMessage::TriggerGraphRun(execution_id, document_id) => {
|
||||
let after_graph_run = self.after_graph_run.entry(document_id).or_default();
|
||||
if after_graph_run.is_empty() {
|
||||
return;
|
||||
}
|
||||
// Find the index of the last message we can process
|
||||
let num_elements_to_remove = self.after_graph_run.binary_search_by_key(&(execution_id + 1), |x| x.0).unwrap_or_else(|pos| pos - 1);
|
||||
let elements = self.after_graph_run.drain(0..=num_elements_to_remove);
|
||||
let split = after_graph_run.partition_point(|&(id, _)| id <= execution_id);
|
||||
let elements = after_graph_run.drain(..split);
|
||||
for (_, message) in elements.rev() {
|
||||
responses.add_front(message);
|
||||
}
|
||||
for (id, messages) in self.after_graph_run.iter() {
|
||||
if !messages.is_empty() {
|
||||
responses.add(PortfolioMessage::SubmitGraphRender { document_id: *id, ignore_hash: false });
|
||||
}
|
||||
}
|
||||
}
|
||||
DeferMessage::TriggerNavigationReady => {
|
||||
for message in self.after_viewport_resize.drain(..).rev() {
|
||||
|
||||
@@ -4,4 +4,4 @@ mod defer_message_handler;
|
||||
#[doc(inline)]
|
||||
pub use defer_message::{DeferMessage, DeferMessageDiscriminant};
|
||||
#[doc(inline)]
|
||||
pub use defer_message_handler::DeferMessageHandler;
|
||||
pub use defer_message_handler::{DeferMessageContext, DeferMessageHandler};
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
use super::new_document_dialog::NewDocumentDialogMessageContext;
|
||||
use super::simple_dialogs::{self, AboutGraphiteDialog, ComingSoonDialog, DemoArtworkDialog, LicensesDialog};
|
||||
use crate::messages::input_mapper::utility_types::input_mouse::ViewportBounds;
|
||||
use crate::messages::layout::utility_types::widget_prelude::*;
|
||||
use crate::messages::prelude::*;
|
||||
|
||||
#[derive(ExtractField)]
|
||||
pub struct DialogMessageContext<'a> {
|
||||
pub portfolio: &'a PortfolioMessageHandler,
|
||||
pub viewport_bounds: &'a ViewportBounds,
|
||||
pub preferences: &'a PreferencesMessageHandler,
|
||||
}
|
||||
|
||||
@@ -19,11 +22,15 @@ pub struct DialogMessageHandler {
|
||||
#[message_handler_data]
|
||||
impl MessageHandler<DialogMessage, DialogMessageContext<'_>> for DialogMessageHandler {
|
||||
fn process_message(&mut self, message: DialogMessage, responses: &mut VecDeque<Message>, context: DialogMessageContext) {
|
||||
let DialogMessageContext { portfolio, preferences } = context;
|
||||
let DialogMessageContext {
|
||||
portfolio,
|
||||
preferences,
|
||||
viewport_bounds,
|
||||
} = context;
|
||||
|
||||
match message {
|
||||
DialogMessage::ExportDialog(message) => self.export_dialog.process_message(message, responses, ExportDialogMessageContext { portfolio }),
|
||||
DialogMessage::NewDocumentDialog(message) => self.new_document_dialog.process_message(message, responses, ()),
|
||||
DialogMessage::NewDocumentDialog(message) => self.new_document_dialog.process_message(message, responses, NewDocumentDialogMessageContext { viewport_bounds }),
|
||||
DialogMessage::PreferencesDialog(message) => self.preferences_dialog.process_message(message, responses, PreferencesDialogMessageContext { preferences }),
|
||||
|
||||
DialogMessage::CloseAllDocumentsWithConfirmation => {
|
||||
|
||||
@@ -111,19 +111,19 @@ impl LayoutHolder for ExportDialogMessageHandler {
|
||||
(ExportBounds::Selection, "Selection".to_string(), !self.has_selection),
|
||||
];
|
||||
let artboards = self.artboards.iter().map(|(&layer, name)| (ExportBounds::Artboard(layer), name.to_string(), false)).collect();
|
||||
let groups = [standard_bounds, artboards];
|
||||
let choices = [standard_bounds, artboards];
|
||||
|
||||
let current_bounds = if !self.has_selection && self.bounds == ExportBounds::Selection {
|
||||
ExportBounds::AllArtwork
|
||||
} else {
|
||||
self.bounds
|
||||
};
|
||||
let index = groups.iter().flatten().position(|(bounds, _, _)| *bounds == current_bounds).unwrap();
|
||||
let index = choices.iter().flatten().position(|(bounds, _, _)| *bounds == current_bounds).unwrap();
|
||||
|
||||
let mut entries = groups
|
||||
let mut entries = choices
|
||||
.into_iter()
|
||||
.map(|group| {
|
||||
group
|
||||
.map(|choice| {
|
||||
choice
|
||||
.into_iter()
|
||||
.map(|(val, name, disabled)| {
|
||||
MenuListEntry::new(format!("{val:?}"))
|
||||
@@ -145,14 +145,14 @@ impl LayoutHolder for ExportDialogMessageHandler {
|
||||
DropdownInput::new(entries).selected_index(Some(index as u32)).widget_holder(),
|
||||
];
|
||||
|
||||
let mut checkbox_id = CheckboxId::default();
|
||||
let checkbox_id = CheckboxId::new();
|
||||
let transparent_background = vec![
|
||||
TextLabel::new("Transparency").table_align(true).min_width(100).for_checkbox(&mut checkbox_id).widget_holder(),
|
||||
TextLabel::new("Transparency").table_align(true).min_width(100).for_checkbox(checkbox_id).widget_holder(),
|
||||
Separator::new(SeparatorType::Unrelated).widget_holder(),
|
||||
CheckboxInput::new(self.transparent_background)
|
||||
.disabled(self.file_type == FileType::Jpg)
|
||||
.on_update(move |value: &CheckboxInput| ExportDialogMessage::TransparentBackground(value.checked).into())
|
||||
.for_label(checkbox_id.clone())
|
||||
.for_label(checkbox_id)
|
||||
.widget_holder(),
|
||||
];
|
||||
|
||||
|
||||
@@ -4,4 +4,4 @@ mod new_document_dialog_message_handler;
|
||||
#[doc(inline)]
|
||||
pub use new_document_dialog_message::{NewDocumentDialogMessage, NewDocumentDialogMessageDiscriminant};
|
||||
#[doc(inline)]
|
||||
pub use new_document_dialog_message_handler::NewDocumentDialogMessageHandler;
|
||||
pub use new_document_dialog_message_handler::{NewDocumentDialogMessageContext, NewDocumentDialogMessageHandler};
|
||||
|
||||
+22
-15
@@ -1,8 +1,13 @@
|
||||
use crate::messages::layout::utility_types::widget_prelude::*;
|
||||
use crate::messages::prelude::*;
|
||||
use crate::messages::{input_mapper::utility_types::input_mouse::ViewportBounds, layout::utility_types::widget_prelude::*};
|
||||
use glam::{IVec2, UVec2};
|
||||
use graph_craft::document::NodeId;
|
||||
|
||||
#[derive(ExtractField)]
|
||||
pub struct NewDocumentDialogMessageContext<'a> {
|
||||
pub viewport_bounds: &'a ViewportBounds,
|
||||
}
|
||||
|
||||
/// A dialog to allow users to set some initial options about a new document.
|
||||
#[derive(Debug, Clone, Default, ExtractField)]
|
||||
pub struct NewDocumentDialogMessageHandler {
|
||||
@@ -12,8 +17,8 @@ pub struct NewDocumentDialogMessageHandler {
|
||||
}
|
||||
|
||||
#[message_handler_data]
|
||||
impl MessageHandler<NewDocumentDialogMessage, ()> for NewDocumentDialogMessageHandler {
|
||||
fn process_message(&mut self, message: NewDocumentDialogMessage, responses: &mut VecDeque<Message>, _: ()) {
|
||||
impl<'a> MessageHandler<NewDocumentDialogMessage, NewDocumentDialogMessageContext<'a>> for NewDocumentDialogMessageHandler {
|
||||
fn process_message(&mut self, message: NewDocumentDialogMessage, responses: &mut VecDeque<Message>, context: NewDocumentDialogMessageContext<'a>) {
|
||||
match message {
|
||||
NewDocumentDialogMessage::Name(name) => self.name = name,
|
||||
NewDocumentDialogMessage::Infinite(infinite) => self.infinite = infinite,
|
||||
@@ -24,16 +29,18 @@ impl MessageHandler<NewDocumentDialogMessage, ()> for NewDocumentDialogMessageHa
|
||||
|
||||
let create_artboard = !self.infinite && self.dimensions.x > 0 && self.dimensions.y > 0;
|
||||
if create_artboard {
|
||||
responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||
responses.add(DeferMessage::AfterGraphRun {
|
||||
messages: vec![
|
||||
GraphOperationMessage::NewArtboard {
|
||||
id: NodeId::new(),
|
||||
artboard: graphene_std::Artboard::new(IVec2::ZERO, self.dimensions.as_ivec2()),
|
||||
}
|
||||
.into(),
|
||||
],
|
||||
responses.add(GraphOperationMessage::NewArtboard {
|
||||
id: NodeId::new(),
|
||||
artboard: graphene_std::Artboard::new(IVec2::ZERO, self.dimensions.as_ivec2()),
|
||||
});
|
||||
responses.add(NavigationMessage::CanvasPan { delta: self.dimensions.as_dvec2() });
|
||||
responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||
// If we already have bounds, we won't receive a viewport bounds update so we just fabricate one ourselves
|
||||
if *context.viewport_bounds != ViewportBounds::default() {
|
||||
responses.add(InputPreprocessorMessage::BoundsOfViewports {
|
||||
bounds_of_viewports: vec![context.viewport_bounds.clone()],
|
||||
});
|
||||
}
|
||||
responses.add(DeferMessage::AfterNavigationReady {
|
||||
messages: vec![DocumentMessage::ZoomCanvasToFitAll.into(), DocumentMessage::DeselectAllLayers.into()],
|
||||
});
|
||||
@@ -80,13 +87,13 @@ impl LayoutHolder for NewDocumentDialogMessageHandler {
|
||||
.widget_holder(),
|
||||
];
|
||||
|
||||
let mut checkbox_id = CheckboxId::default();
|
||||
let checkbox_id = CheckboxId::new();
|
||||
let infinite = vec![
|
||||
TextLabel::new("Infinite Canvas").table_align(true).min_width(90).for_checkbox(&mut checkbox_id).widget_holder(),
|
||||
TextLabel::new("Infinite Canvas").table_align(true).min_width(90).for_checkbox(checkbox_id).widget_holder(),
|
||||
Separator::new(SeparatorType::Unrelated).widget_holder(),
|
||||
CheckboxInput::new(self.infinite)
|
||||
.on_update(|checkbox_input: &CheckboxInput| NewDocumentDialogMessage::Infinite(checkbox_input.checked).into())
|
||||
.for_label(checkbox_id.clone())
|
||||
.for_label(checkbox_id)
|
||||
.widget_holder(),
|
||||
];
|
||||
|
||||
|
||||
+9
-13
@@ -68,7 +68,7 @@ impl PreferencesDialogMessageHandler {
|
||||
.widget_holder(),
|
||||
];
|
||||
|
||||
let mut checkbox_id = CheckboxId::default();
|
||||
let checkbox_id = CheckboxId::new();
|
||||
let zoom_with_scroll_tooltip = "Use the scroll wheel for zooming instead of vertically panning (not recommended for trackpads)";
|
||||
let zoom_with_scroll = vec![
|
||||
Separator::new(SeparatorType::Unrelated).widget_holder(),
|
||||
@@ -81,12 +81,12 @@ impl PreferencesDialogMessageHandler {
|
||||
}
|
||||
.into()
|
||||
})
|
||||
.for_label(checkbox_id.clone())
|
||||
.for_label(checkbox_id)
|
||||
.widget_holder(),
|
||||
TextLabel::new("Zoom with Scroll")
|
||||
.table_align(true)
|
||||
.tooltip(zoom_with_scroll_tooltip)
|
||||
.for_checkbox(&mut checkbox_id)
|
||||
.for_checkbox(checkbox_id)
|
||||
.widget_holder(),
|
||||
];
|
||||
|
||||
@@ -169,7 +169,7 @@ impl PreferencesDialogMessageHandler {
|
||||
graph_wire_style,
|
||||
];
|
||||
|
||||
let mut checkbox_id = CheckboxId::default();
|
||||
let checkbox_id = CheckboxId::new();
|
||||
let vello_tooltip = "Use the experimental Vello renderer (your browser must support WebGPU)";
|
||||
let use_vello = vec![
|
||||
Separator::new(SeparatorType::Unrelated).widget_holder(),
|
||||
@@ -178,17 +178,17 @@ impl PreferencesDialogMessageHandler {
|
||||
.tooltip(vello_tooltip)
|
||||
.disabled(!preferences.supports_wgpu())
|
||||
.on_update(|checkbox_input: &CheckboxInput| PreferencesMessage::UseVello { use_vello: checkbox_input.checked }.into())
|
||||
.for_label(checkbox_id.clone())
|
||||
.for_label(checkbox_id)
|
||||
.widget_holder(),
|
||||
TextLabel::new("Vello Renderer")
|
||||
.table_align(true)
|
||||
.tooltip(vello_tooltip)
|
||||
.disabled(!preferences.supports_wgpu())
|
||||
.for_checkbox(&mut checkbox_id)
|
||||
.for_checkbox(checkbox_id)
|
||||
.widget_holder(),
|
||||
];
|
||||
|
||||
let mut checkbox_id = CheckboxId::default();
|
||||
let checkbox_id = CheckboxId::new();
|
||||
let vector_mesh_tooltip =
|
||||
"Allow tools to produce vector meshes, where more than two segments can connect to an anchor point.\n\nCurrently this does not properly handle stroke joins and fills.";
|
||||
let vector_meshes = vec![
|
||||
@@ -197,13 +197,9 @@ impl PreferencesDialogMessageHandler {
|
||||
CheckboxInput::new(preferences.vector_meshes)
|
||||
.tooltip(vector_mesh_tooltip)
|
||||
.on_update(|checkbox_input: &CheckboxInput| PreferencesMessage::VectorMeshes { enabled: checkbox_input.checked }.into())
|
||||
.for_label(checkbox_id.clone())
|
||||
.widget_holder(),
|
||||
TextLabel::new("Vector Meshes")
|
||||
.table_align(true)
|
||||
.tooltip(vector_mesh_tooltip)
|
||||
.for_checkbox(&mut checkbox_id)
|
||||
.for_label(checkbox_id)
|
||||
.widget_holder(),
|
||||
TextLabel::new("Vector Meshes").table_align(true).tooltip(vector_mesh_tooltip).for_checkbox(checkbox_id).widget_holder(),
|
||||
];
|
||||
|
||||
Layout::WidgetLayout(WidgetLayout::new(vec![
|
||||
|
||||
@@ -12,9 +12,14 @@ use graph_craft::document::NodeId;
|
||||
use graphene_std::raster::Image;
|
||||
use graphene_std::raster::color::Color;
|
||||
use graphene_std::text::{Font, TextAlign};
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
use crate::messages::portfolio::document::overlays::utility_types::OverlayContext;
|
||||
|
||||
#[impl_message(Message, Frontend)]
|
||||
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize, specta::Type)]
|
||||
#[derive(derivative::Derivative, Clone, serde::Serialize, serde::Deserialize, specta::Type)]
|
||||
#[derivative(Debug, PartialEq)]
|
||||
pub enum FrontendMessage {
|
||||
// Display prefix: make the frontend show something, like a dialog
|
||||
DisplayDialog {
|
||||
@@ -59,16 +64,22 @@ pub enum FrontendMessage {
|
||||
#[serde(rename = "commitDate")]
|
||||
commit_date: String,
|
||||
},
|
||||
TriggerDownloadImage {
|
||||
TriggerSaveDocument {
|
||||
document_id: DocumentId,
|
||||
name: String,
|
||||
path: Option<PathBuf>,
|
||||
content: Vec<u8>,
|
||||
},
|
||||
TriggerSaveFile {
|
||||
name: String,
|
||||
content: Vec<u8>,
|
||||
},
|
||||
TriggerExportImage {
|
||||
svg: String,
|
||||
name: String,
|
||||
mime: String,
|
||||
size: (f64, f64),
|
||||
},
|
||||
TriggerDownloadTextFile {
|
||||
document: String,
|
||||
name: String,
|
||||
},
|
||||
TriggerFetchAndOpenDocument {
|
||||
name: String,
|
||||
filename: String,
|
||||
@@ -318,4 +329,10 @@ pub enum FrontendMessage {
|
||||
UpdateViewportHolePunch {
|
||||
active: bool,
|
||||
},
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
RenderOverlays(
|
||||
#[serde(skip, default = "OverlayContext::default")]
|
||||
#[derivative(Debug = "ignore", PartialEq = "ignore")]
|
||||
OverlayContext,
|
||||
),
|
||||
}
|
||||
|
||||
@@ -211,14 +211,17 @@ pub fn input_mappings() -> Mapping {
|
||||
entry!(KeyDown(Backspace); modifiers=[Accel], action_dispatch=PathToolMessage::DeleteAndBreakPath),
|
||||
entry!(KeyDown(Delete); modifiers=[Shift], action_dispatch=PathToolMessage::BreakPath),
|
||||
entry!(KeyDown(Backspace); modifiers=[Shift], action_dispatch=PathToolMessage::BreakPath),
|
||||
entry!(KeyDown(KeyX); modifiers=[Accel], action_dispatch=PathToolMessage::Cut { clipboard: Clipboard::Device }),
|
||||
entry!(KeyDown(KeyC); modifiers=[Accel], action_dispatch=PathToolMessage::Copy { clipboard: Clipboard::Device }),
|
||||
entry!(KeyDown(KeyD); modifiers=[Accel], action_dispatch=PathToolMessage::Duplicate),
|
||||
entry!(KeyDownNoRepeat(Tab); action_dispatch=PathToolMessage::SwapSelectedHandles),
|
||||
entry!(KeyDown(MouseLeft); action_dispatch=PathToolMessage::MouseDown { extend_selection: Shift, lasso_select: Control, handle_drag_from_anchor: Alt, drag_restore_handle: Control, molding_in_segment_edit: KeyA }),
|
||||
entry!(KeyDown(MouseLeft); action_dispatch=PathToolMessage::MouseDown { extend_selection: Shift, lasso_select: Control, handle_drag_from_anchor: Alt, drag_restore_handle: Control, segment_editing_modifier: Control }),
|
||||
entry!(KeyDown(MouseRight); action_dispatch=PathToolMessage::RightClick),
|
||||
entry!(KeyDown(Escape); action_dispatch=PathToolMessage::Escape),
|
||||
entry!(KeyDown(KeyG); action_dispatch=PathToolMessage::GRS { key: KeyG }),
|
||||
entry!(KeyDown(KeyR); action_dispatch=PathToolMessage::GRS { key: KeyR }),
|
||||
entry!(KeyDown(KeyS); action_dispatch=PathToolMessage::GRS { key: KeyS }),
|
||||
entry!(PointerMove; refresh_keys=[KeyC, Space, Control, Shift, Alt], action_dispatch=PathToolMessage::PointerMove { toggle_colinear: KeyC, equidistant: Alt, move_anchor_with_handles: Space, snap_angle: Shift, lock_angle: Control, delete_segment: Alt, break_colinear_molding: Alt }),
|
||||
entry!(PointerMove; refresh_keys=[KeyC, Space, Control, Shift, Alt], action_dispatch=PathToolMessage::PointerMove { toggle_colinear: KeyC, equidistant: Alt, move_anchor_with_handles: Space, snap_angle: Shift, lock_angle: Control, delete_segment: Alt, break_colinear_molding: Alt, segment_editing_modifier: Control }),
|
||||
entry!(KeyDown(Delete); action_dispatch=PathToolMessage::Delete),
|
||||
entry!(KeyDown(KeyA); modifiers=[Accel], action_dispatch=PathToolMessage::SelectAllAnchors),
|
||||
entry!(KeyDown(KeyA); modifiers=[Accel, Shift], canonical, action_dispatch=PathToolMessage::DeselectAllPoints),
|
||||
|
||||
@@ -36,6 +36,14 @@ impl MessageHandler<InputPreprocessorMessage, InputPreprocessorMessageContext> f
|
||||
responses.add(NavigationMessage::CanvasPan { delta: DVec2::ZERO });
|
||||
responses.add(NodeGraphMessage::SetGridAlignedEdges);
|
||||
}
|
||||
responses.add(DeferMessage::AfterGraphRun {
|
||||
messages: vec![
|
||||
DeferMessage::AfterGraphRun {
|
||||
messages: vec![DeferMessage::TriggerNavigationReady.into()],
|
||||
}
|
||||
.into(),
|
||||
],
|
||||
});
|
||||
}
|
||||
InputPreprocessorMessage::DoubleClick { editor_mouse_state, modifier_keys } => {
|
||||
self.update_states_of_modifier_keys(modifier_keys, keyboard_platform, responses);
|
||||
|
||||
@@ -59,8 +59,8 @@ impl LayoutMessageHandler {
|
||||
/// Get the widget path for the widget with the specified id
|
||||
fn get_widget_path(widget_layout: &WidgetLayout, widget_id: WidgetId) -> Option<(&WidgetHolder, Vec<usize>)> {
|
||||
let mut stack = widget_layout.layout.iter().enumerate().map(|(index, val)| (vec![index], val)).collect::<Vec<_>>();
|
||||
while let Some((mut widget_path, group)) = stack.pop() {
|
||||
match group {
|
||||
while let Some((mut widget_path, layout_group)) = stack.pop() {
|
||||
match layout_group {
|
||||
// Check if any of the widgets in the current column or row have the correct id
|
||||
LayoutGroup::Column { widgets } | LayoutGroup::Row { widgets } => {
|
||||
for (index, widget) in widgets.iter().enumerate() {
|
||||
|
||||
@@ -653,7 +653,7 @@ impl DiffUpdate {
|
||||
};
|
||||
|
||||
match self {
|
||||
Self::SubLayout(sub_layout) => sub_layout.iter_mut().flat_map(|group| group.iter_mut()).for_each(convert_tooltip),
|
||||
Self::SubLayout(sub_layout) => sub_layout.iter_mut().flat_map(|layout_group| layout_group.iter_mut()).for_each(convert_tooltip),
|
||||
Self::LayoutGroup(layout_group) => layout_group.iter_mut().for_each(convert_tooltip),
|
||||
Self::Widget(widget_holder) => convert_tooltip(widget_holder),
|
||||
}
|
||||
|
||||
@@ -5,8 +5,6 @@ use graphene_std::Color;
|
||||
use graphene_std::raster::curve::Curve;
|
||||
use graphene_std::transform::ReferencePoint;
|
||||
use graphite_proc_macros::WidgetBuilder;
|
||||
use once_cell::sync::OnceCell;
|
||||
use std::sync::Arc;
|
||||
|
||||
#[derive(Clone, Derivative, serde::Serialize, serde::Deserialize, WidgetBuilder, specta::Type)]
|
||||
#[derivative(Debug, PartialEq)]
|
||||
@@ -20,7 +18,7 @@ pub struct CheckboxInput {
|
||||
|
||||
pub tooltip: String,
|
||||
|
||||
#[serde(rename = "forLabel", skip_serializing_if = "checkbox_id_is_empty")]
|
||||
#[serde(rename = "forLabel")]
|
||||
pub for_label: CheckboxId,
|
||||
|
||||
#[serde(skip)]
|
||||
@@ -44,19 +42,24 @@ impl Default for CheckboxInput {
|
||||
icon: "Checkmark".into(),
|
||||
tooltip: Default::default(),
|
||||
tooltip_shortcut: Default::default(),
|
||||
for_label: CheckboxId::default(),
|
||||
for_label: CheckboxId::new(),
|
||||
on_update: Default::default(),
|
||||
on_commit: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Default, Debug, Eq, PartialEq)]
|
||||
pub struct CheckboxId(Arc<OnceCell<u64>>);
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||
pub struct CheckboxId(u64);
|
||||
|
||||
impl CheckboxId {
|
||||
pub fn fill(&mut self) {
|
||||
let _ = self.0.set(graphene_std::uuid::generate_uuid());
|
||||
pub fn new() -> Self {
|
||||
Self(graphene_std::uuid::generate_uuid())
|
||||
}
|
||||
}
|
||||
impl Default for CheckboxId {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
impl specta::Type for CheckboxId {
|
||||
@@ -65,31 +68,6 @@ impl specta::Type for CheckboxId {
|
||||
specta::datatype::DataType::Primitive(specta::datatype::PrimitiveType::u64)
|
||||
}
|
||||
}
|
||||
impl serde::Serialize for CheckboxId {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
self.0.get().copied().serialize(serializer)
|
||||
}
|
||||
}
|
||||
impl<'a> serde::Deserialize<'a> for CheckboxId {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'a>,
|
||||
{
|
||||
let optional_id: Option<u64> = Option::deserialize(deserializer)?;
|
||||
// TODO: This is potentially weird because after deserialization the two labels will be decoupled if the value not existent
|
||||
let id = optional_id.unwrap_or(0);
|
||||
let checkbox_id = CheckboxId(OnceCell::new().into());
|
||||
checkbox_id.0.set(id).map_err(serde::de::Error::custom)?;
|
||||
Ok(checkbox_id)
|
||||
}
|
||||
}
|
||||
|
||||
fn checkbox_id_is_empty(id: &CheckboxId) -> bool {
|
||||
id.0.get().is_none()
|
||||
}
|
||||
|
||||
#[derive(Clone, serde::Serialize, serde::Deserialize, Derivative, WidgetBuilder, specta::Type)]
|
||||
#[derivative(Debug, PartialEq, Default)]
|
||||
|
||||
@@ -57,21 +57,12 @@ pub struct TextLabel {
|
||||
|
||||
pub tooltip: String,
|
||||
|
||||
#[serde(rename = "checkboxId")]
|
||||
#[widget_builder(skip)]
|
||||
pub checkbox_id: CheckboxId,
|
||||
#[serde(rename = "forCheckbox")]
|
||||
pub for_checkbox: CheckboxId,
|
||||
|
||||
// Body
|
||||
#[widget_builder(constructor)]
|
||||
pub value: String,
|
||||
}
|
||||
|
||||
impl TextLabel {
|
||||
pub fn for_checkbox(mut self, id: &mut CheckboxId) -> Self {
|
||||
id.fill();
|
||||
self.checkbox_id = id.clone();
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Add UserInputLabel
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
use super::utility_types::misc::{GroupFolderType, SnappingState};
|
||||
use crate::messages::input_mapper::utility_types::input_keyboard::Key;
|
||||
use crate::messages::portfolio::document::overlays::utility_types::OverlayContext;
|
||||
@@ -105,6 +107,9 @@ pub enum DocumentMessage {
|
||||
RenderRulers,
|
||||
RenderScrollbars,
|
||||
SaveDocument,
|
||||
SavedDocument {
|
||||
path: Option<PathBuf>,
|
||||
},
|
||||
SelectParentLayer,
|
||||
SelectAllLayers,
|
||||
SelectedLayersLower,
|
||||
@@ -182,7 +187,7 @@ pub enum DocumentMessage {
|
||||
UpdateUpstreamTransforms {
|
||||
upstream_footprints: HashMap<NodeId, Footprint>,
|
||||
local_transforms: HashMap<NodeId, DAffine2>,
|
||||
first_instance_source_id: HashMap<NodeId, Option<NodeId>>,
|
||||
first_element_source_id: HashMap<NodeId, Option<NodeId>>,
|
||||
},
|
||||
UpdateClickTargets {
|
||||
click_targets: HashMap<NodeId, Vec<ClickTarget>>,
|
||||
|
||||
@@ -32,10 +32,12 @@ use graph_craft::document::{NodeId, NodeInput, NodeNetwork, OldNodeNetwork};
|
||||
use graphene_std::math::quad::Quad;
|
||||
use graphene_std::path_bool::{boolean_intersect, path_bool_lib};
|
||||
use graphene_std::raster::BlendMode;
|
||||
use graphene_std::raster_types::{Raster, RasterDataTable};
|
||||
use graphene_std::raster_types::Raster;
|
||||
use graphene_std::table::Table;
|
||||
use graphene_std::vector::PointId;
|
||||
use graphene_std::vector::click_target::{ClickTarget, ClickTargetType};
|
||||
use graphene_std::vector::style::ViewMode;
|
||||
use std::path::PathBuf;
|
||||
use std::time::Duration;
|
||||
|
||||
#[derive(ExtractField)]
|
||||
@@ -114,6 +116,9 @@ pub struct DocumentMessageHandler {
|
||||
/// Stack of document network snapshots for future history states.
|
||||
#[serde(skip)]
|
||||
document_redo_history: VecDeque<NodeNetworkInterface>,
|
||||
/// The path of the to the document file.
|
||||
#[serde(skip)]
|
||||
path: Option<PathBuf>,
|
||||
/// Hash of the document snapshot that was most recently saved to disk by the user.
|
||||
#[serde(skip)]
|
||||
saved_hash: Option<u64>,
|
||||
@@ -161,6 +166,7 @@ impl Default for DocumentMessageHandler {
|
||||
selection_network_path: Vec::new(),
|
||||
document_undo_history: VecDeque::new(),
|
||||
document_redo_history: VecDeque::new(),
|
||||
path: None,
|
||||
saved_hash: None,
|
||||
auto_saved_hash: None,
|
||||
layer_range_selection_reference: None,
|
||||
@@ -691,7 +697,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
|
||||
});
|
||||
|
||||
if layer_to_move.parent(self.metadata()) != Some(parent) {
|
||||
// TODO: Fix this so it works when dragging a layer into a group parent which has a Transform node, which used to work before #2689 caused this regression by removing the empty VectorData table row.
|
||||
// TODO: Fix this so it works when dragging a layer into a group parent which has a Transform node, which used to work before #2689 caused this regression by removing the empty vector table row.
|
||||
// TODO: See #2688 for this issue.
|
||||
let layer_local_transform = self.network_interface.document_metadata().transform_to_viewport(layer_to_move);
|
||||
let undo_transform = self.network_interface.document_metadata().transform_to_viewport(parent).inverse();
|
||||
@@ -837,7 +843,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
|
||||
|
||||
responses.add(DocumentMessage::AddTransaction);
|
||||
|
||||
let layer = graph_modification_utils::new_image_layer(RasterDataTable::new(Raster::new_cpu(image)), layer_node_id, self.new_layer_parent(true), responses);
|
||||
let layer = graph_modification_utils::new_image_layer(Table::new_from_element(Raster::new_cpu(image)), layer_node_id, self.new_layer_parent(true), responses);
|
||||
|
||||
if let Some(name) = name {
|
||||
responses.add(NodeGraphMessage::SetDisplayName {
|
||||
@@ -995,11 +1001,16 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
|
||||
true => self.name.clone(),
|
||||
false => self.name.clone() + FILE_SAVE_SUFFIX,
|
||||
};
|
||||
responses.add(FrontendMessage::TriggerDownloadTextFile {
|
||||
document: self.serialize_document(),
|
||||
responses.add(FrontendMessage::TriggerSaveDocument {
|
||||
document_id,
|
||||
name,
|
||||
path: self.path.clone(),
|
||||
content: self.serialize_document().into_bytes(),
|
||||
})
|
||||
}
|
||||
DocumentMessage::SavedDocument { path } => {
|
||||
self.path = path;
|
||||
}
|
||||
DocumentMessage::SelectParentLayer => {
|
||||
let selected_nodes = self.network_interface.selected_nodes();
|
||||
let selected_layers = selected_nodes.selected_layers(self.metadata());
|
||||
@@ -1302,10 +1313,10 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
|
||||
DocumentMessage::UpdateUpstreamTransforms {
|
||||
upstream_footprints,
|
||||
local_transforms,
|
||||
first_instance_source_id,
|
||||
first_element_source_id,
|
||||
} => {
|
||||
self.network_interface.update_transforms(upstream_footprints, local_transforms);
|
||||
self.network_interface.update_first_instance_source_id(first_instance_source_id);
|
||||
self.network_interface.update_first_element_source_id(first_element_source_id);
|
||||
}
|
||||
DocumentMessage::UpdateClickTargets { click_targets } => {
|
||||
// TODO: Allow non layer nodes to have click targets
|
||||
@@ -1435,20 +1446,6 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
|
||||
},
|
||||
})
|
||||
}
|
||||
// Some parts of the editior (e.g. navigation messages) depend on these bounds to be present
|
||||
let bounds = if self.graph_view_overlay_open {
|
||||
self.network_interface.all_nodes_bounding_box(&self.breadcrumb_network_path).cloned()
|
||||
} else {
|
||||
self.network_interface.document_bounds_document_space(true)
|
||||
};
|
||||
if bounds.is_some() {
|
||||
responses.add(DeferMessage::TriggerNavigationReady);
|
||||
} else {
|
||||
// If we don't have bounds yet, we need wait until the node graph has run once more
|
||||
responses.add(DeferMessage::AfterGraphRun {
|
||||
messages: vec![DocumentMessage::PTZUpdate.into()],
|
||||
});
|
||||
}
|
||||
}
|
||||
DocumentMessage::SelectionStepBack => {
|
||||
self.network_interface.selection_step_back(&self.selection_network_path);
|
||||
@@ -2160,7 +2157,7 @@ impl DocumentMessageHandler {
|
||||
},
|
||||
LayoutGroup::Row {
|
||||
widgets: {
|
||||
let mut checkbox_id = CheckboxId::default();
|
||||
let checkbox_id = CheckboxId::new();
|
||||
vec![
|
||||
CheckboxInput::new(self.overlays_visibility_settings.artboard_name)
|
||||
.on_update(|optional_input: &CheckboxInput| {
|
||||
@@ -2170,15 +2167,15 @@ impl DocumentMessageHandler {
|
||||
}
|
||||
.into()
|
||||
})
|
||||
.for_label(checkbox_id.clone())
|
||||
.for_label(checkbox_id)
|
||||
.widget_holder(),
|
||||
TextLabel::new("Artboard Name".to_string()).for_checkbox(&mut checkbox_id).widget_holder(),
|
||||
TextLabel::new("Artboard Name".to_string()).for_checkbox(checkbox_id).widget_holder(),
|
||||
]
|
||||
},
|
||||
},
|
||||
LayoutGroup::Row {
|
||||
widgets: {
|
||||
let mut checkbox_id = CheckboxId::default();
|
||||
let checkbox_id = CheckboxId::new();
|
||||
vec![
|
||||
CheckboxInput::new(self.overlays_visibility_settings.transform_measurement)
|
||||
.on_update(|optional_input: &CheckboxInput| {
|
||||
@@ -2188,9 +2185,9 @@ impl DocumentMessageHandler {
|
||||
}
|
||||
.into()
|
||||
})
|
||||
.for_label(checkbox_id.clone())
|
||||
.for_label(checkbox_id)
|
||||
.widget_holder(),
|
||||
TextLabel::new("G/R/S Measurement".to_string()).for_checkbox(&mut checkbox_id).widget_holder(),
|
||||
TextLabel::new("G/R/S Measurement".to_string()).for_checkbox(checkbox_id).widget_holder(),
|
||||
]
|
||||
},
|
||||
},
|
||||
@@ -2199,7 +2196,7 @@ impl DocumentMessageHandler {
|
||||
},
|
||||
LayoutGroup::Row {
|
||||
widgets: {
|
||||
let mut checkbox_id = CheckboxId::default();
|
||||
let checkbox_id = CheckboxId::new();
|
||||
vec![
|
||||
CheckboxInput::new(self.overlays_visibility_settings.quick_measurement)
|
||||
.on_update(|optional_input: &CheckboxInput| {
|
||||
@@ -2209,15 +2206,15 @@ impl DocumentMessageHandler {
|
||||
}
|
||||
.into()
|
||||
})
|
||||
.for_label(checkbox_id.clone())
|
||||
.for_label(checkbox_id)
|
||||
.widget_holder(),
|
||||
TextLabel::new("Quick Measurement".to_string()).for_checkbox(&mut checkbox_id).widget_holder(),
|
||||
TextLabel::new("Quick Measurement".to_string()).for_checkbox(checkbox_id).widget_holder(),
|
||||
]
|
||||
},
|
||||
},
|
||||
LayoutGroup::Row {
|
||||
widgets: {
|
||||
let mut checkbox_id = CheckboxId::default();
|
||||
let checkbox_id = CheckboxId::new();
|
||||
vec![
|
||||
CheckboxInput::new(self.overlays_visibility_settings.transform_cage)
|
||||
.on_update(|optional_input: &CheckboxInput| {
|
||||
@@ -2227,15 +2224,15 @@ impl DocumentMessageHandler {
|
||||
}
|
||||
.into()
|
||||
})
|
||||
.for_label(checkbox_id.clone())
|
||||
.for_label(checkbox_id)
|
||||
.widget_holder(),
|
||||
TextLabel::new("Transform Cage".to_string()).for_checkbox(&mut checkbox_id).widget_holder(),
|
||||
TextLabel::new("Transform Cage".to_string()).for_checkbox(checkbox_id).widget_holder(),
|
||||
]
|
||||
},
|
||||
},
|
||||
LayoutGroup::Row {
|
||||
widgets: {
|
||||
let mut checkbox_id = CheckboxId::default();
|
||||
let checkbox_id = CheckboxId::new();
|
||||
vec![
|
||||
CheckboxInput::new(self.overlays_visibility_settings.compass_rose)
|
||||
.on_update(|optional_input: &CheckboxInput| {
|
||||
@@ -2245,15 +2242,15 @@ impl DocumentMessageHandler {
|
||||
}
|
||||
.into()
|
||||
})
|
||||
.for_label(checkbox_id.clone())
|
||||
.for_label(checkbox_id)
|
||||
.widget_holder(),
|
||||
TextLabel::new("Transform Dial".to_string()).for_checkbox(&mut checkbox_id).widget_holder(),
|
||||
TextLabel::new("Transform Dial".to_string()).for_checkbox(checkbox_id).widget_holder(),
|
||||
]
|
||||
},
|
||||
},
|
||||
LayoutGroup::Row {
|
||||
widgets: {
|
||||
let mut checkbox_id = CheckboxId::default();
|
||||
let checkbox_id = CheckboxId::new();
|
||||
vec![
|
||||
CheckboxInput::new(self.overlays_visibility_settings.pivot)
|
||||
.on_update(|optional_input: &CheckboxInput| {
|
||||
@@ -2263,15 +2260,15 @@ impl DocumentMessageHandler {
|
||||
}
|
||||
.into()
|
||||
})
|
||||
.for_label(checkbox_id.clone())
|
||||
.for_label(checkbox_id)
|
||||
.widget_holder(),
|
||||
TextLabel::new("Transform Pivot".to_string()).for_checkbox(&mut checkbox_id).widget_holder(),
|
||||
TextLabel::new("Transform Pivot".to_string()).for_checkbox(checkbox_id).widget_holder(),
|
||||
]
|
||||
},
|
||||
},
|
||||
LayoutGroup::Row {
|
||||
widgets: {
|
||||
let mut checkbox_id = CheckboxId::default();
|
||||
let checkbox_id = CheckboxId::new();
|
||||
vec![
|
||||
CheckboxInput::new(self.overlays_visibility_settings.pivot)
|
||||
.on_update(|optional_input: &CheckboxInput| {
|
||||
@@ -2281,15 +2278,15 @@ impl DocumentMessageHandler {
|
||||
}
|
||||
.into()
|
||||
})
|
||||
.for_label(checkbox_id.clone())
|
||||
.for_label(checkbox_id)
|
||||
.widget_holder(),
|
||||
TextLabel::new("Transform Origin".to_string()).for_checkbox(&mut checkbox_id).widget_holder(),
|
||||
TextLabel::new("Transform Origin".to_string()).for_checkbox(checkbox_id).widget_holder(),
|
||||
]
|
||||
},
|
||||
},
|
||||
LayoutGroup::Row {
|
||||
widgets: {
|
||||
let mut checkbox_id = CheckboxId::default();
|
||||
let checkbox_id = CheckboxId::new();
|
||||
vec![
|
||||
CheckboxInput::new(self.overlays_visibility_settings.hover_outline)
|
||||
.on_update(|optional_input: &CheckboxInput| {
|
||||
@@ -2299,15 +2296,15 @@ impl DocumentMessageHandler {
|
||||
}
|
||||
.into()
|
||||
})
|
||||
.for_label(checkbox_id.clone())
|
||||
.for_label(checkbox_id)
|
||||
.widget_holder(),
|
||||
TextLabel::new("Hover Outline".to_string()).for_checkbox(&mut checkbox_id).widget_holder(),
|
||||
TextLabel::new("Hover Outline".to_string()).for_checkbox(checkbox_id).widget_holder(),
|
||||
]
|
||||
},
|
||||
},
|
||||
LayoutGroup::Row {
|
||||
widgets: {
|
||||
let mut checkbox_id = CheckboxId::default();
|
||||
let checkbox_id = CheckboxId::new();
|
||||
vec![
|
||||
CheckboxInput::new(self.overlays_visibility_settings.selection_outline)
|
||||
.on_update(|optional_input: &CheckboxInput| {
|
||||
@@ -2317,9 +2314,9 @@ impl DocumentMessageHandler {
|
||||
}
|
||||
.into()
|
||||
})
|
||||
.for_label(checkbox_id.clone())
|
||||
.for_label(checkbox_id)
|
||||
.widget_holder(),
|
||||
TextLabel::new("Selection Outline".to_string()).for_checkbox(&mut checkbox_id).widget_holder(),
|
||||
TextLabel::new("Selection Outline".to_string()).for_checkbox(checkbox_id).widget_holder(),
|
||||
]
|
||||
},
|
||||
},
|
||||
@@ -2328,7 +2325,7 @@ impl DocumentMessageHandler {
|
||||
},
|
||||
LayoutGroup::Row {
|
||||
widgets: {
|
||||
let mut checkbox_id = CheckboxId::default();
|
||||
let checkbox_id = CheckboxId::new();
|
||||
vec![
|
||||
CheckboxInput::new(self.overlays_visibility_settings.path)
|
||||
.on_update(|optional_input: &CheckboxInput| {
|
||||
@@ -2338,15 +2335,15 @@ impl DocumentMessageHandler {
|
||||
}
|
||||
.into()
|
||||
})
|
||||
.for_label(checkbox_id.clone())
|
||||
.for_label(checkbox_id)
|
||||
.widget_holder(),
|
||||
TextLabel::new("Path".to_string()).for_checkbox(&mut checkbox_id).widget_holder(),
|
||||
TextLabel::new("Path".to_string()).for_checkbox(checkbox_id).widget_holder(),
|
||||
]
|
||||
},
|
||||
},
|
||||
LayoutGroup::Row {
|
||||
widgets: {
|
||||
let mut checkbox_id = CheckboxId::default();
|
||||
let checkbox_id = CheckboxId::new();
|
||||
vec![
|
||||
CheckboxInput::new(self.overlays_visibility_settings.anchors)
|
||||
.on_update(|optional_input: &CheckboxInput| {
|
||||
@@ -2356,15 +2353,15 @@ impl DocumentMessageHandler {
|
||||
}
|
||||
.into()
|
||||
})
|
||||
.for_label(checkbox_id.clone())
|
||||
.for_label(checkbox_id)
|
||||
.widget_holder(),
|
||||
TextLabel::new("Anchors".to_string()).for_checkbox(&mut checkbox_id).widget_holder(),
|
||||
TextLabel::new("Anchors".to_string()).for_checkbox(checkbox_id).widget_holder(),
|
||||
]
|
||||
},
|
||||
},
|
||||
LayoutGroup::Row {
|
||||
widgets: {
|
||||
let mut checkbox_id = CheckboxId::default();
|
||||
let checkbox_id = CheckboxId::new();
|
||||
vec![
|
||||
CheckboxInput::new(self.overlays_visibility_settings.handles)
|
||||
.disabled(!self.overlays_visibility_settings.anchors)
|
||||
@@ -2375,11 +2372,11 @@ impl DocumentMessageHandler {
|
||||
}
|
||||
.into()
|
||||
})
|
||||
.for_label(checkbox_id.clone())
|
||||
.for_label(checkbox_id)
|
||||
.widget_holder(),
|
||||
TextLabel::new("Handles".to_string())
|
||||
.disabled(!self.overlays_visibility_settings.anchors)
|
||||
.for_checkbox(&mut checkbox_id)
|
||||
.for_checkbox(checkbox_id)
|
||||
.widget_holder(),
|
||||
]
|
||||
},
|
||||
@@ -2412,7 +2409,7 @@ impl DocumentMessageHandler {
|
||||
.into_iter()
|
||||
.chain(SNAP_FUNCTIONS_FOR_BOUNDING_BOXES.into_iter().map(|(name, closure, tooltip)| LayoutGroup::Row {
|
||||
widgets: {
|
||||
let mut checkbox_id = CheckboxId::default();
|
||||
let checkbox_id = CheckboxId::new();
|
||||
vec![
|
||||
CheckboxInput::new(*closure(&mut snapping_state))
|
||||
.on_update(move |input: &CheckboxInput| {
|
||||
@@ -2423,9 +2420,9 @@ impl DocumentMessageHandler {
|
||||
.into()
|
||||
})
|
||||
.tooltip(tooltip)
|
||||
.for_label(checkbox_id.clone())
|
||||
.for_label(checkbox_id)
|
||||
.widget_holder(),
|
||||
TextLabel::new(name).tooltip(tooltip).for_checkbox(&mut checkbox_id).widget_holder(),
|
||||
TextLabel::new(name).tooltip(tooltip).for_checkbox(checkbox_id).widget_holder(),
|
||||
]
|
||||
},
|
||||
}))
|
||||
@@ -2434,7 +2431,7 @@ impl DocumentMessageHandler {
|
||||
}])
|
||||
.chain(SNAP_FUNCTIONS_FOR_PATHS.into_iter().map(|(name, closure, tooltip)| LayoutGroup::Row {
|
||||
widgets: {
|
||||
let mut checkbox_id = CheckboxId::default();
|
||||
let checkbox_id = CheckboxId::new();
|
||||
vec![
|
||||
CheckboxInput::new(*closure(&mut snapping_state2))
|
||||
.on_update(move |input: &CheckboxInput| {
|
||||
@@ -2445,9 +2442,9 @@ impl DocumentMessageHandler {
|
||||
.into()
|
||||
})
|
||||
.tooltip(tooltip)
|
||||
.for_label(checkbox_id.clone())
|
||||
.for_label(checkbox_id)
|
||||
.widget_holder(),
|
||||
TextLabel::new(name).tooltip(tooltip).for_checkbox(&mut checkbox_id).widget_holder(),
|
||||
TextLabel::new(name).tooltip(tooltip).for_checkbox(checkbox_id).widget_holder(),
|
||||
]
|
||||
},
|
||||
}))
|
||||
@@ -2920,7 +2917,7 @@ impl DocumentMessageHandler {
|
||||
/// Create a network interface with a single export
|
||||
fn default_document_network_interface() -> NodeNetworkInterface {
|
||||
let mut network_interface = NodeNetworkInterface::default();
|
||||
network_interface.add_export(TaggedValue::ArtboardGroup(graphene_std::ArtboardGroupTable::default()), -1, "", &[]);
|
||||
network_interface.add_export(TaggedValue::Artboard(Default::default()), -1, "", &[]);
|
||||
network_interface
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,8 @@ use graph_craft::document::NodeId;
|
||||
use graphene_std::Artboard;
|
||||
use graphene_std::brush::brush_stroke::BrushStroke;
|
||||
use graphene_std::raster::BlendMode;
|
||||
use graphene_std::raster_types::{CPU, RasterDataTable};
|
||||
use graphene_std::raster_types::{CPU, Raster};
|
||||
use graphene_std::table::Table;
|
||||
use graphene_std::text::{Font, TypesettingConfig};
|
||||
use graphene_std::vector::PointId;
|
||||
use graphene_std::vector::VectorModificationType;
|
||||
@@ -69,7 +70,7 @@ pub enum GraphOperationMessage {
|
||||
},
|
||||
NewBitmapLayer {
|
||||
id: NodeId,
|
||||
image_frame: RasterDataTable<CPU>,
|
||||
image_frame: Table<Raster<CPU>>,
|
||||
parent: LayerNodeIdentifier,
|
||||
insert_index: usize,
|
||||
},
|
||||
|
||||
+2
-2
@@ -174,7 +174,7 @@ impl MessageHandler<GraphOperationMessage, GraphOperationMessageContext<'_>> for
|
||||
GraphOperationMessage::NewVectorLayer { id, subpaths, parent, insert_index } => {
|
||||
let mut modify_inputs = ModifyInputsContext::new(network_interface, responses);
|
||||
let layer = modify_inputs.create_layer(id);
|
||||
modify_inputs.insert_vector_data(subpaths, layer, true, true, true);
|
||||
modify_inputs.insert_vector(subpaths, layer, true, true, true);
|
||||
network_interface.move_layer_to_stack(layer, parent, insert_index, &[]);
|
||||
responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||
}
|
||||
@@ -349,7 +349,7 @@ fn import_usvg_node(modify_inputs: &mut ModifyInputsContext, node: &usvg::Node,
|
||||
let subpaths = convert_usvg_path(path);
|
||||
let bounds = subpaths.iter().filter_map(|subpath| subpath.bounding_box()).reduce(Quad::combine_bounds).unwrap_or_default();
|
||||
|
||||
modify_inputs.insert_vector_data(subpaths, layer, true, path.fill().is_some(), path.stroke().is_some());
|
||||
modify_inputs.insert_vector(subpaths, layer, true, path.fill().is_some(), path.stroke().is_some());
|
||||
|
||||
if let Some(transform_node_id) = modify_inputs.existing_node_id("Transform", true) {
|
||||
transform_utils::update_transform(modify_inputs.network_interface, &transform_node_id, transform * usvg_transform(node.abs_transform()));
|
||||
|
||||
@@ -11,12 +11,13 @@ use graph_craft::document::{NodeId, NodeInput};
|
||||
use graphene_std::Artboard;
|
||||
use graphene_std::brush::brush_stroke::BrushStroke;
|
||||
use graphene_std::raster::BlendMode;
|
||||
use graphene_std::raster_types::{CPU, RasterDataTable};
|
||||
use graphene_std::raster_types::{CPU, Raster};
|
||||
use graphene_std::table::Table;
|
||||
use graphene_std::text::{Font, TypesettingConfig};
|
||||
use graphene_std::vector::Vector;
|
||||
use graphene_std::vector::style::{Fill, Stroke};
|
||||
use graphene_std::vector::{PointId, VectorModificationType};
|
||||
use graphene_std::vector::{VectorData, VectorDataTable};
|
||||
use graphene_std::{GraphicGroupTable, NodeInputDecleration};
|
||||
use graphene_std::{Graphic, NodeInputDecleration};
|
||||
|
||||
#[derive(PartialEq, Clone, Copy, Debug, serde::Serialize, serde::Deserialize)]
|
||||
pub enum TransformIn {
|
||||
@@ -130,8 +131,8 @@ impl<'a> ModifyInputsContext<'a> {
|
||||
/// Creates an artboard as the primary export for the document network
|
||||
pub fn create_artboard(&mut self, new_id: NodeId, artboard: Artboard) -> LayerNodeIdentifier {
|
||||
let artboard_node_template = resolve_document_node_type("Artboard").expect("Node").node_template_input_override([
|
||||
Some(NodeInput::value(TaggedValue::ArtboardGroup(graphene_std::ArtboardGroupTable::default()), true)),
|
||||
Some(NodeInput::value(TaggedValue::GraphicGroup(graphene_std::GraphicGroupTable::default()), true)),
|
||||
Some(NodeInput::value(TaggedValue::Artboard(Default::default()), true)),
|
||||
Some(NodeInput::value(TaggedValue::Graphic(Default::default()), true)),
|
||||
Some(NodeInput::value(TaggedValue::DVec2(artboard.location.into()), false)),
|
||||
Some(NodeInput::value(TaggedValue::DVec2(artboard.dimensions.into()), false)),
|
||||
Some(NodeInput::value(TaggedValue::Color(artboard.background), false)),
|
||||
@@ -143,7 +144,7 @@ impl<'a> ModifyInputsContext<'a> {
|
||||
|
||||
pub fn insert_boolean_data(&mut self, operation: graphene_std::path_bool::BooleanOperation, layer: LayerNodeIdentifier) {
|
||||
let boolean = resolve_document_node_type("Boolean Operation").expect("Boolean node does not exist").node_template_input_override([
|
||||
Some(NodeInput::value(TaggedValue::GraphicGroup(graphene_std::GraphicGroupTable::default()), true)),
|
||||
Some(NodeInput::value(TaggedValue::Graphic(Default::default()), true)),
|
||||
Some(NodeInput::value(TaggedValue::BooleanOperation(operation), false)),
|
||||
]);
|
||||
|
||||
@@ -152,12 +153,12 @@ impl<'a> ModifyInputsContext<'a> {
|
||||
self.network_interface.move_node_to_chain_start(&boolean_id, layer, &[]);
|
||||
}
|
||||
|
||||
pub fn insert_vector_data(&mut self, subpaths: Vec<Subpath<PointId>>, layer: LayerNodeIdentifier, include_transform: bool, include_fill: bool, include_stroke: bool) {
|
||||
let vector_data = VectorDataTable::new(VectorData::from_subpaths(subpaths, true));
|
||||
pub fn insert_vector(&mut self, subpaths: Vec<Subpath<PointId>>, layer: LayerNodeIdentifier, include_transform: bool, include_fill: bool, include_stroke: bool) {
|
||||
let vector = Table::new_from_element(Vector::from_subpaths(subpaths, true));
|
||||
|
||||
let shape = resolve_document_node_type("Path")
|
||||
.expect("Path node does not exist")
|
||||
.node_template_input_override([Some(NodeInput::value(TaggedValue::VectorData(vector_data), false))]);
|
||||
.node_template_input_override([Some(NodeInput::value(TaggedValue::Vector(vector), false))]);
|
||||
let shape_id = NodeId::new();
|
||||
self.network_interface.insert_node(shape_id, shape, &[]);
|
||||
self.network_interface.move_node_to_chain_start(&shape_id, layer, &[]);
|
||||
@@ -218,11 +219,11 @@ impl<'a> ModifyInputsContext<'a> {
|
||||
self.network_interface.move_node_to_chain_start(&stroke_id, layer, &[]);
|
||||
}
|
||||
|
||||
pub fn insert_image_data(&mut self, image_frame: RasterDataTable<CPU>, layer: LayerNodeIdentifier) {
|
||||
pub fn insert_image_data(&mut self, image_frame: Table<Raster<CPU>>, layer: LayerNodeIdentifier) {
|
||||
let transform = resolve_document_node_type("Transform").expect("Transform node does not exist").default_node_template();
|
||||
let image = resolve_document_node_type("Image Value")
|
||||
.expect("ImageValue node does not exist")
|
||||
.node_template_input_override([Some(NodeInput::value(TaggedValue::None, false)), Some(NodeInput::value(TaggedValue::RasterData(image_frame), false))]);
|
||||
.node_template_input_override([Some(NodeInput::value(TaggedValue::None, false)), Some(NodeInput::value(TaggedValue::Raster(image_frame), false))]);
|
||||
|
||||
let image_id = NodeId::new();
|
||||
self.network_interface.insert_node(image_id, image, &[]);
|
||||
@@ -262,7 +263,7 @@ impl<'a> ModifyInputsContext<'a> {
|
||||
}
|
||||
|
||||
/// Gets the node id of a node with a specific reference (name) that is upstream (leftward) from the layer node, but before reaching another upstream layer stack.
|
||||
/// For example, if given a group layer, this would find a requested "Transform" or "Boolean Operation" node in its chain, between the group layer and its layer stack child contents.
|
||||
/// For example, if given a parent layer, this would find a requested "Transform" or "Boolean Operation" node in its chain, between the parent layer and its layer stack child contents.
|
||||
/// It would also travel up an entire layer that's not fed by a stack until reaching the generator node, such as a "Rectangle" or "Path" layer.
|
||||
pub fn locate_node_in_layer_chain(reference_name: &str, left_of_layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option<NodeId> {
|
||||
let upstream = network_interface.upstream_flow_back_from_nodes(vec![left_of_layer.to_node()], &[], network_interface::FlowType::HorizontalFlow);
|
||||
@@ -295,14 +296,15 @@ impl<'a> ModifyInputsContext<'a> {
|
||||
pub fn create_node(&mut self, reference: &str) -> Option<NodeId> {
|
||||
let output_layer = self.get_output_layer()?;
|
||||
let Some(node_definition) = resolve_document_node_type(reference) else {
|
||||
log::error!("Node type {} does not exist in ModifyInputsContext::existing_node_id", reference);
|
||||
log::error!("Node type {reference} does not exist in ModifyInputsContext::existing_node_id");
|
||||
return None;
|
||||
};
|
||||
// If inserting a path node, insert a Flatten Path if the type is a graphic group.
|
||||
// TODO: Allow the path node to operate on Graphic Group data by utilizing the reference for each vector data in a group.
|
||||
|
||||
// If inserting a 'Path' node, insert a 'Flatten Path' node if the type is `Graphic`.
|
||||
// TODO: Allow the 'Path' node to operate on table data by utilizing the reference (index or ID?) for each row.
|
||||
if node_definition.identifier == "Path" {
|
||||
let layer_input_type = self.network_interface.input_type(&InputConnector::node(output_layer.to_node(), 1), &[]).0.nested_type().clone();
|
||||
if layer_input_type == concrete!(GraphicGroupTable) {
|
||||
if layer_input_type == concrete!(Table<Graphic>) {
|
||||
let Some(flatten_path_definition) = resolve_document_node_type("Flatten Path") else {
|
||||
log::error!("Flatten Path does not exist in ModifyInputsContext::existing_node_id");
|
||||
return None;
|
||||
|
||||
@@ -19,11 +19,12 @@ use graph_craft::document::*;
|
||||
use graphene_std::brush::brush_cache::BrushCache;
|
||||
use graphene_std::extract_xy::XY;
|
||||
use graphene_std::raster::{CellularDistanceFunction, CellularReturnType, Color, DomainWarpType, FractalType, NoiseType, RedGreenBlueAlpha};
|
||||
use graphene_std::raster_types::{CPU, RasterDataTable};
|
||||
use graphene_std::raster_types::{CPU, Raster};
|
||||
use graphene_std::table::Table;
|
||||
use graphene_std::text::{Font, TypesettingConfig};
|
||||
#[allow(unused_imports)]
|
||||
use graphene_std::transform::Footprint;
|
||||
use graphene_std::vector::VectorDataTable;
|
||||
use graphene_std::vector::Vector;
|
||||
use graphene_std::*;
|
||||
use std::collections::{HashMap, HashSet, VecDeque};
|
||||
|
||||
@@ -85,7 +86,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
let custom = vec![
|
||||
// TODO: Auto-generate this from its proto node macro
|
||||
DocumentNodeDefinition {
|
||||
identifier: "Identity",
|
||||
identifier: "Passthrough",
|
||||
category: "General",
|
||||
node_template: NodeTemplate {
|
||||
document_node: DocumentNode {
|
||||
@@ -94,13 +95,13 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
..Default::default()
|
||||
},
|
||||
persistent_node_metadata: DocumentNodePersistentMetadata {
|
||||
input_metadata: vec![("In", "TODO").into()],
|
||||
input_metadata: vec![("Content", "TODO").into()],
|
||||
output_names: vec!["Out".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
},
|
||||
description: Cow::Borrowed("Passes-through the input value without changing it. This is useful for rerouting wires for organization purposes."),
|
||||
properties: Some("identity_properties"),
|
||||
description: Cow::Borrowed("Returns the input value without changing it. This is useful for rerouting wires for organization purposes."),
|
||||
properties: None,
|
||||
},
|
||||
// TODO: Auto-generate this from its proto node macro
|
||||
DocumentNodeDefinition {
|
||||
@@ -226,25 +227,32 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
node_template: NodeTemplate {
|
||||
document_node: DocumentNode {
|
||||
implementation: DocumentNodeImplementation::Network(NodeNetwork {
|
||||
exports: vec![NodeInput::node(NodeId(3), 0)],
|
||||
exports: vec![NodeInput::node(NodeId(4), 0)],
|
||||
nodes: [
|
||||
// Secondary (left) input type coercion
|
||||
DocumentNode {
|
||||
inputs: vec![NodeInput::network(generic!(T), 1)],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(graphic_element::to_element::IDENTIFIER),
|
||||
manual_composition: Some(concrete!(Context)),
|
||||
..Default::default()
|
||||
},
|
||||
// Primary (bottom) input type coercion
|
||||
DocumentNode {
|
||||
inputs: vec![NodeInput::network(generic!(T), 0)],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(graphic_element::to_group::IDENTIFIER),
|
||||
implementation: DocumentNodeImplementation::ProtoNode(graphic::to_graphic::IDENTIFIER),
|
||||
manual_composition: Some(concrete!(Context)),
|
||||
..Default::default()
|
||||
},
|
||||
// Secondary (left) input type coercion
|
||||
DocumentNode {
|
||||
inputs: vec![NodeInput::network(generic!(T), 1)],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(graphic::wrap_graphic::IDENTIFIER),
|
||||
manual_composition: Some(concrete!(Context)),
|
||||
..Default::default()
|
||||
},
|
||||
// Store the ID of the parent node (which encapsulates this sub-network) in each row we are extending the table with.
|
||||
DocumentNode {
|
||||
inputs: vec![NodeInput::node(NodeId(1), 0), NodeInput::Reflection(graph_craft::document::DocumentNodeMetadata::DocumentNodePath)],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(graphic::source_node_id::IDENTIFIER),
|
||||
manual_composition: Some(concrete!(Context)),
|
||||
..Default::default()
|
||||
},
|
||||
// The monitor node is used to display a thumbnail in the UI
|
||||
DocumentNode {
|
||||
inputs: vec![NodeInput::node(NodeId(0), 0)],
|
||||
inputs: vec![NodeInput::node(NodeId(2), 0)],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(memo::monitor::IDENTIFIER),
|
||||
manual_composition: Some(concrete!(Context)),
|
||||
skip_deduplication: true,
|
||||
@@ -252,12 +260,8 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
},
|
||||
DocumentNode {
|
||||
manual_composition: Some(generic!(T)),
|
||||
inputs: vec![
|
||||
NodeInput::node(NodeId(1), 0),
|
||||
NodeInput::node(NodeId(2), 0),
|
||||
NodeInput::Reflection(graph_craft::document::DocumentNodeMetadata::DocumentNodePath),
|
||||
],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(graphic_element::layer::IDENTIFIER),
|
||||
inputs: vec![NodeInput::node(NodeId(0), 0), NodeInput::node(NodeId(3), 0)],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(graphic::extend::IDENTIFIER),
|
||||
..Default::default()
|
||||
},
|
||||
]
|
||||
@@ -268,13 +272,13 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
..Default::default()
|
||||
}),
|
||||
inputs: vec![
|
||||
NodeInput::value(TaggedValue::GraphicGroup(GraphicGroupTable::default()), true),
|
||||
NodeInput::value(TaggedValue::GraphicGroup(GraphicGroupTable::default()), true),
|
||||
NodeInput::value(TaggedValue::Graphic(Default::default()), true),
|
||||
NodeInput::value(TaggedValue::Graphic(Default::default()), true),
|
||||
],
|
||||
..Default::default()
|
||||
},
|
||||
persistent_node_metadata: DocumentNodePersistentMetadata {
|
||||
input_metadata: vec![("Graphical Data", "TODO").into(), ("Over", "TODO").into()],
|
||||
input_metadata: vec![("Base", "TODO").into(), ("Content", "TODO").into()],
|
||||
output_names: vec!["Out".to_string()],
|
||||
node_type_metadata: NodeTypePersistentMetadata::layer(IVec2::new(0, 0)),
|
||||
network_metadata: Some(NodeNetworkMetadata {
|
||||
@@ -282,16 +286,24 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
node_metadata: [
|
||||
DocumentNodeMetadata {
|
||||
persistent_metadata: DocumentNodePersistentMetadata {
|
||||
display_name: "To Element".to_string(),
|
||||
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(-14, -1)),
|
||||
display_name: "To Graphic".to_string(),
|
||||
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(-21, -3)),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
DocumentNodeMetadata {
|
||||
persistent_metadata: DocumentNodePersistentMetadata {
|
||||
display_name: "To Group".to_string(),
|
||||
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(-14, -3)),
|
||||
display_name: "Wrap Graphic".to_string(),
|
||||
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(-21, -1)),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
DocumentNodeMetadata {
|
||||
persistent_metadata: DocumentNodePersistentMetadata {
|
||||
display_name: "Source Node ID".to_string(),
|
||||
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(-14, -1)),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
@@ -306,7 +318,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
},
|
||||
DocumentNodeMetadata {
|
||||
persistent_metadata: DocumentNodePersistentMetadata {
|
||||
display_name: "Layer".to_string(),
|
||||
display_name: "Extend".to_string(),
|
||||
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(0, -3)),
|
||||
..Default::default()
|
||||
},
|
||||
@@ -324,7 +336,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
..Default::default()
|
||||
},
|
||||
},
|
||||
description: Cow::Borrowed("Merge attaches a layer to the stack's group."),
|
||||
description: Cow::Borrowed("Merges new content as an entry into the graphic table that represents a layer compositing stack."),
|
||||
properties: None,
|
||||
},
|
||||
DocumentNodeDefinition {
|
||||
@@ -333,12 +345,12 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
node_template: NodeTemplate {
|
||||
document_node: DocumentNode {
|
||||
implementation: DocumentNodeImplementation::Network(NodeNetwork {
|
||||
exports: vec![NodeInput::node(NodeId(2), 0)],
|
||||
exports: vec![NodeInput::node(NodeId(3), 0)],
|
||||
nodes: [
|
||||
// Ensure this ID is kept in sync with the ID in set_alias so that the name input is kept in sync with the alias
|
||||
DocumentNode {
|
||||
manual_composition: Some(generic!(T)),
|
||||
implementation: DocumentNodeImplementation::ProtoNode(graphic_element::to_artboard::IDENTIFIER),
|
||||
implementation: DocumentNodeImplementation::ProtoNode(artboard::create_artboard::IDENTIFIER),
|
||||
inputs: vec![
|
||||
NodeInput::network(concrete!(TaggedValue), 1),
|
||||
NodeInput::value(TaggedValue::String(String::from("Artboard")), false),
|
||||
@@ -349,10 +361,17 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
],
|
||||
..Default::default()
|
||||
},
|
||||
// Store the ID of the parent node (which encapsulates this sub-network) in each row we are extending the table with.
|
||||
DocumentNode {
|
||||
inputs: vec![NodeInput::node(NodeId(0), 0), NodeInput::Reflection(graph_craft::document::DocumentNodeMetadata::DocumentNodePath)],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(graphic::source_node_id::IDENTIFIER),
|
||||
manual_composition: Some(concrete!(Context)),
|
||||
..Default::default()
|
||||
},
|
||||
// The monitor node is used to display a thumbnail in the UI.
|
||||
// TODO: Check if thumbnail is reversed
|
||||
DocumentNode {
|
||||
inputs: vec![NodeInput::node(NodeId(0), 0)],
|
||||
inputs: vec![NodeInput::node(NodeId(1), 0)],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(memo::monitor::IDENTIFIER),
|
||||
manual_composition: Some(generic!(T)),
|
||||
skip_deduplication: true,
|
||||
@@ -361,11 +380,11 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
DocumentNode {
|
||||
manual_composition: Some(concrete!(Context)),
|
||||
inputs: vec![
|
||||
NodeInput::network(graphene_std::Type::Fn(Box::new(concrete!(Context)), Box::new(concrete!(ArtboardGroupTable))), 0),
|
||||
NodeInput::node(NodeId(1), 0),
|
||||
NodeInput::network(graphene_std::Type::Fn(Box::new(concrete!(Context)), Box::new(concrete!(Table<Artboard>))), 0),
|
||||
NodeInput::node(NodeId(2), 0),
|
||||
NodeInput::Reflection(graph_craft::document::DocumentNodeMetadata::DocumentNodePath),
|
||||
],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(graphic_element::append_artboard::IDENTIFIER),
|
||||
implementation: DocumentNodeImplementation::ProtoNode(graphic::extend::IDENTIFIER),
|
||||
..Default::default()
|
||||
},
|
||||
]
|
||||
@@ -376,8 +395,8 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
..Default::default()
|
||||
}),
|
||||
inputs: vec![
|
||||
NodeInput::value(TaggedValue::ArtboardGroup(ArtboardGroupTable::default()), true),
|
||||
NodeInput::value(TaggedValue::GraphicGroup(GraphicGroupTable::default()), true),
|
||||
NodeInput::value(TaggedValue::Artboard(Default::default()), true),
|
||||
NodeInput::value(TaggedValue::Graphic(Default::default()), true),
|
||||
NodeInput::value(TaggedValue::DVec2(DVec2::ZERO), false),
|
||||
NodeInput::value(TaggedValue::DVec2(DVec2::new(1920., 1080.)), false),
|
||||
NodeInput::value(TaggedValue::Color(Color::WHITE), false),
|
||||
@@ -387,8 +406,8 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
},
|
||||
persistent_node_metadata: DocumentNodePersistentMetadata {
|
||||
input_metadata: vec![
|
||||
("Artboards", "TODO").into(),
|
||||
InputMetadata::with_name_description_override("Contents", "TODO", WidgetOverride::Hidden),
|
||||
("Base", "TODO").into(),
|
||||
InputMetadata::with_name_description_override("Content", "TODO", WidgetOverride::Hidden),
|
||||
InputMetadata::with_name_description_override(
|
||||
"Location",
|
||||
"TODO",
|
||||
@@ -421,7 +440,15 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
node_metadata: [
|
||||
DocumentNodeMetadata {
|
||||
persistent_metadata: DocumentNodePersistentMetadata {
|
||||
display_name: "To Artboard".to_string(),
|
||||
display_name: "Create Artboard".to_string(),
|
||||
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(-21, -3)),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
DocumentNodeMetadata {
|
||||
persistent_metadata: DocumentNodePersistentMetadata {
|
||||
display_name: "Source Node ID".to_string(),
|
||||
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(-14, -3)),
|
||||
..Default::default()
|
||||
},
|
||||
@@ -437,7 +464,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
},
|
||||
DocumentNodeMetadata {
|
||||
persistent_metadata: DocumentNodePersistentMetadata {
|
||||
display_name: "Append Artboards".to_string(),
|
||||
display_name: "Extend".to_string(),
|
||||
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(0, -4)),
|
||||
..Default::default()
|
||||
},
|
||||
@@ -591,7 +618,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
description: Cow::Borrowed("Creates a new canvas object."),
|
||||
properties: None,
|
||||
},
|
||||
#[cfg(all(feature = "gpu", target_arch = "wasm32"))]
|
||||
#[cfg(all(feature = "gpu", target_family = "wasm"))]
|
||||
DocumentNodeDefinition {
|
||||
identifier: "Rasterize",
|
||||
category: "Raster",
|
||||
@@ -627,7 +654,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
..Default::default()
|
||||
}),
|
||||
inputs: vec![
|
||||
NodeInput::value(TaggedValue::VectorData(VectorDataTable::default()), true),
|
||||
NodeInput::value(TaggedValue::Vector(Default::default()), true),
|
||||
NodeInput::value(
|
||||
TaggedValue::Footprint(Footprint {
|
||||
transform: DAffine2::from_scale_angle_translation(DVec2::new(1000., 1000.), 0., DVec2::new(0., 0.)),
|
||||
@@ -681,7 +708,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
..Default::default()
|
||||
},
|
||||
},
|
||||
description: Cow::Borrowed("Rasterizes the given vector data"),
|
||||
description: Cow::Borrowed("TODO"),
|
||||
properties: None,
|
||||
},
|
||||
DocumentNodeDefinition {
|
||||
@@ -752,7 +779,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
nodes: [
|
||||
DocumentNode {
|
||||
inputs: vec![
|
||||
NodeInput::network(concrete!(RasterDataTable<CPU>), 0),
|
||||
NodeInput::network(concrete!(Table<Raster<CPU>>), 0),
|
||||
NodeInput::value(TaggedValue::RedGreenBlueAlpha(RedGreenBlueAlpha::Red), false),
|
||||
],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(raster_nodes::adjustments::extract_channel::IDENTIFIER),
|
||||
@@ -761,7 +788,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
},
|
||||
DocumentNode {
|
||||
inputs: vec![
|
||||
NodeInput::network(concrete!(RasterDataTable<CPU>), 0),
|
||||
NodeInput::network(concrete!(Table<Raster<CPU>>), 0),
|
||||
NodeInput::value(TaggedValue::RedGreenBlueAlpha(RedGreenBlueAlpha::Green), false),
|
||||
],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(raster_nodes::adjustments::extract_channel::IDENTIFIER),
|
||||
@@ -770,7 +797,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
},
|
||||
DocumentNode {
|
||||
inputs: vec![
|
||||
NodeInput::network(concrete!(RasterDataTable<CPU>), 0),
|
||||
NodeInput::network(concrete!(Table<Raster<CPU>>), 0),
|
||||
NodeInput::value(TaggedValue::RedGreenBlueAlpha(RedGreenBlueAlpha::Blue), false),
|
||||
],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(raster_nodes::adjustments::extract_channel::IDENTIFIER),
|
||||
@@ -779,7 +806,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
},
|
||||
DocumentNode {
|
||||
inputs: vec![
|
||||
NodeInput::network(concrete!(RasterDataTable<CPU>), 0),
|
||||
NodeInput::network(concrete!(Table<Raster<CPU>>), 0),
|
||||
NodeInput::value(TaggedValue::RedGreenBlueAlpha(RedGreenBlueAlpha::Alpha), false),
|
||||
],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(raster_nodes::adjustments::extract_channel::IDENTIFIER),
|
||||
@@ -793,7 +820,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
.collect(),
|
||||
..Default::default()
|
||||
}),
|
||||
inputs: vec![NodeInput::value(TaggedValue::RasterData(RasterDataTable::default()), true)],
|
||||
inputs: vec![NodeInput::value(TaggedValue::Raster(Default::default()), true)],
|
||||
..Default::default()
|
||||
},
|
||||
persistent_node_metadata: DocumentNodePersistentMetadata {
|
||||
@@ -859,13 +886,13 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
exports: vec![NodeInput::node(NodeId(0), 0), NodeInput::node(NodeId(1), 0)],
|
||||
nodes: [
|
||||
DocumentNode {
|
||||
inputs: vec![NodeInput::network(concrete!(RasterDataTable<CPU>), 0), NodeInput::value(TaggedValue::XY(XY::X), false)],
|
||||
inputs: vec![NodeInput::network(concrete!(Table<Raster<CPU>>), 0), NodeInput::value(TaggedValue::XY(XY::X), false)],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(extract_xy::extract_xy::IDENTIFIER),
|
||||
manual_composition: Some(generic!(T)),
|
||||
..Default::default()
|
||||
},
|
||||
DocumentNode {
|
||||
inputs: vec![NodeInput::network(concrete!(RasterDataTable<CPU>), 0), NodeInput::value(TaggedValue::XY(XY::Y), false)],
|
||||
inputs: vec![NodeInput::network(concrete!(Table<Raster<CPU>>), 0), NodeInput::value(TaggedValue::XY(XY::Y), false)],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(extract_xy::extract_xy::IDENTIFIER),
|
||||
manual_composition: Some(generic!(T)),
|
||||
..Default::default()
|
||||
@@ -878,7 +905,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
|
||||
..Default::default()
|
||||
}),
|
||||
inputs: vec![NodeInput::value(TaggedValue::RasterData(RasterDataTable::default()), true)],
|
||||
inputs: vec![NodeInput::value(TaggedValue::Raster(Default::default()), true)],
|
||||
..Default::default()
|
||||
},
|
||||
persistent_node_metadata: DocumentNodePersistentMetadata {
|
||||
@@ -931,7 +958,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
exports: vec![NodeInput::node(NodeId(0), 0)],
|
||||
nodes: vec![DocumentNode {
|
||||
inputs: vec![
|
||||
NodeInput::network(concrete!(RasterDataTable<CPU>), 0),
|
||||
NodeInput::network(concrete!(Table<Raster<CPU>>), 0),
|
||||
NodeInput::network(concrete!(Vec<brush::brush_stroke::BrushStroke>), 1),
|
||||
NodeInput::network(concrete!(BrushCache), 2),
|
||||
],
|
||||
@@ -946,7 +973,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
..Default::default()
|
||||
}),
|
||||
inputs: vec![
|
||||
NodeInput::value(TaggedValue::RasterData(RasterDataTable::default()), true),
|
||||
NodeInput::value(TaggedValue::Raster(Default::default()), true),
|
||||
NodeInput::value(TaggedValue::BrushStrokes(Vec::new()), false),
|
||||
NodeInput::value(TaggedValue::BrushCache(BrushCache::default()), false),
|
||||
],
|
||||
@@ -985,7 +1012,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
node_template: NodeTemplate {
|
||||
document_node: DocumentNode {
|
||||
implementation: DocumentNodeImplementation::ProtoNode(memo::memo::IDENTIFIER),
|
||||
inputs: vec![NodeInput::value(TaggedValue::RasterData(RasterDataTable::default()), true)],
|
||||
inputs: vec![NodeInput::value(TaggedValue::Raster(Default::default()), true)],
|
||||
manual_composition: Some(concrete!(Context)),
|
||||
..Default::default()
|
||||
},
|
||||
@@ -1004,7 +1031,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
node_template: NodeTemplate {
|
||||
document_node: DocumentNode {
|
||||
implementation: DocumentNodeImplementation::ProtoNode(memo::impure_memo::IDENTIFIER),
|
||||
inputs: vec![NodeInput::value(TaggedValue::RasterData(RasterDataTable::default()), true)],
|
||||
inputs: vec![NodeInput::value(TaggedValue::Raster(Default::default()), true)],
|
||||
manual_composition: Some(concrete!(Context)),
|
||||
..Default::default()
|
||||
},
|
||||
@@ -1098,7 +1125,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
..Default::default()
|
||||
},
|
||||
DocumentNode {
|
||||
inputs: vec![NodeInput::network(concrete!(RasterDataTable<CPU>), 0), NodeInput::node(NodeId(0), 0)],
|
||||
inputs: vec![NodeInput::network(concrete!(Table<Raster<CPU>>), 0), NodeInput::node(NodeId(0), 0)],
|
||||
manual_composition: Some(generic!(T)),
|
||||
implementation: DocumentNodeImplementation::ProtoNode(wgpu_executor::texture_upload::upload_texture::IDENTIFIER),
|
||||
..Default::default()
|
||||
@@ -1116,7 +1143,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
.collect(),
|
||||
..Default::default()
|
||||
}),
|
||||
inputs: vec![NodeInput::value(TaggedValue::RasterData(RasterDataTable::default()), true)],
|
||||
inputs: vec![NodeInput::value(TaggedValue::Raster(Default::default()), true)],
|
||||
..Default::default()
|
||||
},
|
||||
persistent_node_metadata: DocumentNodePersistentMetadata {
|
||||
@@ -1195,7 +1222,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
// document_node: DocumentNode {
|
||||
// implementation: DocumentNodeImplementation::proto("graphene_core::raster::CurvesNode"),
|
||||
// inputs: vec![
|
||||
// NodeInput::value(TaggedValue::RasterData(RasterDataTable::default()), true),
|
||||
// NodeInput::value(TaggedValue::Raster(Default::default()), true),
|
||||
// NodeInput::value(TaggedValue::Curve(Default::default()), false),
|
||||
// ],
|
||||
// ..Default::default()
|
||||
@@ -1218,7 +1245,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
exports: vec![NodeInput::node(NodeId(1), 0)],
|
||||
nodes: vec![
|
||||
DocumentNode {
|
||||
inputs: vec![NodeInput::network(concrete!(VectorDataTable), 0)],
|
||||
inputs: vec![NodeInput::network(concrete!(Table<Vector>), 0)],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(memo::monitor::IDENTIFIER),
|
||||
manual_composition: Some(generic!(T)),
|
||||
skip_deduplication: true,
|
||||
@@ -1242,14 +1269,14 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
..Default::default()
|
||||
}),
|
||||
inputs: vec![
|
||||
NodeInput::value(TaggedValue::VectorData(VectorDataTable::default()), true),
|
||||
NodeInput::value(TaggedValue::Vector(Default::default()), true),
|
||||
NodeInput::value(TaggedValue::VectorModification(Default::default()), false),
|
||||
],
|
||||
..Default::default()
|
||||
},
|
||||
persistent_node_metadata: DocumentNodePersistentMetadata {
|
||||
input_metadata: vec![("Vector Data", "TODO").into(), ("Modification", "TODO").into()],
|
||||
output_names: vec!["Vector Data".to_string()],
|
||||
input_metadata: vec![("Content", "TODO").into(), ("Modification", "TODO").into()],
|
||||
output_names: vec!["Modified".to_string()],
|
||||
network_metadata: Some(NodeNetworkMetadata {
|
||||
persistent_metadata: NodeNetworkPersistentMetadata {
|
||||
node_metadata: [
|
||||
@@ -1373,7 +1400,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
}),
|
||||
),
|
||||
InputMetadata::with_name_description_override("Align", "TODO", WidgetOverride::Custom("text_align".to_string())),
|
||||
("Per-Glyph Instances", "Splits each text glyph into its own instance, i.e. row in the table of vector data.").into(),
|
||||
("Per-Glyph Instances", "Splits each text glyph into its own row in the table of vector geometry.").into(),
|
||||
],
|
||||
output_names: vec!["Vector".to_string()],
|
||||
..Default::default()
|
||||
@@ -1495,7 +1522,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
exports: vec![NodeInput::node(NodeId(3), 0)],
|
||||
nodes: vec![
|
||||
DocumentNode {
|
||||
inputs: vec![NodeInput::network(concrete!(VectorDataTable), 0), NodeInput::network(concrete!(vector::style::Fill), 1)],
|
||||
inputs: vec![NodeInput::network(concrete!(Table<Vector>), 0), NodeInput::network(concrete!(vector::style::Fill), 1)],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(path_bool::boolean_operation::IDENTIFIER),
|
||||
manual_composition: Some(generic!(T)),
|
||||
..Default::default()
|
||||
@@ -1526,7 +1553,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
..Default::default()
|
||||
}),
|
||||
inputs: vec![
|
||||
NodeInput::value(TaggedValue::GraphicGroup(GraphicGroupTable::default()), true),
|
||||
NodeInput::value(TaggedValue::Graphic(Default::default()), true),
|
||||
NodeInput::value(TaggedValue::BooleanOperation(path_bool::BooleanOperation::Union), false),
|
||||
],
|
||||
..Default::default()
|
||||
@@ -1576,7 +1603,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
},
|
||||
..Default::default()
|
||||
}),
|
||||
input_metadata: vec![("Group of Paths", "TODO").into(), ("Operation", "TODO").into()],
|
||||
input_metadata: vec![("Content", "TODO").into(), ("Operation", "TODO").into()],
|
||||
output_names: vec!["Vector".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
@@ -1593,14 +1620,14 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
exports: vec![NodeInput::node(NodeId(4), 0)],
|
||||
nodes: [
|
||||
DocumentNode {
|
||||
inputs: vec![NodeInput::network(concrete!(graphene_std::vector::VectorDataTable), 0)],
|
||||
inputs: vec![NodeInput::network(concrete!(Table<Vector>), 0)],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(vector::subpath_segment_lengths::IDENTIFIER),
|
||||
manual_composition: Some(generic!(T)),
|
||||
..Default::default()
|
||||
},
|
||||
DocumentNode {
|
||||
inputs: vec![
|
||||
NodeInput::network(concrete!(graphene_std::vector::VectorDataTable), 0),
|
||||
NodeInput::network(concrete!(Table<Vector>), 0),
|
||||
NodeInput::network(concrete!(vector::misc::PointSpacingType), 1),
|
||||
NodeInput::network(concrete!(f64), 2),
|
||||
NodeInput::network(concrete!(u32), 3),
|
||||
@@ -1639,7 +1666,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
..Default::default()
|
||||
}),
|
||||
inputs: vec![
|
||||
NodeInput::value(TaggedValue::VectorData(graphene_std::vector::VectorDataTable::default()), true),
|
||||
NodeInput::value(TaggedValue::Vector(Default::default()), true),
|
||||
NodeInput::value(TaggedValue::PointSpacingType(Default::default()), false),
|
||||
NodeInput::value(TaggedValue::F64(100.), false),
|
||||
NodeInput::value(TaggedValue::U32(100), false),
|
||||
@@ -1703,7 +1730,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
..Default::default()
|
||||
}),
|
||||
input_metadata: vec![
|
||||
("Vector Data", "The shape to be resampled and converted into a polyline.").into(),
|
||||
("Content", "The shape to be resampled and converted into a polyline.").into(),
|
||||
("Spacing", node_properties::SAMPLE_POLYLINE_TOOLTIP_SPACING).into(),
|
||||
InputMetadata::with_name_description_override(
|
||||
"Separation",
|
||||
@@ -1760,7 +1787,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
nodes: [
|
||||
DocumentNode {
|
||||
inputs: vec![
|
||||
NodeInput::network(concrete!(graphene_std::vector::VectorDataTable), 0),
|
||||
NodeInput::network(concrete!(Table<Vector>), 0),
|
||||
NodeInput::network(concrete!(f64), 1),
|
||||
NodeInput::network(concrete!(u32), 2),
|
||||
],
|
||||
@@ -1794,7 +1821,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
..Default::default()
|
||||
}),
|
||||
inputs: vec![
|
||||
NodeInput::value(TaggedValue::VectorData(graphene_std::vector::VectorDataTable::default()), true),
|
||||
NodeInput::value(TaggedValue::Vector(Default::default()), true),
|
||||
NodeInput::value(TaggedValue::F64(10.), false),
|
||||
NodeInput::value(TaggedValue::U32(0), false),
|
||||
],
|
||||
@@ -1846,7 +1873,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
..Default::default()
|
||||
}),
|
||||
input_metadata: vec![
|
||||
("Vector Data", "TODO").into(),
|
||||
("Content", "TODO").into(),
|
||||
InputMetadata::with_name_description_override(
|
||||
"Separation Disk Diameter",
|
||||
"TODO",
|
||||
@@ -1898,13 +1925,9 @@ fn static_node_properties() -> NodeProperties {
|
||||
map.insert("rectangle_properties".to_string(), Box::new(node_properties::rectangle_properties));
|
||||
map.insert("grid_properties".to_string(), Box::new(node_properties::grid_properties));
|
||||
map.insert("sample_polyline_properties".to_string(), Box::new(node_properties::sample_polyline_properties));
|
||||
map.insert(
|
||||
"identity_properties".to_string(),
|
||||
Box::new(|_node_id, _context| node_properties::string_properties("The identity node passes its data through.")),
|
||||
);
|
||||
map.insert(
|
||||
"monitor_properties".to_string(),
|
||||
Box::new(|_node_id, _context| node_properties::string_properties("The Monitor node is used by the editor to access the data flowing through it.")),
|
||||
Box::new(|_node_id, _context| node_properties::string_properties("Used internally by the editor to obtain a layer thumbnail.")),
|
||||
);
|
||||
map
|
||||
}
|
||||
|
||||
@@ -17,17 +17,16 @@ use crate::messages::portfolio::document::utility_types::wires::{GraphWireStyle,
|
||||
use crate::messages::prelude::*;
|
||||
use crate::messages::tool::common_functionality::auto_panning::AutoPanning;
|
||||
use crate::messages::tool::common_functionality::graph_modification_utils::{self, get_clip_mode};
|
||||
use crate::messages::tool::common_functionality::utility_functions::make_path_editable_is_allowed;
|
||||
use crate::messages::tool::tool_messages::tool_prelude::{Key, MouseMotion};
|
||||
use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo};
|
||||
use bezier_rs::Subpath;
|
||||
use glam::{DAffine2, DVec2, IVec2};
|
||||
use graph_craft::document::value::TaggedValue;
|
||||
use graph_craft::document::{DocumentNodeImplementation, NodeId, NodeInput};
|
||||
use graph_craft::proto::GraphErrors;
|
||||
use graphene_std::math::math_ext::QuadExt;
|
||||
use graphene_std::vector::misc::subpath_to_kurbo_bezpath;
|
||||
use graphene_std::vector::algorithms::bezpath_algorithms::bezpath_is_inside_bezpath;
|
||||
use graphene_std::*;
|
||||
use kurbo::{Line, Point};
|
||||
use kurbo::{DEFAULT_ACCURACY, Shape};
|
||||
use renderer::Quad;
|
||||
use std::cmp::Ordering;
|
||||
|
||||
@@ -126,35 +125,9 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
|
||||
responses.add(NodeGraphMessage::SelectedNodesSet { nodes: vec![new_layer_id] });
|
||||
}
|
||||
NodeGraphMessage::AddPathNode => {
|
||||
let selected_nodes = network_interface.selected_nodes();
|
||||
let mut selected_layers = selected_nodes.selected_layers(network_interface.document_metadata());
|
||||
let first_layer = selected_layers.next();
|
||||
let second_layer = selected_layers.next();
|
||||
let has_single_selection = first_layer.is_some() && second_layer.is_none();
|
||||
|
||||
let compatible_type = first_layer.and_then(|layer| {
|
||||
let graph_layer = graph_modification_utils::NodeGraphLayer::new(layer, &network_interface);
|
||||
graph_layer.horizontal_layer_flow().nth(1).and_then(|node_id| {
|
||||
let (output_type, _) = network_interface.output_type(&node_id, 0, &[]);
|
||||
Some(format!("type:{}", output_type.nested_type()))
|
||||
})
|
||||
});
|
||||
|
||||
let is_compatible = compatible_type.as_deref() == Some("type:Instances<VectorData>");
|
||||
|
||||
if first_layer.is_some() && has_single_selection && is_compatible {
|
||||
if let Some(layer) = first_layer {
|
||||
let node_type = "Path".to_string();
|
||||
let graph_layer = graph_modification_utils::NodeGraphLayer::new(layer, &network_interface);
|
||||
let is_modifiable = matches!(graph_layer.find_input("Path", 1), Some(TaggedValue::VectorModification(_)));
|
||||
if !is_modifiable {
|
||||
responses.add(NodeGraphMessage::CreateNodeInLayerWithTransaction {
|
||||
node_type: node_type.clone(),
|
||||
layer: LayerNodeIdentifier::new_unchecked(layer.to_node()),
|
||||
});
|
||||
responses.add(BroadcastEvent::SelectionChanged);
|
||||
}
|
||||
}
|
||||
if let Some(layer) = make_path_editable_is_allowed(network_interface, network_interface.document_metadata()) {
|
||||
responses.add(NodeGraphMessage::CreateNodeInLayerWithTransaction { node_type: "Path".to_string(), layer });
|
||||
responses.add(BroadcastEvent::SelectionChanged);
|
||||
}
|
||||
}
|
||||
NodeGraphMessage::AddImport => {
|
||||
@@ -984,8 +957,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
|
||||
to_connector_is_layer,
|
||||
GraphWireStyle::Direct,
|
||||
);
|
||||
let mut path_string = String::new();
|
||||
let _ = vector_wire.subpath_to_svg(&mut path_string, DAffine2::IDENTITY);
|
||||
let path_string = vector_wire.to_svg();
|
||||
let wire_path = WirePath {
|
||||
path_string,
|
||||
data_type: self.wire_in_progress_type,
|
||||
@@ -1222,7 +1194,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
|
||||
.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 {
|
||||
let Some(node_bbox) = 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;
|
||||
};
|
||||
@@ -1246,31 +1218,12 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
|
||||
log::debug!("preferences.graph_wire_style: {:?}", preferences.graph_wire_style);
|
||||
let (wire, is_stack) = network_interface.vector_wire_from_input(&input, preferences.graph_wire_style, selection_network_path)?;
|
||||
|
||||
let bbox_rect = kurbo::Rect::new(bounding_box[0].x, bounding_box[0].y, bounding_box[1].x, bounding_box[1].y);
|
||||
let node_bbox = kurbo::Rect::new(node_bbox[0].x, node_bbox[0].y, node_bbox[1].x, node_bbox[1].y).to_path(DEFAULT_ACCURACY);
|
||||
let inside = bezpath_is_inside_bezpath(&wire, &node_bbox, None, None);
|
||||
|
||||
let p1 = DVec2::new(bbox_rect.x0, bbox_rect.y0);
|
||||
let p2 = DVec2::new(bbox_rect.x1, bbox_rect.y0);
|
||||
let p3 = DVec2::new(bbox_rect.x1, bbox_rect.y1);
|
||||
let p4 = DVec2::new(bbox_rect.x0, bbox_rect.y1);
|
||||
let ps = [p1, p2, p3, p4];
|
||||
|
||||
let inside = wire.is_inside_subpath(&Subpath::from_anchors_linear(ps, true), None, None);
|
||||
|
||||
let wire = subpath_to_kurbo_bezpath(wire);
|
||||
|
||||
let intersect = wire.segments().any(|segment| {
|
||||
let rect = kurbo::Rect::new(bounding_box[0].x, bounding_box[0].y, bounding_box[1].x, bounding_box[1].y);
|
||||
|
||||
let top_line = Line::new(Point::new(rect.x0, rect.y0), Point::new(rect.x1, rect.y0));
|
||||
let bottom_line = Line::new(Point::new(rect.x0, rect.y1), Point::new(rect.x1, rect.y1));
|
||||
let left_line = Line::new(Point::new(rect.x0, rect.y0), Point::new(rect.x0, rect.y1));
|
||||
let right_line = Line::new(Point::new(rect.x1, rect.y0), Point::new(rect.x1, rect.y1));
|
||||
|
||||
!segment.intersect_line(top_line).is_empty()
|
||||
|| !segment.intersect_line(bottom_line).is_empty()
|
||||
|| !segment.intersect_line(left_line).is_empty()
|
||||
|| !segment.intersect_line(right_line).is_empty()
|
||||
});
|
||||
let intersect = wire
|
||||
.segments()
|
||||
.any(|segment| node_bbox.segments().filter_map(|segment| segment.as_line()).any(|line| !segment.intersect_line(line).is_empty()));
|
||||
|
||||
(intersect || inside).then_some((input, is_stack))
|
||||
})
|
||||
|
||||
@@ -11,6 +11,7 @@ use glam::{DAffine2, DVec2};
|
||||
use graph_craft::Type;
|
||||
use graph_craft::document::value::TaggedValue;
|
||||
use graph_craft::document::{DocumentNode, DocumentNodeImplementation, NodeId, NodeInput};
|
||||
use graphene_std::NodeInputDecleration;
|
||||
use graphene_std::animation::RealTimeMode;
|
||||
use graphene_std::extract_xy::XY;
|
||||
use graphene_std::path_bool::BooleanOperation;
|
||||
@@ -19,16 +20,10 @@ use graphene_std::raster::{
|
||||
BlendMode, CellularDistanceFunction, CellularReturnType, Color, DomainWarpType, FractalType, LuminanceCalculation, NoiseType, RedGreenBlue, RedGreenBlueAlpha, RelativeAbsolute,
|
||||
SelectiveColorChoice,
|
||||
};
|
||||
use graphene_std::raster_types::{CPU, GPU, RasterDataTable};
|
||||
use graphene_std::text::{Font, TextAlign};
|
||||
use graphene_std::transform::{Footprint, ReferencePoint, Transform};
|
||||
use graphene_std::vector::VectorDataTable;
|
||||
use graphene_std::vector::misc::GridType;
|
||||
use graphene_std::vector::misc::{ArcType, MergeByDistanceAlgorithm};
|
||||
use graphene_std::vector::misc::{CentroidType, PointSpacingType};
|
||||
use graphene_std::vector::style::{Fill, FillChoice, FillType, GradientStops};
|
||||
use graphene_std::vector::style::{GradientType, PaintOrder, StrokeAlign, StrokeCap, StrokeJoin};
|
||||
use graphene_std::{GraphicGroupTable, NodeInputDecleration};
|
||||
use graphene_std::vector::misc::{ArcType, CentroidType, GridType, MergeByDistanceAlgorithm, PointSpacingType};
|
||||
use graphene_std::vector::style::{Fill, FillChoice, FillType, GradientStops, GradientType, PaintOrder, StrokeAlign, StrokeCap, StrokeJoin};
|
||||
|
||||
pub(crate) fn string_properties(text: &str) -> Vec<LayoutGroup> {
|
||||
let widget = TextLabel::new(text).widget_holder();
|
||||
@@ -182,12 +177,6 @@ pub(crate) fn property_from_type(
|
||||
// ==========================
|
||||
Some(x) if x == TypeId::of::<Vec<f64>>() => array_of_number_widget(default_info, TextInput::default()).into(),
|
||||
Some(x) if x == TypeId::of::<Vec<DVec2>>() => array_of_vec2_widget(default_info, TextInput::default()).into(),
|
||||
// ====================
|
||||
// GRAPHICAL DATA TYPES
|
||||
// ====================
|
||||
Some(x) if x == TypeId::of::<VectorDataTable>() => vector_data_widget(default_info).into(),
|
||||
Some(x) if x == TypeId::of::<RasterDataTable<CPU>>() || x == TypeId::of::<RasterDataTable<GPU>>() => raster_widget(default_info).into(),
|
||||
Some(x) if x == TypeId::of::<GraphicGroupTable>() => group_widget(default_info).into(),
|
||||
// ============
|
||||
// STRUCT TYPES
|
||||
// ============
|
||||
@@ -796,33 +785,6 @@ pub fn font_inputs(parameter_widgets_info: ParameterWidgetsInfo) -> (Vec<WidgetH
|
||||
(first_widgets, second_widgets)
|
||||
}
|
||||
|
||||
pub fn vector_data_widget(parameter_widgets_info: ParameterWidgetsInfo) -> Vec<WidgetHolder> {
|
||||
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());
|
||||
|
||||
widgets
|
||||
}
|
||||
|
||||
pub fn raster_widget(parameter_widgets_info: ParameterWidgetsInfo) -> Vec<WidgetHolder> {
|
||||
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());
|
||||
|
||||
widgets
|
||||
}
|
||||
|
||||
pub fn group_widget(parameter_widgets_info: ParameterWidgetsInfo) -> Vec<WidgetHolder> {
|
||||
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());
|
||||
|
||||
widgets
|
||||
}
|
||||
|
||||
pub fn number_widget(parameter_widgets_info: ParameterWidgetsInfo, number_props: NumberInput) -> Vec<WidgetHolder> {
|
||||
let ParameterWidgetsInfo { document_node, node_id, index, .. } = parameter_widgets_info;
|
||||
|
||||
@@ -1882,7 +1844,7 @@ pub fn math_properties(node_id: NodeId, context: &mut NodePropertiesContext) ->
|
||||
let mut expression = x.value.trim().to_string();
|
||||
|
||||
if ["+", "-", "*", "/", "^", "%"].iter().any(|&infix| infix == expression) {
|
||||
expression = format!("A {} B", expression);
|
||||
expression = format!("A {expression} B");
|
||||
} else if expression == "^" {
|
||||
expression = String::from("A^B");
|
||||
}
|
||||
@@ -1997,8 +1959,8 @@ pub mod choice {
|
||||
{
|
||||
let items = E::list()
|
||||
.iter()
|
||||
.map(|group| {
|
||||
group
|
||||
.map(|section| {
|
||||
section
|
||||
.iter()
|
||||
.map(|(item, metadata)| {
|
||||
let updater = updater_factory();
|
||||
@@ -2018,7 +1980,7 @@ pub mod choice {
|
||||
{
|
||||
let items = E::list()
|
||||
.iter()
|
||||
.flat_map(|group| group.iter())
|
||||
.flat_map(|section| section.iter())
|
||||
.map(|(item, var_meta)| {
|
||||
let updater = updater_factory();
|
||||
let committer = committer_factory();
|
||||
@@ -2080,7 +2042,7 @@ pub mod choice {
|
||||
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);
|
||||
log::error!("Could not get document node when building property row for node {node_id:?}");
|
||||
return LayoutGroup::Row { widgets: Vec::new() };
|
||||
};
|
||||
|
||||
|
||||
@@ -9,28 +9,27 @@ pub enum FrontendGraphDataType {
|
||||
#[default]
|
||||
General,
|
||||
Raster,
|
||||
VectorData,
|
||||
Vector,
|
||||
Number,
|
||||
Group,
|
||||
Graphic,
|
||||
Artboard,
|
||||
}
|
||||
|
||||
impl FrontendGraphDataType {
|
||||
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,
|
||||
TaggedValue::Raster(_) => Self::Raster,
|
||||
TaggedValue::Vector(_) => Self::Vector,
|
||||
TaggedValue::U32(_)
|
||||
| TaggedValue::U64(_)
|
||||
| TaggedValue::F64(_)
|
||||
| TaggedValue::DVec2(_)
|
||||
| TaggedValue::OptionalDVec2(_)
|
||||
| TaggedValue::F64Array4(_)
|
||||
| TaggedValue::VecF64(_)
|
||||
| TaggedValue::VecDVec2(_)
|
||||
| TaggedValue::DAffine2(_) => Self::Number,
|
||||
TaggedValue::GraphicGroup(_) | TaggedValue::GraphicElement(_) => Self::Group, // TODO: Is GraphicElement supposed to be included here?
|
||||
TaggedValue::ArtboardGroup(_) => Self::Artboard,
|
||||
TaggedValue::Graphic(_) => Self::Graphic,
|
||||
TaggedValue::Artboard(_) => Self::Artboard,
|
||||
_ => Self::General,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,12 +2,8 @@ pub mod grid_overlays;
|
||||
mod overlays_message;
|
||||
mod overlays_message_handler;
|
||||
pub mod utility_functions;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
#[cfg_attr(not(target_family = "wasm"), path = "utility_types_vello.rs")]
|
||||
pub mod utility_types;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub mod utility_types_vello;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub use utility_types_vello as utility_types;
|
||||
|
||||
#[doc(inline)]
|
||||
pub use overlays_message::{OverlaysMessage, OverlaysMessageDiscriminant};
|
||||
|
||||
@@ -11,20 +11,24 @@ pub struct OverlaysMessageContext<'a> {
|
||||
#[derive(Debug, Clone, Default, ExtractField)]
|
||||
pub struct OverlaysMessageHandler {
|
||||
pub overlay_providers: HashSet<OverlayProvider>,
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
#[cfg(target_family = "wasm")]
|
||||
canvas: Option<web_sys::HtmlCanvasElement>,
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
#[cfg(target_family = "wasm")]
|
||||
context: Option<web_sys::CanvasRenderingContext2d>,
|
||||
}
|
||||
|
||||
#[message_handler_data]
|
||||
impl MessageHandler<OverlaysMessage, OverlaysMessageContext<'_>> for OverlaysMessageHandler {
|
||||
fn process_message(&mut self, message: OverlaysMessage, responses: &mut VecDeque<Message>, context: OverlaysMessageContext) {
|
||||
let OverlaysMessageContext { visibility_settings, ipp, .. } = context;
|
||||
let device_pixel_ratio = context.device_pixel_ratio;
|
||||
let OverlaysMessageContext {
|
||||
visibility_settings,
|
||||
ipp,
|
||||
device_pixel_ratio,
|
||||
..
|
||||
} = context;
|
||||
|
||||
match message {
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
#[cfg(target_family = "wasm")]
|
||||
OverlaysMessage::Draw => {
|
||||
use super::utility_functions::overlay_canvas_element;
|
||||
use super::utility_types::OverlayContext;
|
||||
@@ -68,39 +72,26 @@ impl MessageHandler<OverlaysMessage, OverlaysMessageContext<'_>> for OverlaysMes
|
||||
}
|
||||
}
|
||||
}
|
||||
#[cfg(test)]
|
||||
OverlaysMessage::Draw => {}
|
||||
#[cfg(all(not(target_arch = "wasm32"), not(test)))]
|
||||
#[cfg(all(not(target_family = "wasm"), not(test)))]
|
||||
OverlaysMessage::Draw => {
|
||||
use super::utility_types::OverlayContext;
|
||||
use vello::Scene;
|
||||
|
||||
let size = ipp.viewport_bounds.size().as_uvec2();
|
||||
let size = ipp.viewport_bounds.size();
|
||||
|
||||
let scene = Scene::new();
|
||||
let overlay_context = OverlayContext::new(size, device_pixel_ratio, visibility_settings);
|
||||
|
||||
if visibility_settings.all() {
|
||||
let overlay_context = OverlayContext {
|
||||
scene,
|
||||
size: size.as_dvec2(),
|
||||
device_pixel_ratio,
|
||||
visibility_settings,
|
||||
};
|
||||
|
||||
responses.add(DocumentMessage::GridOverlays(overlay_context.clone()));
|
||||
|
||||
for provider in &self.overlay_providers {
|
||||
let overlay_context = OverlayContext {
|
||||
scene: Scene::new(),
|
||||
size: size.as_dvec2(),
|
||||
device_pixel_ratio,
|
||||
visibility_settings,
|
||||
};
|
||||
responses.add(provider(overlay_context));
|
||||
responses.add(provider(overlay_context.clone()));
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Render the Vello scene to a texture and display it
|
||||
responses.add(FrontendMessage::RenderOverlays(overlay_context));
|
||||
}
|
||||
#[cfg(all(not(target_family = "wasm"), test))]
|
||||
OverlaysMessage::Draw => {
|
||||
let _ = (responses, visibility_settings, ipp, device_pixel_ratio);
|
||||
}
|
||||
OverlaysMessage::AddProvider(message) => {
|
||||
self.overlay_providers.insert(message);
|
||||
|
||||
Binary file not shown.
@@ -5,7 +5,7 @@ use crate::messages::tool::common_functionality::shape_editor::{SelectedLayerSta
|
||||
use crate::messages::tool::tool_messages::tool_prelude::{DocumentMessageHandler, PreferencesMessageHandler};
|
||||
use bezier_rs::{Bezier, BezierHandles};
|
||||
use glam::{DAffine2, DVec2};
|
||||
use graphene_std::vector::ManipulatorPointId;
|
||||
use graphene_std::vector::misc::ManipulatorPointId;
|
||||
use graphene_std::vector::{PointId, SegmentId};
|
||||
use wasm_bindgen::JsCast;
|
||||
|
||||
@@ -42,9 +42,9 @@ pub fn selected_segments(network_interface: &NodeNetworkInterface, shape_editor:
|
||||
// TODO: Currently if there are two duplicate layers, both of their segments get overlays
|
||||
// Adding segments which are are connected to selected anchors
|
||||
for layer in network_interface.selected_nodes().selected_layers(network_interface.document_metadata()) {
|
||||
let Some(vector_data) = network_interface.compute_modified_vector(layer) else { continue };
|
||||
let Some(vector) = network_interface.compute_modified_vector(layer) else { continue };
|
||||
|
||||
for (segment_id, _bezier, start, end) in vector_data.segment_bezier_iter() {
|
||||
for (segment_id, _bezier, start, end) in vector.segment_bezier_iter() {
|
||||
if selected_anchors.contains(&start) || selected_anchors.contains(&end) {
|
||||
selected_segments.push(segment_id);
|
||||
}
|
||||
@@ -118,14 +118,14 @@ pub fn path_overlays(document: &DocumentMessageHandler, draw_handles: DrawHandle
|
||||
let display_anchors = overlay_context.visibility_settings.anchors();
|
||||
|
||||
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 Some(vector) = document.network_interface.compute_modified_vector(layer) else { continue };
|
||||
let transform = document.metadata().transform_to_viewport_if_feeds(layer, &document.network_interface);
|
||||
if display_path {
|
||||
overlay_context.outline_vector(&vector_data, transform);
|
||||
overlay_context.outline_vector(&vector, transform);
|
||||
}
|
||||
|
||||
// Get the selected segments and then add a bold line overlay on them
|
||||
for (segment_id, bezier, _, _) in vector_data.segment_bezier_iter() {
|
||||
for (segment_id, bezier, _, _) in vector.segment_bezier_iter() {
|
||||
let Some(selected_shape_state) = shape_editor.selected_shape_state.get_mut(&layer) else {
|
||||
continue;
|
||||
};
|
||||
@@ -139,30 +139,30 @@ pub fn path_overlays(document: &DocumentMessageHandler, draw_handles: DrawHandle
|
||||
let is_selected = |point: ManipulatorPointId| selected.is_some_and(|selected| selected.is_point_selected(point));
|
||||
|
||||
if display_handles {
|
||||
let opposite_handles_data: Vec<(PointId, SegmentId)> = shape_editor.selected_points().filter_map(|point_id| vector_data.adjacent_segment(point_id)).collect();
|
||||
let opposite_handles_data: Vec<(PointId, SegmentId)> = shape_editor.selected_points().filter_map(|point_id| vector.adjacent_segment(point_id)).collect();
|
||||
|
||||
match draw_handles {
|
||||
DrawHandles::All => {
|
||||
vector_data.segment_bezier_iter().for_each(|(segment_id, bezier, _start, _end)| {
|
||||
vector.segment_bezier_iter().for_each(|(segment_id, bezier, _start, _end)| {
|
||||
overlay_bezier_handles(bezier, segment_id, transform, is_selected, overlay_context);
|
||||
});
|
||||
}
|
||||
DrawHandles::SelectedAnchors(ref selected_segments) => {
|
||||
vector_data
|
||||
vector
|
||||
.segment_bezier_iter()
|
||||
.filter(|(segment_id, ..)| selected_segments.contains(segment_id))
|
||||
.for_each(|(segment_id, bezier, _start, _end)| {
|
||||
overlay_bezier_handles(bezier, segment_id, transform, is_selected, overlay_context);
|
||||
});
|
||||
|
||||
for (segment_id, bezier, start, end) in vector_data.segment_bezier_iter() {
|
||||
for (segment_id, bezier, start, end) in vector.segment_bezier_iter() {
|
||||
if let Some((corresponding_anchor, _)) = opposite_handles_data.iter().find(|(_, adj_segment_id)| adj_segment_id == &segment_id) {
|
||||
overlay_bezier_handle_specific_point(bezier, segment_id, (start, end), *corresponding_anchor, transform, is_selected, overlay_context);
|
||||
}
|
||||
}
|
||||
}
|
||||
DrawHandles::FrontierHandles(ref segment_endpoints) => {
|
||||
vector_data
|
||||
vector
|
||||
.segment_bezier_iter()
|
||||
.filter(|(segment_id, ..)| segment_endpoints.contains_key(segment_id))
|
||||
.for_each(|(segment_id, bezier, start, end)| {
|
||||
@@ -179,7 +179,7 @@ pub fn path_overlays(document: &DocumentMessageHandler, draw_handles: DrawHandle
|
||||
}
|
||||
|
||||
if display_anchors {
|
||||
for (&id, &position) in vector_data.point_domain.ids().iter().zip(vector_data.point_domain.positions()) {
|
||||
for (&id, &position) in vector.point_domain.ids().iter().zip(vector.point_domain.positions()) {
|
||||
overlay_context.manipulator_anchor(transform.transform_point2(position), is_selected(ManipulatorPointId::Anchor(id)), None);
|
||||
}
|
||||
}
|
||||
@@ -192,7 +192,7 @@ pub fn path_endpoint_overlays(document: &DocumentMessageHandler, shape_editor: &
|
||||
}
|
||||
|
||||
for layer in document.network_interface.selected_nodes().selected_layers(document.metadata()) {
|
||||
let Some(vector_data) = document.network_interface.compute_modified_vector(layer) else {
|
||||
let Some(vector) = document.network_interface.compute_modified_vector(layer) else {
|
||||
continue;
|
||||
};
|
||||
//let document_to_viewport = document.navigation_handler.calculate_offset_transform(overlay_context.size / 2., &document.document_ptz);
|
||||
@@ -200,8 +200,8 @@ pub fn path_endpoint_overlays(document: &DocumentMessageHandler, shape_editor: &
|
||||
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));
|
||||
|
||||
for point in vector_data.extendable_points(preferences.vector_meshes) {
|
||||
let Some(position) = vector_data.point_domain.position_from_id(point) else { continue };
|
||||
for point in vector.extendable_points(preferences.vector_meshes) {
|
||||
let Some(position) = vector.point_domain.position_from_id(point) else { continue };
|
||||
let position = transform.transform_point2(position);
|
||||
overlay_context.manipulator_anchor(position, is_selected(selected, ManipulatorPointId::Anchor(point)), None);
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ use super::utility_functions::overlay_canvas_context;
|
||||
use crate::consts::{
|
||||
ARC_SWEEP_GIZMO_RADIUS, COLOR_OVERLAY_BLUE, COLOR_OVERLAY_BLUE_50, 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,
|
||||
PIVOT_CROSSHAIR_LENGTH, PIVOT_CROSSHAIR_THICKNESS, PIVOT_DIAMETER, SEGMENT_SELECTED_THICKNESS,
|
||||
};
|
||||
use crate::messages::prelude::Message;
|
||||
use bezier_rs::{Bezier, Subpath};
|
||||
@@ -12,7 +12,7 @@ use glam::{DAffine2, DVec2};
|
||||
use graphene_std::Color;
|
||||
use graphene_std::math::quad::Quad;
|
||||
use graphene_std::vector::click_target::ClickTargetType;
|
||||
use graphene_std::vector::{PointId, SegmentId, VectorData};
|
||||
use graphene_std::vector::{PointId, SegmentId, Vector};
|
||||
use std::collections::HashMap;
|
||||
use wasm_bindgen::{JsCast, JsValue};
|
||||
use web_sys::{OffscreenCanvas, OffscreenCanvasRenderingContext2d};
|
||||
@@ -23,7 +23,7 @@ pub fn empty_provider() -> OverlayProvider {
|
||||
|_| Message::NoOp
|
||||
}
|
||||
|
||||
// Types of overlays used by DocumentMessage to enable/disable select group of overlays in the frontend
|
||||
/// Types of overlays used by DocumentMessage to enable/disable the selected set of viewport overlays.
|
||||
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize, specta::Type)]
|
||||
pub enum OverlaysType {
|
||||
ArtboardName,
|
||||
@@ -294,6 +294,147 @@ impl OverlayContext {
|
||||
self.end_dpi_aware_transform();
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn dashed_ellipse(
|
||||
&mut self,
|
||||
center: DVec2,
|
||||
radius_x: f64,
|
||||
radius_y: f64,
|
||||
rotation: Option<f64>,
|
||||
start_angle: Option<f64>,
|
||||
end_angle: Option<f64>,
|
||||
counterclockwise: Option<bool>,
|
||||
color_fill: Option<&str>,
|
||||
color_stroke: Option<&str>,
|
||||
dash_width: Option<f64>,
|
||||
dash_gap_width: Option<f64>,
|
||||
dash_offset: Option<f64>,
|
||||
) {
|
||||
let color_stroke = color_stroke.unwrap_or(COLOR_OVERLAY_BLUE);
|
||||
let center = center.round();
|
||||
|
||||
self.start_dpi_aware_transform();
|
||||
|
||||
if let Some(dash_width) = dash_width {
|
||||
let dash_gap_width = dash_gap_width.unwrap_or(1.);
|
||||
let array = js_sys::Array::new();
|
||||
array.push(&JsValue::from(dash_width));
|
||||
array.push(&JsValue::from(dash_gap_width));
|
||||
|
||||
if let Some(dash_offset) = dash_offset {
|
||||
if dash_offset != 0. {
|
||||
self.render_context.set_line_dash_offset(dash_offset);
|
||||
}
|
||||
}
|
||||
|
||||
self.render_context
|
||||
.set_line_dash(&JsValue::from(array))
|
||||
.map_err(|error| log::warn!("Error drawing dashed line: {:?}", error))
|
||||
.ok();
|
||||
}
|
||||
|
||||
self.render_context.begin_path();
|
||||
self.render_context
|
||||
.ellipse_with_anticlockwise(
|
||||
center.x,
|
||||
center.y,
|
||||
radius_x,
|
||||
radius_y,
|
||||
rotation.unwrap_or_default(),
|
||||
start_angle.unwrap_or_default(),
|
||||
end_angle.unwrap_or(TAU),
|
||||
counterclockwise.unwrap_or_default(),
|
||||
)
|
||||
.expect("Failed to draw ellipse");
|
||||
self.render_context.set_stroke_style_str(color_stroke);
|
||||
|
||||
if let Some(fill_color) = color_fill {
|
||||
self.render_context.set_fill_style_str(fill_color);
|
||||
self.render_context.fill();
|
||||
}
|
||||
self.render_context.stroke();
|
||||
|
||||
// Reset the dash pattern back to solid
|
||||
if dash_width.is_some() {
|
||||
self.render_context
|
||||
.set_line_dash(&JsValue::from(js_sys::Array::new()))
|
||||
.map_err(|error| log::warn!("Error drawing dashed line: {:?}", error))
|
||||
.ok();
|
||||
}
|
||||
if dash_offset.is_some() && dash_offset != Some(0.) {
|
||||
self.render_context.set_line_dash_offset(0.);
|
||||
}
|
||||
|
||||
self.end_dpi_aware_transform();
|
||||
}
|
||||
|
||||
pub fn dashed_circle(
|
||||
&mut self,
|
||||
position: DVec2,
|
||||
radius: f64,
|
||||
color_fill: Option<&str>,
|
||||
color_stroke: Option<&str>,
|
||||
dash_width: Option<f64>,
|
||||
dash_gap_width: Option<f64>,
|
||||
dash_offset: Option<f64>,
|
||||
transform: Option<DAffine2>,
|
||||
) {
|
||||
let color_stroke = color_stroke.unwrap_or(COLOR_OVERLAY_BLUE);
|
||||
let position = position.round();
|
||||
|
||||
self.start_dpi_aware_transform();
|
||||
|
||||
if let Some(transform) = transform {
|
||||
let [a, b, c, d, e, f] = transform.to_cols_array();
|
||||
self.render_context.transform(a, b, c, d, e, f).expect("Failed to transform circle");
|
||||
}
|
||||
|
||||
if let Some(dash_width) = dash_width {
|
||||
let dash_gap_width = dash_gap_width.unwrap_or(1.);
|
||||
let array = js_sys::Array::new();
|
||||
array.push(&JsValue::from(dash_width));
|
||||
array.push(&JsValue::from(dash_gap_width));
|
||||
|
||||
if let Some(dash_offset) = dash_offset {
|
||||
if dash_offset != 0. {
|
||||
self.render_context.set_line_dash_offset(dash_offset);
|
||||
}
|
||||
}
|
||||
|
||||
self.render_context
|
||||
.set_line_dash(&JsValue::from(array))
|
||||
.map_err(|error| log::warn!("Error drawing dashed line: {:?}", error))
|
||||
.ok();
|
||||
}
|
||||
|
||||
self.render_context.begin_path();
|
||||
self.render_context.arc(position.x, position.y, radius, 0., TAU).expect("Failed to draw the circle");
|
||||
self.render_context.set_stroke_style_str(color_stroke);
|
||||
|
||||
if let Some(fill_color) = color_fill {
|
||||
self.render_context.set_fill_style_str(fill_color);
|
||||
self.render_context.fill();
|
||||
}
|
||||
self.render_context.stroke();
|
||||
|
||||
// Reset the dash pattern back to solid
|
||||
if dash_width.is_some() {
|
||||
self.render_context
|
||||
.set_line_dash(&JsValue::from(js_sys::Array::new()))
|
||||
.map_err(|error| log::warn!("Error drawing dashed line: {:?}", error))
|
||||
.ok();
|
||||
}
|
||||
if dash_offset.is_some() && dash_offset != Some(0.) {
|
||||
self.render_context.set_line_dash_offset(0.);
|
||||
}
|
||||
|
||||
self.end_dpi_aware_transform();
|
||||
}
|
||||
|
||||
pub fn circle(&mut self, position: DVec2, radius: f64, color_fill: Option<&str>, color_stroke: Option<&str>) {
|
||||
self.dashed_circle(position, radius, color_fill, color_stroke, None, None, None, None);
|
||||
}
|
||||
|
||||
pub fn manipulator_handle(&mut self, position: DVec2, selected: bool, color: Option<&str>) {
|
||||
self.start_dpi_aware_transform();
|
||||
|
||||
@@ -319,6 +460,42 @@ impl OverlayContext {
|
||||
self.square(position, None, Some(color_fill), Some(color_stroke));
|
||||
}
|
||||
|
||||
pub fn hover_manipulator_handle(&mut self, position: DVec2, selected: bool) {
|
||||
self.start_dpi_aware_transform();
|
||||
|
||||
let position = position.round() - DVec2::splat(0.5);
|
||||
|
||||
self.render_context.begin_path();
|
||||
self.render_context
|
||||
.arc(position.x, position.y, (MANIPULATOR_GROUP_MARKER_SIZE + 2.) / 2., 0., TAU)
|
||||
.expect("Failed to draw the circle");
|
||||
|
||||
self.render_context.set_fill_style_str(COLOR_OVERLAY_BLUE_50);
|
||||
self.render_context.set_stroke_style_str(COLOR_OVERLAY_BLUE_50);
|
||||
self.render_context.fill();
|
||||
self.render_context.stroke();
|
||||
|
||||
self.render_context.begin_path();
|
||||
self.render_context
|
||||
.arc(position.x, position.y, MANIPULATOR_GROUP_MARKER_SIZE / 2., 0., TAU)
|
||||
.expect("Failed to draw the circle");
|
||||
|
||||
let color_fill = if selected { COLOR_OVERLAY_BLUE } else { COLOR_OVERLAY_WHITE };
|
||||
|
||||
self.render_context.set_fill_style_str(color_fill);
|
||||
self.render_context.set_stroke_style_str(COLOR_OVERLAY_BLUE);
|
||||
self.render_context.fill();
|
||||
self.render_context.stroke();
|
||||
|
||||
self.end_dpi_aware_transform();
|
||||
}
|
||||
|
||||
pub fn hover_manipulator_anchor(&mut self, position: DVec2, selected: bool) {
|
||||
self.square(position, Some(MANIPULATOR_GROUP_MARKER_SIZE + 2.), Some(COLOR_OVERLAY_BLUE_50), Some(COLOR_OVERLAY_BLUE_50));
|
||||
let color_fill = if selected { COLOR_OVERLAY_BLUE } else { COLOR_OVERLAY_WHITE };
|
||||
self.square(position, None, Some(color_fill), Some(COLOR_OVERLAY_BLUE));
|
||||
}
|
||||
|
||||
/// Transforms the canvas context to adjust for DPI scaling
|
||||
///
|
||||
/// Overwrites all existing tranforms. This operation can be reversed with [`Self::reset_transform`].
|
||||
@@ -374,23 +551,6 @@ impl OverlayContext {
|
||||
self.end_dpi_aware_transform();
|
||||
}
|
||||
|
||||
pub fn circle(&mut self, position: DVec2, radius: f64, color_fill: Option<&str>, color_stroke: Option<&str>) {
|
||||
let color_fill = color_fill.unwrap_or(COLOR_OVERLAY_WHITE);
|
||||
let color_stroke = color_stroke.unwrap_or(COLOR_OVERLAY_BLUE);
|
||||
let position = position.round();
|
||||
|
||||
self.start_dpi_aware_transform();
|
||||
|
||||
self.render_context.begin_path();
|
||||
self.render_context.arc(position.x, position.y, radius, 0., TAU).expect("Failed to draw the circle");
|
||||
self.render_context.set_fill_style_str(color_fill);
|
||||
self.render_context.set_stroke_style_str(color_stroke);
|
||||
self.render_context.fill();
|
||||
self.render_context.stroke();
|
||||
|
||||
self.end_dpi_aware_transform();
|
||||
}
|
||||
|
||||
pub fn draw_arc(&mut self, center: DVec2, radius: f64, start_from: f64, end_at: f64) {
|
||||
let segments = ((end_at - start_from).abs() / (std::f64::consts::PI / 4.)).ceil() as usize;
|
||||
let step = (end_at - start_from) / segments as f64;
|
||||
@@ -591,18 +751,18 @@ impl OverlayContext {
|
||||
}
|
||||
|
||||
pub fn arc_sweep_angle(&mut self, offset_angle: f64, angle: f64, end_point_position: DVec2, bold_radius: f64, pivot: DVec2, text: &str, transform: DAffine2) {
|
||||
self.manipulator_handle(end_point_position, true, Some(COLOR_OVERLAY_RED));
|
||||
self.manipulator_handle(end_point_position, true, None);
|
||||
self.draw_arc_gizmo_angle(pivot, bold_radius, ARC_SWEEP_GIZMO_RADIUS, offset_angle, angle.to_radians());
|
||||
self.text(&text, COLOR_OVERLAY_BLUE, None, transform, 16., [Pivot::Middle, Pivot::Middle]);
|
||||
}
|
||||
|
||||
/// 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) {
|
||||
pub fn outline_vector(&mut self, vector: &Vector, transform: DAffine2) {
|
||||
self.start_dpi_aware_transform();
|
||||
|
||||
self.render_context.begin_path();
|
||||
let mut last_point = None;
|
||||
for (_, bezier, start_id, end_id) in vector_data.segment_bezier_iter() {
|
||||
for (_, bezier, start_id, end_id) in vector.segment_bezier_iter() {
|
||||
let move_to = last_point != Some(start_id);
|
||||
last_point = Some(end_id);
|
||||
|
||||
@@ -634,7 +794,7 @@ impl OverlayContext {
|
||||
self.render_context.begin_path();
|
||||
self.bezier_command(bezier, transform, true);
|
||||
self.render_context.set_stroke_style_str(COLOR_OVERLAY_BLUE);
|
||||
self.render_context.set_line_width(4.);
|
||||
self.render_context.set_line_width(SEGMENT_SELECTED_THICKNESS);
|
||||
self.render_context.stroke();
|
||||
|
||||
self.render_context.set_line_width(1.);
|
||||
@@ -648,7 +808,7 @@ impl OverlayContext {
|
||||
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_line_width(4.);
|
||||
self.render_context.set_line_width(SEGMENT_SELECTED_THICKNESS);
|
||||
self.render_context.stroke();
|
||||
|
||||
self.render_context.set_line_width(1.);
|
||||
|
||||
@@ -10,9 +10,12 @@ use core::f64::consts::{FRAC_PI_2, PI, TAU};
|
||||
use glam::{DAffine2, DVec2};
|
||||
use graphene_std::Color;
|
||||
use graphene_std::math::quad::Quad;
|
||||
use graphene_std::table::Table;
|
||||
use graphene_std::text::{TextAlign, TypesettingConfig, load_font, to_path};
|
||||
use graphene_std::vector::click_target::ClickTargetType;
|
||||
use graphene_std::vector::{PointId, SegmentId, VectorData};
|
||||
use graphene_std::vector::{PointId, SegmentId, Vector};
|
||||
use std::collections::HashMap;
|
||||
use std::sync::{Arc, Mutex, MutexGuard};
|
||||
use vello::Scene;
|
||||
use vello::kurbo::{self, BezPath};
|
||||
use vello::peniko;
|
||||
@@ -23,7 +26,7 @@ pub fn empty_provider() -> OverlayProvider {
|
||||
|_| Message::NoOp
|
||||
}
|
||||
|
||||
// Types of overlays used by DocumentMessage to enable/disable select group of overlays in the frontend
|
||||
/// Types of overlays used by DocumentMessage to enable/disable the selected set of viewport overlays.
|
||||
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize, specta::Type)]
|
||||
pub enum OverlaysType {
|
||||
ArtboardName,
|
||||
@@ -132,12 +135,12 @@ impl OverlaysVisibilitySettings {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, serde::Serialize, serde::Deserialize, specta::Type)]
|
||||
#[derive(serde::Serialize, serde::Deserialize, specta::Type)]
|
||||
pub struct OverlayContext {
|
||||
// Serde functionality isn't used but is required by the message system macros
|
||||
#[serde(skip)]
|
||||
#[specta(skip)]
|
||||
pub scene: Scene,
|
||||
internal: Arc<Mutex<OverlayContextInternal>>,
|
||||
pub size: DVec2,
|
||||
// The device pixel ratio is a property provided by the browser window and is the CSS pixel size divided by the physical monitor's pixel size.
|
||||
// It allows better pixel density of visualizations on high-DPI displays where the OS display scaling is not 100%, or where the browser is zoomed.
|
||||
@@ -145,6 +148,22 @@ pub struct OverlayContext {
|
||||
pub visibility_settings: OverlaysVisibilitySettings,
|
||||
}
|
||||
|
||||
impl Clone for OverlayContext {
|
||||
fn clone(&self) -> Self {
|
||||
let internal = self.internal.lock().expect("Failed to lock internal overlay context");
|
||||
let size = internal.size;
|
||||
let device_pixel_ratio = internal.device_pixel_ratio;
|
||||
let visibility_settings = internal.visibility_settings;
|
||||
drop(internal); // Explicitly release the lock before cloning the Arc<Mutex<_>>
|
||||
Self {
|
||||
internal: self.internal.clone(),
|
||||
size,
|
||||
device_pixel_ratio,
|
||||
visibility_settings,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Manual implementations since Scene doesn't implement PartialEq or Debug
|
||||
impl PartialEq for OverlayContext {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
@@ -167,7 +186,7 @@ impl std::fmt::Debug for OverlayContext {
|
||||
impl Default for OverlayContext {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
scene: Scene::new(),
|
||||
internal: Mutex::new(OverlayContextInternal::default()).into(),
|
||||
size: DVec2::ZERO,
|
||||
device_pixel_ratio: 1.0,
|
||||
visibility_settings: OverlaysVisibilitySettings::default(),
|
||||
@@ -181,6 +200,236 @@ impl core::hash::Hash for OverlayContext {
|
||||
}
|
||||
|
||||
impl OverlayContext {
|
||||
pub(super) fn new(size: DVec2, device_pixel_ratio: f64, visibility_settings: OverlaysVisibilitySettings) -> Self {
|
||||
Self {
|
||||
internal: Arc::new(Mutex::new(OverlayContextInternal::new(size, device_pixel_ratio, visibility_settings))),
|
||||
size,
|
||||
device_pixel_ratio,
|
||||
visibility_settings,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn take_scene(self) -> Scene {
|
||||
let mut internal = self.internal.lock().expect("Failed to lock internal overlay context");
|
||||
std::mem::take(&mut *internal).scene
|
||||
}
|
||||
|
||||
fn internal(&'_ self) -> MutexGuard<'_, OverlayContextInternal> {
|
||||
self.internal.lock().expect("Failed to lock internal overlay context")
|
||||
}
|
||||
|
||||
pub fn quad(&mut self, quad: Quad, stroke_color: Option<&str>, color_fill: Option<&str>) {
|
||||
self.internal().quad(quad, stroke_color, color_fill);
|
||||
}
|
||||
|
||||
pub fn draw_triangle(&mut self, base: DVec2, direction: DVec2, size: f64, color_fill: Option<&str>, color_stroke: Option<&str>) {
|
||||
self.internal().draw_triangle(base, direction, size, color_fill, color_stroke);
|
||||
}
|
||||
|
||||
pub fn dashed_quad(&mut self, quad: Quad, stroke_color: Option<&str>, color_fill: Option<&str>, dash_width: Option<f64>, dash_gap_width: Option<f64>, dash_offset: Option<f64>) {
|
||||
self.internal().dashed_quad(quad, stroke_color, color_fill, dash_width, dash_gap_width, dash_offset);
|
||||
}
|
||||
|
||||
pub fn polygon(&mut self, polygon: &[DVec2], stroke_color: Option<&str>, color_fill: Option<&str>) {
|
||||
self.internal().polygon(polygon, stroke_color, color_fill);
|
||||
}
|
||||
|
||||
pub fn dashed_polygon(&mut self, polygon: &[DVec2], stroke_color: Option<&str>, color_fill: Option<&str>, dash_width: Option<f64>, dash_gap_width: Option<f64>, dash_offset: Option<f64>) {
|
||||
self.internal().dashed_polygon(polygon, stroke_color, color_fill, dash_width, dash_gap_width, dash_offset);
|
||||
}
|
||||
|
||||
pub fn line(&mut self, start: DVec2, end: DVec2, color: Option<&str>, thickness: Option<f64>) {
|
||||
self.internal().line(start, end, color, thickness);
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn dashed_line(&mut self, start: DVec2, end: DVec2, color: Option<&str>, thickness: Option<f64>, dash_width: Option<f64>, dash_gap_width: Option<f64>, dash_offset: Option<f64>) {
|
||||
self.internal().dashed_line(start, end, color, thickness, dash_width, dash_gap_width, dash_offset);
|
||||
}
|
||||
|
||||
pub fn hover_manipulator_handle(&mut self, position: DVec2, selected: bool) {
|
||||
self.internal().hover_manipulator_handle(position, selected);
|
||||
}
|
||||
|
||||
pub fn hover_manipulator_anchor(&mut self, position: DVec2, selected: bool) {
|
||||
self.internal().hover_manipulator_anchor(position, selected);
|
||||
}
|
||||
|
||||
pub fn manipulator_handle(&mut self, position: DVec2, selected: bool, color: Option<&str>) {
|
||||
self.internal().manipulator_handle(position, selected, color);
|
||||
}
|
||||
|
||||
pub fn manipulator_anchor(&mut self, position: DVec2, selected: bool, color: Option<&str>) {
|
||||
self.internal().manipulator_anchor(position, selected, color);
|
||||
}
|
||||
|
||||
pub fn square(&mut self, position: DVec2, size: Option<f64>, color_fill: Option<&str>, color_stroke: Option<&str>) {
|
||||
self.internal().square(position, size, color_fill, color_stroke);
|
||||
}
|
||||
|
||||
pub fn pixel(&mut self, position: DVec2, color: Option<&str>) {
|
||||
self.internal().pixel(position, color);
|
||||
}
|
||||
|
||||
pub fn circle(&mut self, position: DVec2, radius: f64, color_fill: Option<&str>, color_stroke: Option<&str>) {
|
||||
self.internal().circle(position, radius, color_fill, color_stroke);
|
||||
}
|
||||
|
||||
pub fn dashed_ellipse(
|
||||
&mut self,
|
||||
center: DVec2,
|
||||
radius_x: f64,
|
||||
radius_y: f64,
|
||||
rotation: Option<f64>,
|
||||
start_angle: Option<f64>,
|
||||
end_angle: Option<f64>,
|
||||
counterclockwise: Option<bool>,
|
||||
color_fill: Option<&str>,
|
||||
color_stroke: Option<&str>,
|
||||
dash_width: Option<f64>,
|
||||
dash_gap_width: Option<f64>,
|
||||
dash_offset: Option<f64>,
|
||||
) {
|
||||
self.internal().dashed_ellipse(
|
||||
center,
|
||||
radius_x,
|
||||
radius_y,
|
||||
rotation,
|
||||
start_angle,
|
||||
end_angle,
|
||||
counterclockwise,
|
||||
color_fill,
|
||||
color_stroke,
|
||||
dash_width,
|
||||
dash_gap_width,
|
||||
dash_offset,
|
||||
);
|
||||
}
|
||||
|
||||
pub fn draw_arc(&mut self, center: DVec2, radius: f64, start_from: f64, end_at: f64) {
|
||||
self.internal().draw_arc(center, radius, start_from, end_at);
|
||||
}
|
||||
|
||||
pub fn draw_arc_gizmo_angle(&mut self, pivot: DVec2, bold_radius: f64, arc_radius: f64, offset_angle: f64, angle: f64) {
|
||||
self.internal().draw_arc_gizmo_angle(pivot, bold_radius, arc_radius, offset_angle, angle);
|
||||
}
|
||||
|
||||
pub fn draw_angle(&mut self, pivot: DVec2, radius: f64, arc_radius: f64, offset_angle: f64, angle: f64) {
|
||||
self.internal().draw_angle(pivot, radius, arc_radius, offset_angle, angle);
|
||||
}
|
||||
|
||||
pub fn draw_scale(&mut self, start: DVec2, scale: f64, radius: f64, text: &str) {
|
||||
self.internal().draw_scale(start, scale, radius, text);
|
||||
}
|
||||
|
||||
pub fn compass_rose(&mut self, compass_center: DVec2, angle: f64, show_compass_with_hover_ring: Option<bool>) {
|
||||
self.internal().compass_rose(compass_center, angle, show_compass_with_hover_ring);
|
||||
}
|
||||
|
||||
pub fn pivot(&mut self, position: DVec2, angle: f64) {
|
||||
self.internal().pivot(position, angle);
|
||||
}
|
||||
|
||||
pub fn dowel_pin(&mut self, position: DVec2, angle: f64, color: Option<&str>) {
|
||||
self.internal().dowel_pin(position, angle, color);
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn arc_sweep_angle(&mut self, offset_angle: f64, angle: f64, end_point_position: DVec2, bold_radius: f64, pivot: DVec2, text: &str, transform: DAffine2) {
|
||||
self.internal().arc_sweep_angle(offset_angle, angle, end_point_position, bold_radius, pivot, text, transform);
|
||||
}
|
||||
|
||||
/// Used by the Pen and Path tools to outline the path of the shape.
|
||||
pub fn outline_vector(&mut self, vector: &Vector, transform: DAffine2) {
|
||||
self.internal().outline_vector(vector, transform);
|
||||
}
|
||||
|
||||
/// Used by the Pen tool in order to show how the bezier curve would look like.
|
||||
pub fn outline_bezier(&mut self, bezier: Bezier, transform: DAffine2) {
|
||||
self.internal().outline_bezier(bezier, transform);
|
||||
}
|
||||
|
||||
/// Used by the path tool segment mode in order to show the selected segments.
|
||||
pub fn outline_select_bezier(&mut self, bezier: Bezier, transform: DAffine2) {
|
||||
self.internal().outline_select_bezier(bezier, transform);
|
||||
}
|
||||
|
||||
pub fn outline_overlay_bezier(&mut self, bezier: Bezier, transform: DAffine2) {
|
||||
self.internal().outline_overlay_bezier(bezier, transform);
|
||||
}
|
||||
|
||||
/// Used by the Select tool to outline a path or a free point when selected or hovered.
|
||||
pub fn outline(&mut self, target_types: impl Iterator<Item = impl Borrow<ClickTargetType>>, transform: DAffine2, color: Option<&str>) {
|
||||
self.internal().outline(target_types, transform, color);
|
||||
}
|
||||
|
||||
/// Fills the area inside the path. Assumes `color` is in gamma space.
|
||||
/// Used by the Pen tool to show the path being closed.
|
||||
pub fn fill_path(&mut self, subpaths: impl Iterator<Item = impl Borrow<Subpath<PointId>>>, transform: DAffine2, color: &str) {
|
||||
self.internal().fill_path(subpaths, transform, color);
|
||||
}
|
||||
|
||||
/// Fills the area inside the path with a pattern. Assumes `color` is in gamma space.
|
||||
/// Used by the fill tool to show the area to be filled.
|
||||
pub fn fill_path_pattern(&mut self, subpaths: impl Iterator<Item = impl Borrow<Subpath<PointId>>>, transform: DAffine2, color: &Color) {
|
||||
self.internal().fill_path_pattern(subpaths, transform, color);
|
||||
}
|
||||
|
||||
pub fn get_width(&self, text: &str) -> f64 {
|
||||
self.internal().get_width(text)
|
||||
}
|
||||
|
||||
pub fn text(&self, text: &str, font_color: &str, background_color: Option<&str>, transform: DAffine2, padding: f64, pivot: [Pivot; 2]) {
|
||||
let mut internal = self.internal();
|
||||
internal.text(text, font_color, background_color, transform, padding, pivot);
|
||||
}
|
||||
|
||||
pub fn translation_box(&mut self, translation: DVec2, quad: Quad, typed_string: Option<String>) {
|
||||
self.internal().translation_box(translation, quad, typed_string);
|
||||
}
|
||||
}
|
||||
|
||||
pub enum Pivot {
|
||||
Start,
|
||||
Middle,
|
||||
End,
|
||||
}
|
||||
|
||||
pub enum DrawHandles {
|
||||
All,
|
||||
SelectedAnchors(Vec<SegmentId>),
|
||||
FrontierHandles(HashMap<SegmentId, Vec<PointId>>),
|
||||
None,
|
||||
}
|
||||
|
||||
pub(super) struct OverlayContextInternal {
|
||||
scene: Scene,
|
||||
size: DVec2,
|
||||
device_pixel_ratio: f64,
|
||||
visibility_settings: OverlaysVisibilitySettings,
|
||||
}
|
||||
|
||||
impl Default for OverlayContextInternal {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
scene: Scene::new(),
|
||||
size: DVec2::ZERO,
|
||||
device_pixel_ratio: 1.0,
|
||||
visibility_settings: OverlaysVisibilitySettings::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl OverlayContextInternal {
|
||||
pub(super) fn new(size: DVec2, device_pixel_ratio: f64, visibility_settings: OverlaysVisibilitySettings) -> Self {
|
||||
Self {
|
||||
scene: Scene::new(),
|
||||
size,
|
||||
device_pixel_ratio,
|
||||
visibility_settings,
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_color(color: &str) -> peniko::Color {
|
||||
let hex = color.trim_start_matches('#');
|
||||
let r = u8::from_str_radix(&hex[0..2], 16).unwrap_or(0);
|
||||
@@ -190,11 +439,11 @@ impl OverlayContext {
|
||||
peniko::Color::from_rgba8(r, g, b, a)
|
||||
}
|
||||
|
||||
pub fn quad(&mut self, quad: Quad, stroke_color: Option<&str>, color_fill: Option<&str>) {
|
||||
fn quad(&mut self, quad: Quad, stroke_color: Option<&str>, color_fill: Option<&str>) {
|
||||
self.dashed_polygon(&quad.0, stroke_color, color_fill, None, None, None);
|
||||
}
|
||||
|
||||
pub fn draw_triangle(&mut self, base: DVec2, direction: DVec2, size: f64, color_fill: Option<&str>, color_stroke: Option<&str>) {
|
||||
fn draw_triangle(&mut self, base: DVec2, direction: DVec2, size: f64, color_fill: Option<&str>, color_stroke: Option<&str>) {
|
||||
let color_fill = color_fill.unwrap_or(COLOR_OVERLAY_WHITE);
|
||||
let color_stroke = color_stroke.unwrap_or(COLOR_OVERLAY_BLUE);
|
||||
let normal = direction.perp();
|
||||
@@ -215,15 +464,15 @@ impl OverlayContext {
|
||||
self.scene.stroke(&kurbo::Stroke::new(1.0), transform, Self::parse_color(color_stroke), None, &path);
|
||||
}
|
||||
|
||||
pub fn dashed_quad(&mut self, quad: Quad, stroke_color: Option<&str>, color_fill: Option<&str>, dash_width: Option<f64>, dash_gap_width: Option<f64>, dash_offset: Option<f64>) {
|
||||
fn dashed_quad(&mut self, quad: Quad, stroke_color: Option<&str>, color_fill: Option<&str>, dash_width: Option<f64>, dash_gap_width: Option<f64>, dash_offset: Option<f64>) {
|
||||
self.dashed_polygon(&quad.0, stroke_color, color_fill, dash_width, dash_gap_width, dash_offset);
|
||||
}
|
||||
|
||||
pub fn polygon(&mut self, polygon: &[DVec2], stroke_color: Option<&str>, color_fill: Option<&str>) {
|
||||
fn polygon(&mut self, polygon: &[DVec2], stroke_color: Option<&str>, color_fill: Option<&str>) {
|
||||
self.dashed_polygon(polygon, stroke_color, color_fill, None, None, None);
|
||||
}
|
||||
|
||||
pub fn dashed_polygon(&mut self, polygon: &[DVec2], stroke_color: Option<&str>, color_fill: Option<&str>, dash_width: Option<f64>, dash_gap_width: Option<f64>, dash_offset: Option<f64>) {
|
||||
fn dashed_polygon(&mut self, polygon: &[DVec2], stroke_color: Option<&str>, color_fill: Option<&str>, dash_width: Option<f64>, dash_gap_width: Option<f64>, dash_offset: Option<f64>) {
|
||||
if polygon.len() < 2 {
|
||||
return;
|
||||
}
|
||||
@@ -255,12 +504,12 @@ impl OverlayContext {
|
||||
self.scene.stroke(&stroke, transform, Self::parse_color(stroke_color), None, &path);
|
||||
}
|
||||
|
||||
pub fn line(&mut self, start: DVec2, end: DVec2, color: Option<&str>, thickness: Option<f64>) {
|
||||
fn line(&mut self, start: DVec2, end: DVec2, color: Option<&str>, thickness: Option<f64>) {
|
||||
self.dashed_line(start, end, color, thickness, None, None, None)
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn dashed_line(&mut self, start: DVec2, end: DVec2, color: Option<&str>, thickness: Option<f64>, dash_width: Option<f64>, dash_gap_width: Option<f64>, dash_offset: Option<f64>) {
|
||||
fn dashed_line(&mut self, start: DVec2, end: DVec2, color: Option<&str>, thickness: Option<f64>, dash_width: Option<f64>, dash_gap_width: Option<f64>, dash_offset: Option<f64>) {
|
||||
let transform = self.get_transform();
|
||||
|
||||
let start = start.round() - DVec2::splat(0.5);
|
||||
@@ -280,7 +529,7 @@ impl OverlayContext {
|
||||
self.scene.stroke(&stroke, transform, Self::parse_color(color.unwrap_or(COLOR_OVERLAY_BLUE)), None, &path);
|
||||
}
|
||||
|
||||
pub fn manipulator_handle(&mut self, position: DVec2, selected: bool, color: Option<&str>) {
|
||||
fn manipulator_handle(&mut self, position: DVec2, selected: bool, color: Option<&str>) {
|
||||
let transform = self.get_transform();
|
||||
let position = position.round() - DVec2::splat(0.5);
|
||||
|
||||
@@ -293,17 +542,41 @@ impl OverlayContext {
|
||||
.stroke(&kurbo::Stroke::new(1.0), transform, Self::parse_color(color.unwrap_or(COLOR_OVERLAY_BLUE)), None, &circle);
|
||||
}
|
||||
|
||||
pub fn manipulator_anchor(&mut self, position: DVec2, selected: bool, color: Option<&str>) {
|
||||
fn hover_manipulator_handle(&mut self, position: DVec2, selected: bool) {
|
||||
let transform = self.get_transform();
|
||||
|
||||
let position = position.round() - DVec2::splat(0.5);
|
||||
|
||||
let circle = kurbo::Circle::new((position.x, position.y), (MANIPULATOR_GROUP_MARKER_SIZE + 2.) / 2.);
|
||||
|
||||
let fill = COLOR_OVERLAY_BLUE_50;
|
||||
self.scene.fill(peniko::Fill::NonZero, transform, Self::parse_color(fill), None, &circle);
|
||||
self.scene.stroke(&kurbo::Stroke::new(1.0), transform, Self::parse_color(COLOR_OVERLAY_BLUE_50), None, &circle);
|
||||
|
||||
let inner_circle = kurbo::Circle::new((position.x, position.y), MANIPULATOR_GROUP_MARKER_SIZE / 2.);
|
||||
|
||||
let color_fill = if selected { COLOR_OVERLAY_BLUE } else { COLOR_OVERLAY_WHITE };
|
||||
self.scene.fill(peniko::Fill::NonZero, transform, Self::parse_color(color_fill), None, &circle);
|
||||
self.scene.stroke(&kurbo::Stroke::new(1.0), transform, Self::parse_color(COLOR_OVERLAY_BLUE), None, &inner_circle);
|
||||
}
|
||||
|
||||
fn manipulator_anchor(&mut self, position: DVec2, selected: bool, color: Option<&str>) {
|
||||
let color_stroke = color.unwrap_or(COLOR_OVERLAY_BLUE);
|
||||
let color_fill = if selected { color_stroke } else { COLOR_OVERLAY_WHITE };
|
||||
self.square(position, None, Some(color_fill), Some(color_stroke));
|
||||
}
|
||||
|
||||
fn hover_manipulator_anchor(&mut self, position: DVec2, selected: bool) {
|
||||
self.square(position, Some(MANIPULATOR_GROUP_MARKER_SIZE + 2.), Some(COLOR_OVERLAY_BLUE_50), Some(COLOR_OVERLAY_BLUE_50));
|
||||
let color_fill = if selected { COLOR_OVERLAY_BLUE } else { COLOR_OVERLAY_WHITE };
|
||||
self.square(position, None, Some(color_fill), Some(COLOR_OVERLAY_BLUE));
|
||||
}
|
||||
|
||||
fn get_transform(&self) -> kurbo::Affine {
|
||||
kurbo::Affine::scale(self.device_pixel_ratio)
|
||||
}
|
||||
|
||||
pub fn square(&mut self, position: DVec2, size: Option<f64>, color_fill: Option<&str>, color_stroke: Option<&str>) {
|
||||
fn square(&mut self, position: DVec2, size: Option<f64>, color_fill: Option<&str>, color_stroke: Option<&str>) {
|
||||
let size = size.unwrap_or(MANIPULATOR_GROUP_MARKER_SIZE);
|
||||
let color_fill = color_fill.unwrap_or(COLOR_OVERLAY_WHITE);
|
||||
let color_stroke = color_stroke.unwrap_or(COLOR_OVERLAY_BLUE);
|
||||
@@ -319,7 +592,7 @@ impl OverlayContext {
|
||||
self.scene.stroke(&kurbo::Stroke::new(1.0), transform, Self::parse_color(color_stroke), None, &rect);
|
||||
}
|
||||
|
||||
pub fn pixel(&mut self, position: DVec2, color: Option<&str>) {
|
||||
fn pixel(&mut self, position: DVec2, color: Option<&str>) {
|
||||
let size = 1.;
|
||||
let color_fill = color.unwrap_or(COLOR_OVERLAY_WHITE);
|
||||
|
||||
@@ -332,7 +605,7 @@ impl OverlayContext {
|
||||
self.scene.fill(peniko::Fill::NonZero, transform, Self::parse_color(color_fill), None, &rect);
|
||||
}
|
||||
|
||||
pub fn circle(&mut self, position: DVec2, radius: f64, color_fill: Option<&str>, color_stroke: Option<&str>) {
|
||||
fn circle(&mut self, position: DVec2, radius: f64, color_fill: Option<&str>, color_stroke: Option<&str>) {
|
||||
let color_fill = color_fill.unwrap_or(COLOR_OVERLAY_WHITE);
|
||||
let color_stroke = color_stroke.unwrap_or(COLOR_OVERLAY_BLUE);
|
||||
let position = position.round();
|
||||
@@ -345,7 +618,24 @@ impl OverlayContext {
|
||||
self.scene.stroke(&kurbo::Stroke::new(1.0), transform, Self::parse_color(color_stroke), None, &circle);
|
||||
}
|
||||
|
||||
pub fn draw_arc(&mut self, center: DVec2, radius: f64, start_from: f64, end_at: f64) {
|
||||
fn dashed_ellipse(
|
||||
&mut self,
|
||||
_center: DVec2,
|
||||
_radius_x: f64,
|
||||
_radius_y: f64,
|
||||
_rotation: Option<f64>,
|
||||
_start_angle: Option<f64>,
|
||||
_end_angle: Option<f64>,
|
||||
_counterclockwise: Option<bool>,
|
||||
_color_fill: Option<&str>,
|
||||
_color_stroke: Option<&str>,
|
||||
_dash_width: Option<f64>,
|
||||
_dash_gap_width: Option<f64>,
|
||||
_dash_offset: Option<f64>,
|
||||
) {
|
||||
}
|
||||
|
||||
fn draw_arc(&mut self, center: DVec2, radius: f64, start_from: f64, end_at: f64) {
|
||||
let segments = ((end_at - start_from).abs() / (std::f64::consts::PI / 4.)).ceil() as usize;
|
||||
let step = (end_at - start_from) / segments as f64;
|
||||
let half_step = step / 2.;
|
||||
@@ -379,13 +669,13 @@ impl OverlayContext {
|
||||
self.scene.stroke(&kurbo::Stroke::new(1.0), self.get_transform(), Self::parse_color(COLOR_OVERLAY_BLUE), None, &path);
|
||||
}
|
||||
|
||||
pub fn draw_arc_gizmo_angle(&mut self, pivot: DVec2, bold_radius: f64, arc_radius: f64, offset_angle: f64, angle: f64) {
|
||||
fn draw_arc_gizmo_angle(&mut self, pivot: DVec2, bold_radius: f64, arc_radius: f64, offset_angle: f64, angle: f64) {
|
||||
let end_point1 = pivot + bold_radius * DVec2::from_angle(angle + offset_angle);
|
||||
self.line(pivot, end_point1, None, None);
|
||||
self.draw_arc(pivot, arc_radius, offset_angle, (angle) % TAU + offset_angle);
|
||||
}
|
||||
|
||||
pub fn draw_angle(&mut self, pivot: DVec2, radius: f64, arc_radius: f64, offset_angle: f64, angle: f64) {
|
||||
fn draw_angle(&mut self, pivot: DVec2, radius: f64, arc_radius: f64, offset_angle: f64, angle: f64) {
|
||||
let end_point1 = pivot + radius * DVec2::from_angle(angle + offset_angle);
|
||||
let end_point2 = pivot + radius * DVec2::from_angle(offset_angle);
|
||||
self.line(pivot, end_point1, None, None);
|
||||
@@ -393,7 +683,7 @@ impl OverlayContext {
|
||||
self.draw_arc(pivot, arc_radius, offset_angle, (angle) % TAU + offset_angle);
|
||||
}
|
||||
|
||||
pub fn draw_scale(&mut self, start: DVec2, scale: f64, radius: f64, text: &str) {
|
||||
fn draw_scale(&mut self, start: DVec2, scale: f64, radius: f64, text: &str) {
|
||||
let sign = scale.signum();
|
||||
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, '#');
|
||||
@@ -411,7 +701,7 @@ impl OverlayContext {
|
||||
)
|
||||
}
|
||||
|
||||
pub fn compass_rose(&mut self, compass_center: DVec2, angle: f64, show_compass_with_hover_ring: Option<bool>) {
|
||||
fn compass_rose(&mut self, compass_center: DVec2, angle: f64, show_compass_with_hover_ring: Option<bool>) {
|
||||
const HOVER_RING_OUTER_RADIUS: f64 = COMPASS_ROSE_HOVER_RING_DIAMETER / 2.;
|
||||
const MAIN_RING_OUTER_RADIUS: f64 = COMPASS_ROSE_MAIN_RING_DIAMETER / 2.;
|
||||
const MAIN_RING_INNER_RADIUS: f64 = COMPASS_ROSE_RING_INNER_DIAMETER / 2.;
|
||||
@@ -467,7 +757,7 @@ impl OverlayContext {
|
||||
.stroke(&kurbo::Stroke::new(MAIN_RING_STROKE_WIDTH), transform, Self::parse_color(COLOR_OVERLAY_BLUE), None, &circle);
|
||||
}
|
||||
|
||||
pub fn pivot(&mut self, position: DVec2, angle: f64) {
|
||||
fn pivot(&mut self, position: DVec2, angle: f64) {
|
||||
let uv = DVec2::from_angle(angle);
|
||||
let (x, y) = (position.round() - DVec2::splat(0.5)).into();
|
||||
|
||||
@@ -498,7 +788,7 @@ impl OverlayContext {
|
||||
self.scene.stroke(&stroke, transform, Self::parse_color(COLOR_OVERLAY_YELLOW), None, &path);
|
||||
}
|
||||
|
||||
pub fn dowel_pin(&mut self, position: DVec2, angle: f64, color: Option<&str>) {
|
||||
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);
|
||||
|
||||
@@ -540,19 +830,19 @@ impl OverlayContext {
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn arc_sweep_angle(&mut self, offset_angle: f64, angle: f64, end_point_position: DVec2, bold_radius: f64, pivot: DVec2, text: &str, transform: DAffine2) {
|
||||
self.manipulator_handle(end_point_position, true, Some(COLOR_OVERLAY_RED));
|
||||
fn arc_sweep_angle(&mut self, offset_angle: f64, angle: f64, end_point_position: DVec2, bold_radius: f64, pivot: DVec2, text: &str, transform: DAffine2) {
|
||||
self.manipulator_handle(end_point_position, true, None);
|
||||
self.draw_arc_gizmo_angle(pivot, bold_radius, ARC_SWEEP_GIZMO_RADIUS, offset_angle, angle.to_radians());
|
||||
self.text(text, COLOR_OVERLAY_BLUE, None, transform, 16., [Pivot::Middle, Pivot::Middle]);
|
||||
}
|
||||
|
||||
/// 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) {
|
||||
fn outline_vector(&mut self, vector: &Vector, transform: DAffine2) {
|
||||
let vello_transform = self.get_transform();
|
||||
let mut path = BezPath::new();
|
||||
|
||||
let mut last_point = None;
|
||||
for (_, bezier, start_id, end_id) in vector_data.segment_bezier_iter() {
|
||||
for (_, bezier, start_id, end_id) in vector.segment_bezier_iter() {
|
||||
let move_to = last_point != Some(start_id);
|
||||
last_point = Some(end_id);
|
||||
|
||||
@@ -563,7 +853,7 @@ impl OverlayContext {
|
||||
}
|
||||
|
||||
/// Used by the Pen tool in order to show how the bezier curve would look like.
|
||||
pub fn outline_bezier(&mut self, bezier: Bezier, transform: DAffine2) {
|
||||
fn outline_bezier(&mut self, bezier: Bezier, transform: DAffine2) {
|
||||
let vello_transform = self.get_transform();
|
||||
let mut path = BezPath::new();
|
||||
self.bezier_to_path(bezier, transform, true, &mut path);
|
||||
@@ -572,7 +862,7 @@ impl OverlayContext {
|
||||
}
|
||||
|
||||
/// Used by the path tool segment mode in order to show the selected segments.
|
||||
pub fn outline_select_bezier(&mut self, bezier: Bezier, transform: DAffine2) {
|
||||
fn outline_select_bezier(&mut self, bezier: Bezier, transform: DAffine2) {
|
||||
let vello_transform = self.get_transform();
|
||||
let mut path = BezPath::new();
|
||||
self.bezier_to_path(bezier, transform, true, &mut path);
|
||||
@@ -580,7 +870,7 @@ impl OverlayContext {
|
||||
self.scene.stroke(&kurbo::Stroke::new(4.0), vello_transform, Self::parse_color(COLOR_OVERLAY_BLUE), None, &path);
|
||||
}
|
||||
|
||||
pub fn outline_overlay_bezier(&mut self, bezier: Bezier, transform: DAffine2) {
|
||||
fn outline_overlay_bezier(&mut self, bezier: Bezier, transform: DAffine2) {
|
||||
let vello_transform = self.get_transform();
|
||||
let mut path = BezPath::new();
|
||||
self.bezier_to_path(bezier, transform, true, &mut path);
|
||||
@@ -654,7 +944,7 @@ impl OverlayContext {
|
||||
}
|
||||
|
||||
/// Used by the Select tool to outline a path or a free point when selected or hovered.
|
||||
pub fn outline(&mut self, target_types: impl Iterator<Item = impl Borrow<ClickTargetType>>, transform: DAffine2, color: Option<&str>) {
|
||||
fn outline(&mut self, target_types: impl Iterator<Item = impl Borrow<ClickTargetType>>, transform: DAffine2, color: Option<&str>) {
|
||||
let mut subpaths: Vec<bezier_rs::Subpath<PointId>> = vec![];
|
||||
|
||||
for target_type in target_types {
|
||||
@@ -676,7 +966,7 @@ impl OverlayContext {
|
||||
|
||||
/// Fills the area inside the path. Assumes `color` is in gamma space.
|
||||
/// Used by the Pen tool to show the path being closed.
|
||||
pub fn fill_path(&mut self, subpaths: impl Iterator<Item = impl Borrow<Subpath<PointId>>>, transform: DAffine2, color: &str) {
|
||||
fn fill_path(&mut self, subpaths: impl Iterator<Item = impl Borrow<Subpath<PointId>>>, transform: DAffine2, color: &str) {
|
||||
let path = self.push_path(subpaths, transform);
|
||||
|
||||
self.scene.fill(peniko::Fill::NonZero, self.get_transform(), Self::parse_color(color), None, &path);
|
||||
@@ -684,36 +974,199 @@ impl OverlayContext {
|
||||
|
||||
/// Fills the area inside the path with a pattern. Assumes `color` is in gamma space.
|
||||
/// Used by the fill tool to show the area to be filled.
|
||||
pub fn fill_path_pattern(&mut self, subpaths: impl Iterator<Item = impl Borrow<Subpath<PointId>>>, transform: DAffine2, color: &Color) {
|
||||
// TODO: Implement pattern fill in Vello
|
||||
// For now, just fill with a semi-transparent version of the color
|
||||
fn fill_path_pattern(&mut self, subpaths: impl Iterator<Item = impl Borrow<Subpath<PointId>>>, transform: DAffine2, color: &Color) {
|
||||
const PATTERN_WIDTH: u32 = 4;
|
||||
const PATTERN_HEIGHT: u32 = 4;
|
||||
|
||||
// Create a 4x4 pixel pattern with colored pixels at (0,0) and (2,2)
|
||||
// This matches the Canvas2D checkerboard pattern
|
||||
let mut data = vec![0u8; (PATTERN_WIDTH * PATTERN_HEIGHT * 4) as usize];
|
||||
let rgba = color.to_rgba8_srgb();
|
||||
|
||||
// ┌▄▄┬──┬──┬──┐
|
||||
// ├▀▀┼──┼──┼──┤
|
||||
// ├──┼──┼▄▄┼──┤
|
||||
// ├──┼──┼▀▀┼──┤
|
||||
// └──┴──┴──┴──┘
|
||||
// Set pixels at (0,0) and (2,2) to the specified color
|
||||
let pixels = [(0, 0), (2, 2)];
|
||||
for &(x, y) in &pixels {
|
||||
let index = ((y * PATTERN_WIDTH + x) * 4) as usize;
|
||||
data[index..index + 4].copy_from_slice(&rgba);
|
||||
}
|
||||
|
||||
let image = peniko::Image {
|
||||
data: data.into(),
|
||||
format: peniko::ImageFormat::Rgba8,
|
||||
width: PATTERN_WIDTH,
|
||||
height: PATTERN_HEIGHT,
|
||||
x_extend: peniko::Extend::Repeat,
|
||||
y_extend: peniko::Extend::Repeat,
|
||||
alpha: 1.0,
|
||||
quality: peniko::ImageQuality::default(),
|
||||
};
|
||||
|
||||
let path = self.push_path(subpaths, transform);
|
||||
let semi_transparent_color = color.with_alpha(0.5);
|
||||
let brush = peniko::Brush::Image(image);
|
||||
|
||||
self.scene.fill(
|
||||
peniko::Fill::NonZero,
|
||||
self.get_transform(),
|
||||
peniko::Color::from_rgba8(
|
||||
(semi_transparent_color.r() * 255.) as u8,
|
||||
(semi_transparent_color.g() * 255.) as u8,
|
||||
(semi_transparent_color.b() * 255.) as u8,
|
||||
(semi_transparent_color.a() * 255.) as u8,
|
||||
),
|
||||
None,
|
||||
&path,
|
||||
);
|
||||
self.scene.fill(peniko::Fill::NonZero, self.get_transform(), &brush, None, &path);
|
||||
}
|
||||
|
||||
pub fn get_width(&self, _text: &str) -> f64 {
|
||||
// TODO: Implement proper text measurement in Vello
|
||||
0.
|
||||
fn get_width(&self, text: &str) -> f64 {
|
||||
// Use the actual text-to-path system to get precise text width
|
||||
const FONT_SIZE: f64 = 12.0;
|
||||
|
||||
let typesetting = TypesettingConfig {
|
||||
font_size: FONT_SIZE,
|
||||
line_height_ratio: 1.2,
|
||||
character_spacing: 0.0,
|
||||
max_width: None,
|
||||
max_height: None,
|
||||
tilt: 0.0,
|
||||
align: TextAlign::Left,
|
||||
};
|
||||
|
||||
// Load Source Sans Pro font data
|
||||
const FONT_DATA: &[u8] = include_bytes!("source-sans-pro-regular.ttf");
|
||||
let font_blob = Some(load_font(FONT_DATA));
|
||||
|
||||
// Convert text to paths and calculate actual bounds
|
||||
let text_table = to_path(text, font_blob, typesetting, false);
|
||||
let text_bounds = self.calculate_text_bounds(&text_table);
|
||||
text_bounds.width()
|
||||
}
|
||||
|
||||
pub fn text(&self, _text: &str, _font_color: &str, _background_color: Option<&str>, _transform: DAffine2, _padding: f64, _pivot: [Pivot; 2]) {
|
||||
// TODO: Implement text rendering in Vello
|
||||
fn text(&mut self, text: &str, font_color: &str, background_color: Option<&str>, transform: DAffine2, padding: f64, pivot: [Pivot; 2]) {
|
||||
// Use the proper text-to-path system for accurate text rendering
|
||||
const FONT_SIZE: f64 = 12.0;
|
||||
|
||||
// Create typesetting configuration
|
||||
let typesetting = TypesettingConfig {
|
||||
font_size: FONT_SIZE,
|
||||
line_height_ratio: 1.2,
|
||||
character_spacing: 0.0,
|
||||
max_width: None,
|
||||
max_height: None,
|
||||
tilt: 0.0,
|
||||
align: TextAlign::Left, // We'll handle alignment manually via pivot
|
||||
};
|
||||
|
||||
// Load Source Sans Pro font data
|
||||
const FONT_DATA: &[u8] = include_bytes!("source-sans-pro-regular.ttf");
|
||||
let font_blob = Some(load_font(FONT_DATA));
|
||||
|
||||
// Convert text to vector paths using the existing text system
|
||||
let text_table = to_path(text, font_blob, typesetting, false);
|
||||
// Calculate text bounds from the generated paths
|
||||
let text_bounds = self.calculate_text_bounds(&text_table);
|
||||
let text_width = text_bounds.width();
|
||||
let text_height = text_bounds.height();
|
||||
|
||||
// Calculate position based on pivot
|
||||
let mut position = DVec2::ZERO;
|
||||
match pivot[0] {
|
||||
Pivot::Start => position.x = padding,
|
||||
Pivot::Middle => position.x = -text_width / 2.0,
|
||||
Pivot::End => position.x = -padding - text_width,
|
||||
}
|
||||
match pivot[1] {
|
||||
Pivot::Start => position.y = padding,
|
||||
Pivot::Middle => position.y -= text_height * 0.5,
|
||||
Pivot::End => position.y = -padding - text_height,
|
||||
}
|
||||
|
||||
let text_transform = transform * DAffine2::from_translation(position);
|
||||
let device_transform = self.get_transform();
|
||||
let combined_transform = kurbo::Affine::new(text_transform.to_cols_array());
|
||||
let vello_transform = device_transform * combined_transform;
|
||||
|
||||
// Draw background if specified
|
||||
if let Some(bg_color) = background_color {
|
||||
let bg_rect = kurbo::Rect::new(
|
||||
text_bounds.min_x() - padding,
|
||||
text_bounds.min_y() - padding,
|
||||
text_bounds.max_x() + padding,
|
||||
text_bounds.max_y() + padding,
|
||||
);
|
||||
self.scene.fill(peniko::Fill::NonZero, vello_transform, Self::parse_color(bg_color), None, &bg_rect);
|
||||
}
|
||||
|
||||
// Render the actual text paths
|
||||
self.render_text_paths(&text_table, font_color, vello_transform);
|
||||
}
|
||||
|
||||
pub fn translation_box(&mut self, translation: DVec2, quad: Quad, typed_string: Option<String>) {
|
||||
// Calculate bounds of text from vector table
|
||||
fn calculate_text_bounds(&self, text_table: &Table<Vector>) -> kurbo::Rect {
|
||||
let mut min_x = f64::INFINITY;
|
||||
let mut min_y = f64::INFINITY;
|
||||
let mut max_x = f64::NEG_INFINITY;
|
||||
let mut max_y = f64::NEG_INFINITY;
|
||||
|
||||
for row in text_table.iter() {
|
||||
// Use the existing segment_bezier_iter to get all bezier curves
|
||||
for (_, bezier, _, _) in row.element.segment_bezier_iter() {
|
||||
let transformed_bezier = bezier.apply_transformation(|point| row.transform.transform_point2(point));
|
||||
|
||||
// Add start and end points to bounds
|
||||
let points = [transformed_bezier.start, transformed_bezier.end];
|
||||
for point in points {
|
||||
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);
|
||||
}
|
||||
|
||||
// Add handle points if they exist
|
||||
match transformed_bezier.handles {
|
||||
bezier_rs::BezierHandles::Quadratic { handle } => {
|
||||
min_x = min_x.min(handle.x);
|
||||
min_y = min_y.min(handle.y);
|
||||
max_x = max_x.max(handle.x);
|
||||
max_y = max_y.max(handle.y);
|
||||
}
|
||||
bezier_rs::BezierHandles::Cubic { handle_start, handle_end } => {
|
||||
for handle in [handle_start, handle_end] {
|
||||
min_x = min_x.min(handle.x);
|
||||
min_y = min_y.min(handle.y);
|
||||
max_x = max_x.max(handle.x);
|
||||
max_y = max_y.max(handle.y);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if min_x.is_finite() && min_y.is_finite() && max_x.is_finite() && max_y.is_finite() {
|
||||
kurbo::Rect::new(min_x, min_y, max_x, max_y)
|
||||
} else {
|
||||
// Fallback for empty text
|
||||
kurbo::Rect::new(0.0, 0.0, 0.0, 12.0)
|
||||
}
|
||||
}
|
||||
|
||||
// Render text paths to the vello scene using existing infrastructure
|
||||
fn render_text_paths(&mut self, text_table: &Table<Vector>, font_color: &str, base_transform: kurbo::Affine) {
|
||||
let color = Self::parse_color(font_color);
|
||||
|
||||
for row in text_table.iter() {
|
||||
// Use the existing bezier_to_path infrastructure to convert Vector to BezPath
|
||||
let mut path = BezPath::new();
|
||||
let mut last_point = None;
|
||||
|
||||
for (_, bezier, start_id, end_id) in row.element.segment_bezier_iter() {
|
||||
let move_to = last_point != Some(start_id);
|
||||
last_point = Some(end_id);
|
||||
|
||||
self.bezier_to_path(bezier, row.transform.clone(), move_to, &mut path);
|
||||
}
|
||||
|
||||
// Render the path
|
||||
self.scene.fill(peniko::Fill::NonZero, base_transform, color, None, &path);
|
||||
}
|
||||
}
|
||||
|
||||
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));
|
||||
|
||||
@@ -743,16 +1196,3 @@ impl OverlayContext {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub enum Pivot {
|
||||
Start,
|
||||
Middle,
|
||||
End,
|
||||
}
|
||||
|
||||
pub enum DrawHandles {
|
||||
All,
|
||||
SelectedAnchors(Vec<SegmentId>),
|
||||
FrontierHandles(HashMap<SegmentId, Vec<PointId>>),
|
||||
None,
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ use graph_craft::document::NodeId;
|
||||
use graphene_std::math::quad::Quad;
|
||||
use graphene_std::transform::Footprint;
|
||||
use graphene_std::vector::click_target::{ClickTarget, ClickTargetType};
|
||||
use graphene_std::vector::{PointId, VectorData};
|
||||
use graphene_std::vector::{PointId, Vector};
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::num::NonZeroU64;
|
||||
|
||||
@@ -22,11 +22,11 @@ use std::num::NonZeroU64;
|
||||
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 first_element_source_ids: HashMap<NodeId, Option<NodeId>>,
|
||||
pub structure: HashMap<LayerNodeIdentifier, NodeRelations>,
|
||||
pub click_targets: HashMap<LayerNodeIdentifier, Vec<ClickTarget>>,
|
||||
pub clip_targets: HashSet<NodeId>,
|
||||
pub vector_modify: HashMap<NodeId, VectorData>,
|
||||
pub vector_modify: HashMap<NodeId, Vector>,
|
||||
/// Transform from document space to viewport space.
|
||||
pub document_to_viewport: DAffine2,
|
||||
}
|
||||
@@ -90,8 +90,8 @@ impl DocumentMetadata {
|
||||
|
||||
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 let Some(path_node) = graph_layer.upstream_visible_node_id_from_name_in_layer("Path") {
|
||||
if let Some(&source) = self.first_element_source_ids.get(&layer.to_node()) {
|
||||
if !network_interface
|
||||
.upstream_flow_back_from_nodes(vec![path_node], &[], FlowType::HorizontalFlow)
|
||||
.any(|upstream| Some(upstream) == source)
|
||||
@@ -303,10 +303,10 @@ impl LayerNodeIdentifier {
|
||||
child.ancestors(metadata).any(|ancestor| ancestor == self)
|
||||
}
|
||||
|
||||
/// Is the layer last child of parent group? Used for clipping
|
||||
/// Is the layer the last child of its stack? Used for clipping
|
||||
pub fn can_be_clipped(self, metadata: &DocumentMetadata) -> bool {
|
||||
self.parent(metadata)
|
||||
.map_or(false, |layer| layer.last_child(metadata).expect("Parent accessed via child should have children") != self)
|
||||
.is_some_and(|layer| layer.last_child(metadata).expect("Parent accessed via child should have children") != self)
|
||||
}
|
||||
|
||||
/// Iterator over all direct children (excluding self and recursive children)
|
||||
|
||||
@@ -13,12 +13,15 @@ use glam::{DAffine2, DVec2, IVec2};
|
||||
use graph_craft::document::value::TaggedValue;
|
||||
use graph_craft::document::{DocumentNode, DocumentNodeImplementation, NodeId, NodeInput, NodeNetwork, OldDocumentNodeImplementation, OldNodeNetwork};
|
||||
use graph_craft::{Type, concrete};
|
||||
use graphene_std::Artboard;
|
||||
use graphene_std::math::quad::Quad;
|
||||
use graphene_std::table::Table;
|
||||
use graphene_std::transform::Footprint;
|
||||
use graphene_std::vector::click_target::{ClickTarget, ClickTargetType};
|
||||
use graphene_std::vector::{PointId, VectorData, VectorModificationType};
|
||||
use graphene_std::vector::{PointId, Vector, VectorModificationType};
|
||||
use interpreted_executor::dynamic_executor::ResolvedDocumentNodeTypes;
|
||||
use interpreted_executor::node_registry::NODE_REGISTRY;
|
||||
use kurbo::BezPath;
|
||||
use serde_json::{Value, json};
|
||||
use std::collections::{HashMap, HashSet, VecDeque};
|
||||
use std::hash::{DefaultHasher, Hash, Hasher};
|
||||
@@ -507,8 +510,8 @@ impl NodeNetworkInterface {
|
||||
InputConnector::Node { node_id, input_index } => (node_id, input_index),
|
||||
InputConnector::Export(export_index) => {
|
||||
let Some((encapsulating_node_id, encapsulating_node_id_path)) = network_path.split_last() else {
|
||||
// The outermost network export defaults to an ArtboardGroupTable.
|
||||
return Some((concrete!(graphene_std::ArtboardGroupTable), TypeSource::OuterMostExportDefault));
|
||||
// The outermost network export defaults to a Table<Artboard>.
|
||||
return Some((concrete!(Table<Artboard>), TypeSource::OuterMostExportDefault));
|
||||
};
|
||||
|
||||
let output_type = self.output_type(encapsulating_node_id, export_index, encapsulating_node_id_path);
|
||||
@@ -673,7 +676,7 @@ impl NodeNetworkInterface {
|
||||
let valid_implementation = (0..number_of_inputs).filter(|iterator_index| iterator_index != input_index).all(|iterator_index| {
|
||||
let input_type = self.input_type(&InputConnector::node(*node_id, iterator_index), network_path).0;
|
||||
// Value inputs are stored as concrete, so they are compared to the nested type. Node inputs are stored as fn, so they are compared to the entire type.
|
||||
// For example a node input of (Footprint) -> VectorData would not be compatible with () -> VectorData
|
||||
// For example a node input of (Footprint) -> Vector would not be compatible with () -> Vector
|
||||
node_io.inputs.get(iterator_index).map(|ty| ty.nested_type().clone()).as_ref() == Some(&input_type) || node_io.inputs.get(iterator_index) == Some(&input_type)
|
||||
});
|
||||
if valid_implementation { node_io.inputs.get(*input_index).cloned() } else { None }
|
||||
@@ -1424,13 +1427,13 @@ impl NodeNetworkInterface {
|
||||
.any(|id| id == potentially_upstream_node)
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
fn text_width(&self, node_id: &NodeId, network_path: &[NodeId]) -> Option<f64> {
|
||||
warn!("Failed to find width of {node_id:#?} in network_path {network_path:?} due to non-wasm arch");
|
||||
Some(0.)
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
#[cfg(target_family = "wasm")]
|
||||
fn text_width(&self, node_id: &NodeId, network_path: &[NodeId]) -> Option<f64> {
|
||||
let document = web_sys::window().unwrap().document().unwrap();
|
||||
let div = match document.create_element("div") {
|
||||
@@ -2711,8 +2714,7 @@ impl NodeNetworkInterface {
|
||||
let thick = vertical_end && vertical_start;
|
||||
let vector_wire = build_vector_wire(output_position, input_position, vertical_start, vertical_end, graph_wire_style);
|
||||
|
||||
let mut path_string = String::new();
|
||||
let _ = vector_wire.subpath_to_svg(&mut path_string, DAffine2::IDENTITY);
|
||||
let path_string = vector_wire.to_svg();
|
||||
let data_type = FrontendGraphDataType::from_type(&self.input_type(&input, network_path).0);
|
||||
let wire_path_update = Some(WirePath {
|
||||
path_string,
|
||||
@@ -2729,14 +2731,14 @@ impl NodeNetworkInterface {
|
||||
}
|
||||
|
||||
/// Returns the vector subpath and a boolean of whether the wire should be thick.
|
||||
pub fn vector_wire_from_input(&mut self, input: &InputConnector, wire_style: GraphWireStyle, network_path: &[NodeId]) -> Option<(Subpath<PointId>, bool)> {
|
||||
pub fn vector_wire_from_input(&mut self, input: &InputConnector, wire_style: GraphWireStyle, network_path: &[NodeId]) -> Option<(BezPath, bool)> {
|
||||
let Some(input_position) = self.get_input_center(input, network_path) else {
|
||||
log::error!("Could not get dom rect for wire end: {:?}", input);
|
||||
return None;
|
||||
};
|
||||
// An upstream output could not be found, so the wire does not exist, but it should still be loaded as as empty vector
|
||||
let Some(upstream_output) = self.upstream_output_connector(input, network_path) else {
|
||||
return Some((Subpath::from_anchors(std::iter::empty(), false), false));
|
||||
return Some((BezPath::new(), false));
|
||||
};
|
||||
let Some(output_position) = self.get_output_center(&upstream_output, network_path) else {
|
||||
log::error!("Could not get dom rect for wire start: {:?}", upstream_output);
|
||||
@@ -2750,8 +2752,7 @@ impl NodeNetworkInterface {
|
||||
|
||||
pub fn wire_path_from_input(&mut self, input: &InputConnector, graph_wire_style: GraphWireStyle, dashed: bool, network_path: &[NodeId]) -> Option<WirePath> {
|
||||
let (vector_wire, thick) = self.vector_wire_from_input(input, graph_wire_style, network_path)?;
|
||||
let mut path_string = String::new();
|
||||
let _ = vector_wire.subpath_to_svg(&mut path_string, DAffine2::IDENTITY);
|
||||
let path_string = vector_wire.to_svg();
|
||||
let data_type = FrontendGraphDataType::from_type(&self.input_type(input, network_path).0);
|
||||
Some(WirePath {
|
||||
path_string,
|
||||
@@ -3442,22 +3443,27 @@ impl NodeNetworkInterface {
|
||||
(layer_widths, chain_widths, has_left_input_wire)
|
||||
}
|
||||
|
||||
pub fn compute_modified_vector(&self, layer: LayerNodeIdentifier) -> Option<VectorData> {
|
||||
pub fn compute_modified_vector(&self, layer: LayerNodeIdentifier) -> Option<Vector> {
|
||||
let graph_layer = graph_modification_utils::NodeGraphLayer::new(layer, self);
|
||||
|
||||
if let Some(vector_data) = graph_layer.upstream_node_id_from_name("Path").and_then(|node| self.document_metadata.vector_modify.get(&node)) {
|
||||
let mut modified = vector_data.clone();
|
||||
if let Some(TaggedValue::VectorModification(modification)) = graph_layer.find_input("Path", 1) {
|
||||
modification.apply(&mut modified);
|
||||
if let Some(path_node) = graph_layer.upstream_visible_node_id_from_name_in_layer("Path") {
|
||||
if let Some(vector) = self.document_metadata.vector_modify.get(&path_node) {
|
||||
let mut modified = vector.clone();
|
||||
|
||||
let path_node = self.document_network().nodes.get(&path_node);
|
||||
let modification_input = path_node.and_then(|node: &DocumentNode| node.inputs.get(1)).and_then(|input| input.as_value());
|
||||
if let Some(TaggedValue::VectorModification(modification)) = modification_input {
|
||||
modification.apply(&mut modified);
|
||||
}
|
||||
return Some(modified);
|
||||
}
|
||||
return Some(modified);
|
||||
}
|
||||
|
||||
self.document_metadata
|
||||
.click_targets
|
||||
.get(&layer)
|
||||
.map(|click| click.iter().map(ClickTarget::target_type))
|
||||
.map(|target_types| VectorData::from_target_types(target_types, true))
|
||||
.map(|target_types| Vector::from_target_types(target_types, true))
|
||||
}
|
||||
|
||||
/// Loads the structure of layer nodes from a node graph.
|
||||
@@ -3553,8 +3559,8 @@ impl NodeNetworkInterface {
|
||||
}
|
||||
|
||||
/// Update the cached first instance source id of the layers
|
||||
pub fn update_first_instance_source_id(&mut self, new: HashMap<NodeId, Option<NodeId>>) {
|
||||
self.document_metadata.first_instance_source_ids = new;
|
||||
pub fn update_first_element_source_id(&mut self, new: HashMap<NodeId, Option<NodeId>>) {
|
||||
self.document_metadata.first_element_source_ids = new;
|
||||
}
|
||||
|
||||
/// Update the cached click targets of the layers
|
||||
@@ -3568,7 +3574,7 @@ impl NodeNetworkInterface {
|
||||
}
|
||||
|
||||
/// Update the vector modify of the layers
|
||||
pub fn update_vector_modify(&mut self, new_vector_modify: HashMap<NodeId, VectorData>) {
|
||||
pub fn update_vector_modify(&mut self, new_vector_modify: HashMap<NodeId, Vector>) {
|
||||
self.document_metadata.vector_modify = new_vector_modify;
|
||||
}
|
||||
}
|
||||
@@ -6604,11 +6610,30 @@ struct InputTransientMetadata {
|
||||
fn migrate_output_names<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result<Vec<String>, D::Error> {
|
||||
use serde::Deserialize;
|
||||
|
||||
const REPLACEMENTS: [(&str, &str); 4] = [
|
||||
("VectorData", "Instances<VectorData>"),
|
||||
("GraphicGroup", "Instances<GraphicGroup>"),
|
||||
("ImageFrame", "Instances<Image>"),
|
||||
("Instances<ImageFrame>", "Instances<Image>"),
|
||||
const REPLACEMENTS: &[(&str, &str)] = &[
|
||||
// Single to table data
|
||||
("VectorData", "Table<Vector>"),
|
||||
("GraphicGroup", "Table<Graphic>"),
|
||||
("ImageFrame", "Table<Image>"),
|
||||
// `ImageFrame` to `Image` rename
|
||||
("Instances<ImageFrame>", "Table<Image>"),
|
||||
// `Instances` to `Table` rename
|
||||
("Instances<VectorData>", "Table<Vector>"),
|
||||
("Instances<GraphicGroup>", "Table<Graphic>"),
|
||||
("Instances<Image>", "Table<Image>"),
|
||||
("Instances<GraphicElement>", "Table<Graphic>"),
|
||||
("Table<GraphicElement>", "Table<Graphic>"),
|
||||
("Future<Instances<Vector>>", "Future<Table<Vector>>"),
|
||||
("Future<Instances<GraphicGroup>>", "Future<Table<Graphic>>"),
|
||||
("Future<Instances<Image>>", "Future<Table<Image>>"),
|
||||
("Future<Instances<GraphicElement>>", "Future<Table<Graphic>>"),
|
||||
("Future<Table<GraphicElement>>", "Future<Table<Graphic>>"),
|
||||
("Future<Table<VectorData>>", "Future<Table<Vector>>"),
|
||||
("Table<VectorData>", "Table<Vector>"),
|
||||
("Table<GraphicGroup>", "Table<Graphic>"),
|
||||
("Future<Table<GraphicGroup>>", "Future<Table<Graphic>>"),
|
||||
("Table<Group>", "Table<Graphic>"),
|
||||
("Future<Table<Group>>", "Future<Table<Graphic>>"),
|
||||
];
|
||||
|
||||
let mut names = Vec::<String>::deserialize(deserializer)?;
|
||||
|
||||
@@ -8,7 +8,8 @@ use crate::messages::tool::common_functionality::shape_editor::ShapeState;
|
||||
use crate::messages::tool::utility_types::ToolType;
|
||||
use glam::{DAffine2, DMat2, DVec2};
|
||||
use graphene_std::renderer::Quad;
|
||||
use graphene_std::vector::{HandleExt, HandleId, ManipulatorPointId, PointId, VectorModificationType};
|
||||
use graphene_std::vector::misc::{HandleId, ManipulatorPointId};
|
||||
use graphene_std::vector::{HandleExt, PointId, VectorModificationType};
|
||||
use std::collections::{HashMap, VecDeque};
|
||||
use std::f64::consts::PI;
|
||||
|
||||
@@ -79,7 +80,7 @@ impl OriginalTransforms {
|
||||
if path_map.contains_key(&layer) {
|
||||
continue;
|
||||
}
|
||||
let Some(vector_data) = network_interface.compute_modified_vector(layer) else {
|
||||
let Some(vector) = network_interface.compute_modified_vector(layer) else {
|
||||
continue;
|
||||
};
|
||||
let Some(selected_points) = shape_editor.selected_points_in_layer(layer) else {
|
||||
@@ -91,7 +92,7 @@ impl OriginalTransforms {
|
||||
|
||||
let mut selected_points = selected_points.clone();
|
||||
|
||||
for (segment_id, _, start, end) in vector_data.segment_bezier_iter() {
|
||||
for (segment_id, _, start, end) in vector.segment_bezier_iter() {
|
||||
if selected_segments.contains(&segment_id) {
|
||||
selected_points.insert(ManipulatorPointId::Anchor(start));
|
||||
selected_points.insert(ManipulatorPointId::Anchor(end));
|
||||
@@ -100,23 +101,23 @@ impl OriginalTransforms {
|
||||
|
||||
// Anchors also move their handles
|
||||
let anchor_ids = selected_points.iter().filter_map(|point| point.as_anchor());
|
||||
let anchors = anchor_ids.filter_map(|id| vector_data.point_domain.position_from_id(id).map(|pos| (id, AnchorPoint { initial: pos, current: pos })));
|
||||
let anchors = anchor_ids.filter_map(|id| vector.point_domain.position_from_id(id).map(|pos| (id, AnchorPoint { initial: pos, current: pos })));
|
||||
let anchors = anchors.collect();
|
||||
|
||||
let selected_handles = selected_points.iter().filter_map(|point| point.as_handle());
|
||||
let anchor_ids = selected_points.iter().filter_map(|point| point.as_anchor());
|
||||
let connected_handles = anchor_ids.flat_map(|point| vector_data.all_connected(point));
|
||||
let connected_handles = anchor_ids.flat_map(|point| vector.all_connected(point));
|
||||
let all_handles = selected_handles.chain(connected_handles);
|
||||
|
||||
let handles = all_handles
|
||||
.filter_map(|id| {
|
||||
let anchor = id.to_manipulator_point().get_anchor(&vector_data)?;
|
||||
let initial = id.to_manipulator_point().get_position(&vector_data)?;
|
||||
let relative = vector_data.point_domain.position_from_id(anchor)?;
|
||||
let other_handle = vector_data
|
||||
let anchor = id.to_manipulator_point().get_anchor(&vector)?;
|
||||
let initial = id.to_manipulator_point().get_position(&vector)?;
|
||||
let relative = vector.point_domain.position_from_id(anchor)?;
|
||||
let other_handle = vector
|
||||
.other_colinear_handle(id)
|
||||
.filter(|other| !selected_points.contains(&other.to_manipulator_point()) && !selected_points.contains(&ManipulatorPointId::Anchor(anchor)));
|
||||
let mirror = other_handle.and_then(|id| Some((id, id.to_manipulator_point().get_position(&vector_data)?)));
|
||||
let mirror = other_handle.and_then(|id| Some((id, id.to_manipulator_point().get_position(&vector)?)));
|
||||
|
||||
Some((id, HandlePoint { initial, relative, anchor, mirror }))
|
||||
})
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
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;
|
||||
use graphene_std::{uuid::NodeId, vector::misc::dvec2_to_point};
|
||||
use kurbo::{BezPath, DEFAULT_ACCURACY, Line, Point, Shape};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
|
||||
pub struct WirePath {
|
||||
@@ -53,7 +52,7 @@ impl GraphWireStyle {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn build_vector_wire(output_position: DVec2, input_position: DVec2, vertical_out: bool, vertical_in: bool, graph_wire_style: GraphWireStyle) -> Subpath<PointId> {
|
||||
pub fn build_vector_wire(output_position: DVec2, input_position: DVec2, vertical_out: bool, vertical_in: bool, graph_wire_style: GraphWireStyle) -> BezPath {
|
||||
let grid_spacing = 24.;
|
||||
match graph_wire_style {
|
||||
GraphWireStyle::Direct => {
|
||||
@@ -85,44 +84,21 @@ pub fn build_vector_wire(output_position: DVec2, input_position: DVec2, vertical
|
||||
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,
|
||||
)
|
||||
let mut wire = BezPath::new();
|
||||
wire.move_to(dvec2_to_point(locations[0]));
|
||||
wire.line_to(dvec2_to_point(locations[1]));
|
||||
wire.curve_to(dvec2_to_point(locations[1] + delta01), dvec2_to_point(locations[2] - delta23), dvec2_to_point(locations[2]));
|
||||
wire.line_to(dvec2_to_point(locations[3]));
|
||||
wire
|
||||
}
|
||||
GraphWireStyle::GridAligned => {
|
||||
let locations = straight_wire_paths(output_position, input_position, vertical_out, vertical_in);
|
||||
straight_wire_subpath(locations)
|
||||
let locations = straight_wire_path(output_position, input_position, vertical_out, vertical_in);
|
||||
straight_wire_to_bezpath(locations)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn straight_wire_paths(output_position: DVec2, input_position: DVec2, vertical_out: bool, vertical_in: bool) -> Vec<IVec2> {
|
||||
fn straight_wire_path(output_position: DVec2, input_position: DVec2, vertical_out: bool, vertical_in: bool) -> Vec<IVec2> {
|
||||
let grid_spacing = 24;
|
||||
let line_width = 2;
|
||||
|
||||
@@ -446,40 +422,24 @@ fn straight_wire_paths(output_position: DVec2, input_position: DVec2, vertical_o
|
||||
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> {
|
||||
fn straight_wire_to_bezpath(locations: Vec<IVec2>) -> BezPath {
|
||||
if locations.is_empty() {
|
||||
return Subpath::new(Vec::new(), false);
|
||||
return BezPath::new();
|
||||
}
|
||||
|
||||
let to_point = |location: IVec2| Point::new(location.x as f64, location.y as f64);
|
||||
|
||||
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 p1 = to_point(locations[0]);
|
||||
let p2 = to_point(locations[1]);
|
||||
Line::new(p1, p2).to_path(DEFAULT_ACCURACY);
|
||||
}
|
||||
|
||||
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(),
|
||||
}];
|
||||
let mut path = BezPath::new();
|
||||
path.move_to(to_point(locations[0]));
|
||||
|
||||
for i in 1..(locations.len() - 1) {
|
||||
let prev = locations[i - 1];
|
||||
@@ -563,27 +523,9 @@ fn straight_wire_subpath(locations: Vec<IVec2>) -> Subpath<PointId> {
|
||||
},
|
||||
);
|
||||
|
||||
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.line_to(to_point(corner_start));
|
||||
path.curve_to(to_point(corner_start_mid), to_point(corner_end_mid), to_point(corner_end));
|
||||
}
|
||||
|
||||
path.push(ManipulatorGroup {
|
||||
anchor: (*locations.last().unwrap()).into(),
|
||||
in_handle: None,
|
||||
out_handle: None,
|
||||
id: PointId::generate(),
|
||||
});
|
||||
Subpath::new(path, false)
|
||||
path.line_to(to_point(*locations.last().unwrap()));
|
||||
path
|
||||
}
|
||||
|
||||
@@ -10,10 +10,11 @@ use glam::IVec2;
|
||||
use graph_craft::document::DocumentNode;
|
||||
use graph_craft::document::{DocumentNodeImplementation, NodeInput, value::TaggedValue};
|
||||
use graphene_std::ProtoNodeIdentifier;
|
||||
use graphene_std::table::Table;
|
||||
use graphene_std::text::{TextAlign, TypesettingConfig};
|
||||
use graphene_std::uuid::NodeId;
|
||||
use graphene_std::vector::Vector;
|
||||
use graphene_std::vector::style::{PaintOrder, StrokeAlign};
|
||||
use graphene_std::vector::{VectorData, VectorDataTable};
|
||||
use std::collections::HashMap;
|
||||
|
||||
const TEXT_REPLACEMENTS: &[(&str, &str)] = &[
|
||||
@@ -27,22 +28,56 @@ pub struct NodeReplacement<'a> {
|
||||
}
|
||||
|
||||
const NODE_REPLACEMENTS: &[NodeReplacement<'static>] = &[
|
||||
// graphic element
|
||||
// artboard
|
||||
NodeReplacement {
|
||||
node: graphene_std::graphic_element::append_artboard::IDENTIFIER,
|
||||
aliases: &["graphene_core::AddArtboardNode"],
|
||||
node: graphene_std::artboard::create_artboard::IDENTIFIER,
|
||||
aliases: &[
|
||||
"graphene_core::ConstructArtboardNode",
|
||||
"graphene_core::graphic_element::ToArtboardNode",
|
||||
"graphene_core::artboard::ToArtboardNode",
|
||||
],
|
||||
},
|
||||
// graphic
|
||||
NodeReplacement {
|
||||
node: graphene_std::graphic::to_graphic::IDENTIFIER,
|
||||
aliases: &[
|
||||
"graphene_core::ToGraphicGroupNode",
|
||||
"graphene_core::graphic_element::ToGroupNode",
|
||||
"graphene_core::graphic::ToGroupNode",
|
||||
],
|
||||
},
|
||||
NodeReplacement {
|
||||
node: graphene_std::graphic_element::to_artboard::IDENTIFIER,
|
||||
aliases: &["graphene_core::ConstructArtboardNode"],
|
||||
node: graphene_std::graphic::wrap_graphic::IDENTIFIER,
|
||||
aliases: &[
|
||||
// Converted from "To Element"
|
||||
"graphene_core::ToGraphicElementNode",
|
||||
"graphene_core::graphic_element::ToElementNode",
|
||||
"graphene_core::graphic::ToElementNode",
|
||||
],
|
||||
},
|
||||
NodeReplacement {
|
||||
node: graphene_std::graphic_element::to_element::IDENTIFIER,
|
||||
aliases: &["graphene_core::ToGraphicElementNode"],
|
||||
node: graphene_std::graphic::legacy_layer_extend::IDENTIFIER,
|
||||
aliases: &[
|
||||
"graphene_core::graphic_element::LayerNode",
|
||||
"graphene_core::graphic::LayerNode",
|
||||
// Converted from "Append Artboard"
|
||||
"graphene_core::AddArtboardNode",
|
||||
"graphene_core::graphic_element::AppendArtboardNode",
|
||||
"graphene_core::graphic::AppendArtboardNode",
|
||||
"graphene_core::artboard::AppendArtboardNode",
|
||||
],
|
||||
},
|
||||
NodeReplacement {
|
||||
node: graphene_std::graphic_element::to_group::IDENTIFIER,
|
||||
aliases: &["graphene_core::ToGraphicGroupNode"],
|
||||
node: graphene_std::graphic::flatten_graphic::IDENTIFIER,
|
||||
aliases: &["graphene_core::graphic_element::FlattenGroupNode", "graphene_core::graphic::FlattenGroupNode"],
|
||||
},
|
||||
NodeReplacement {
|
||||
node: graphene_std::graphic::flatten_vector::IDENTIFIER,
|
||||
aliases: &["graphene_core::graphic_element::FlattenVectorNode"],
|
||||
},
|
||||
NodeReplacement {
|
||||
node: graphene_std::graphic::index::IDENTIFIER,
|
||||
aliases: &["graphene_core::graphic_element::IndexNode"],
|
||||
},
|
||||
// math_nodes
|
||||
NodeReplacement {
|
||||
@@ -228,8 +263,8 @@ const NODE_REPLACEMENTS: &[NodeReplacement<'static>] = &[
|
||||
aliases: &["graphene_core::ops::SomeNode"],
|
||||
},
|
||||
NodeReplacement {
|
||||
node: graphene_std::debug::unwrap::IDENTIFIER,
|
||||
aliases: &["graphene_core::ops::UnwrapNode"],
|
||||
node: graphene_std::debug::unwrap_option::IDENTIFIER,
|
||||
aliases: &["graphene_core::ops::UnwrapNode", "graphene_core::debug::UnwrapNode"],
|
||||
},
|
||||
NodeReplacement {
|
||||
node: graphene_std::debug::clone::IDENTIFIER,
|
||||
@@ -386,7 +421,7 @@ const NODE_REPLACEMENTS: &[NodeReplacement<'static>] = &[
|
||||
},
|
||||
NodeReplacement {
|
||||
node: graphene_std::raster_nodes::std_nodes::image_value::IDENTIFIER,
|
||||
aliases: &["graphene_std::raster::ImageValueNode"],
|
||||
aliases: &["graphene_std::raster::ImageValueNode", "graphene_std::raster::ImageNode"],
|
||||
},
|
||||
NodeReplacement {
|
||||
node: graphene_std::raster_nodes::std_nodes::noise_pattern::IDENTIFIER,
|
||||
@@ -455,6 +490,10 @@ const NODE_REPLACEMENTS: &[NodeReplacement<'static>] = &[
|
||||
node: graphene_std::path_bool::boolean_operation::IDENTIFIER,
|
||||
aliases: &["graphene_std::vector::BooleanOperationNode"],
|
||||
},
|
||||
NodeReplacement {
|
||||
node: graphene_std::vector::path_modify::IDENTIFIER,
|
||||
aliases: &["graphene_core::vector::vector_data::modification::PathModifyNode"],
|
||||
},
|
||||
// brush
|
||||
NodeReplacement {
|
||||
node: graphene_std::brush::brush::brush_stamp_generator::IDENTIFIER,
|
||||
@@ -590,13 +629,13 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId],
|
||||
return None;
|
||||
}
|
||||
|
||||
// Obtain the document node for the given node ID, extract the vector points, and create vector data from the list of points
|
||||
// Obtain the document node for the given node ID, extract the vector points, and create a Vector path from the list of points
|
||||
let node = document.network_interface.document_node(node_id, network_path)?;
|
||||
let Some(TaggedValue::VecDVec2(points)) = node.inputs.get(1).and_then(|tagged_value| tagged_value.as_value()) else {
|
||||
log::error!("The old Spline node's input at index 1 is not a TaggedValue::VecDVec2");
|
||||
return None;
|
||||
};
|
||||
let vector_data = VectorData::from_subpath(Subpath::from_anchors_linear(points.to_vec(), false));
|
||||
let vector = Vector::from_subpath(Subpath::from_anchors_linear(points.to_vec(), false));
|
||||
|
||||
// Retrieve the output connectors linked to the "Spline" node's output port
|
||||
let Some(spline_outputs) = document.network_interface.outward_wires(network_path)?.get(&OutputConnector::node(*node_id, 0)).cloned() else {
|
||||
@@ -610,13 +649,13 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId],
|
||||
return None;
|
||||
};
|
||||
|
||||
// Get the "Path" node definition and fill it in with the vector data and default vector modification
|
||||
// Get the "Path" node definition and fill it in with the Vector path and default vector modification
|
||||
let Some(path_node_type) = resolve_document_node_type("Path") else {
|
||||
log::error!("Path node does not exist.");
|
||||
return None;
|
||||
};
|
||||
let path_node = path_node_type.node_template_input_override([
|
||||
Some(NodeInput::value(TaggedValue::VectorData(VectorDataTable::new(vector_data)), true)),
|
||||
Some(NodeInput::value(TaggedValue::Vector(Table::new_from_element(vector)), true)),
|
||||
Some(NodeInput::value(TaggedValue::VectorModification(Default::default()), false)),
|
||||
]);
|
||||
|
||||
@@ -791,7 +830,7 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId],
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 4), old_inputs[3].clone(), network_path);
|
||||
}
|
||||
|
||||
// Upgrade artboard name being passed as hidden value input to "To Artboard"
|
||||
// Upgrade artboard name being passed as hidden value input to "Create Artboard"
|
||||
if reference == "Artboard" && reset_node_definitions_on_open {
|
||||
let label = document.network_interface.display_name(node_id, network_path);
|
||||
document
|
||||
|
||||
@@ -19,7 +19,7 @@ pub struct MenuBarMessageHandler {
|
||||
pub spreadsheet_view_open: bool,
|
||||
pub message_logging_verbosity: MessageLoggingVerbosity,
|
||||
pub reset_node_definitions_on_open: bool,
|
||||
pub single_path_node_compatible_layer_selected: bool,
|
||||
pub make_path_editable_is_allowed: bool,
|
||||
}
|
||||
|
||||
#[message_handler_data]
|
||||
@@ -46,7 +46,7 @@ impl LayoutHolder for MenuBarMessageHandler {
|
||||
let message_logging_verbosity_names = self.message_logging_verbosity == MessageLoggingVerbosity::Names;
|
||||
let message_logging_verbosity_contents = self.message_logging_verbosity == MessageLoggingVerbosity::Contents;
|
||||
let reset_node_definitions_on_open = self.reset_node_definitions_on_open;
|
||||
let single_path_node_compatible_layer_selected = self.single_path_node_compatible_layer_selected;
|
||||
let make_path_editable_is_allowed = self.make_path_editable_is_allowed;
|
||||
|
||||
let menu_bar_entries = vec![
|
||||
MenuBarEntry {
|
||||
@@ -359,8 +359,8 @@ impl LayoutHolder for MenuBarMessageHandler {
|
||||
|
||||
choices
|
||||
.into_iter()
|
||||
.map(|group| {
|
||||
group
|
||||
.map(|section| {
|
||||
section
|
||||
.into_iter()
|
||||
.map(|(axis, aggregate, icon, name)| MenuBarEntry {
|
||||
label: name.into(),
|
||||
@@ -442,7 +442,7 @@ impl LayoutHolder for MenuBarMessageHandler {
|
||||
icon: Some("NodeShape".into()),
|
||||
shortcut: None,
|
||||
action: MenuBarEntry::create_action(|_| NodeGraphMessage::AddPathNode.into()),
|
||||
disabled: !single_path_node_compatible_layer_selected,
|
||||
disabled: !make_path_editable_is_allowed,
|
||||
..MenuBarEntry::default()
|
||||
}],
|
||||
]),
|
||||
|
||||
@@ -88,6 +88,9 @@ pub enum PortfolioMessage {
|
||||
PasteSerializedData {
|
||||
data: String,
|
||||
},
|
||||
PasteSerializedVector {
|
||||
data: String,
|
||||
},
|
||||
CenterPastedLayers {
|
||||
layers: Vec<LayerNodeIdentifier>,
|
||||
},
|
||||
|
||||
@@ -3,7 +3,7 @@ use super::document::utility_types::network_interface;
|
||||
use super::spreadsheet::SpreadsheetMessageHandler;
|
||||
use super::utility_types::{PanelType, PersistentData};
|
||||
use crate::application::generate_uuid;
|
||||
use crate::consts::DEFAULT_DOCUMENT_NAME;
|
||||
use crate::consts::{DEFAULT_DOCUMENT_NAME, DEFAULT_STROKE_WIDTH};
|
||||
use crate::messages::animation::TimingInformation;
|
||||
use crate::messages::debug::utility_types::MessageLoggingVerbosity;
|
||||
use crate::messages::dialog::simple_dialogs;
|
||||
@@ -12,6 +12,7 @@ use crate::messages::layout::utility_types::widget_prelude::*;
|
||||
use crate::messages::portfolio::document::DocumentMessageContext;
|
||||
use crate::messages::portfolio::document::graph_operation::utility_types::TransformIn;
|
||||
use crate::messages::portfolio::document::node_graph::document_node_definitions;
|
||||
use crate::messages::portfolio::document::node_graph::document_node_definitions::resolve_document_node_type;
|
||||
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;
|
||||
@@ -19,13 +20,17 @@ use crate::messages::portfolio::document_migration::*;
|
||||
use crate::messages::preferences::SelectionMode;
|
||||
use crate::messages::prelude::*;
|
||||
use crate::messages::tool::common_functionality::graph_modification_utils;
|
||||
use crate::messages::tool::common_functionality::utility_functions::make_path_editable_is_allowed;
|
||||
use crate::messages::tool::utility_types::{HintData, HintGroup, ToolType};
|
||||
use crate::node_graph_executor::{ExportConfig, NodeGraphExecutor};
|
||||
use bezier_rs::BezierHandles;
|
||||
use glam::{DAffine2, DVec2};
|
||||
use graph_craft::document::NodeId;
|
||||
use graph_craft::document::value::TaggedValue;
|
||||
use graphene_std::Color;
|
||||
use graphene_std::renderer::Quad;
|
||||
use graphene_std::text::Font;
|
||||
use graphene_std::vector::misc::HandleId;
|
||||
use graphene_std::vector::{PointId, SegmentId, Vector, VectorModificationType};
|
||||
use std::vec;
|
||||
|
||||
#[derive(ExtractField)]
|
||||
@@ -50,7 +55,7 @@ pub struct PortfolioMessageHandler {
|
||||
pub persistent_data: PersistentData,
|
||||
pub executor: NodeGraphExecutor,
|
||||
pub selection_mode: SelectionMode,
|
||||
/// The spreadsheet UI allows for instance data to be previewed.
|
||||
/// The spreadsheet UI allows for graph data to be previewed.
|
||||
pub spreadsheet: SpreadsheetMessageHandler,
|
||||
device_pixel_ratio: Option<f64>,
|
||||
pub reset_node_definitions_on_open: bool,
|
||||
@@ -80,7 +85,7 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
|
||||
self.menu_bar_message_handler.has_selected_nodes = false;
|
||||
self.menu_bar_message_handler.has_selected_layers = false;
|
||||
self.menu_bar_message_handler.has_selection_history = (false, false);
|
||||
self.menu_bar_message_handler.single_path_node_compatible_layer_selected = false;
|
||||
self.menu_bar_message_handler.make_path_editable_is_allowed = false;
|
||||
self.menu_bar_message_handler.spreadsheet_view_open = self.spreadsheet.spreadsheet_view_open;
|
||||
self.menu_bar_message_handler.message_logging_verbosity = message_logging_verbosity;
|
||||
self.menu_bar_message_handler.reset_node_definitions_on_open = reset_node_definitions_on_open;
|
||||
@@ -98,30 +103,7 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
|
||||
let metadata = &document.network_interface.document_network_metadata().persistent_metadata;
|
||||
(!metadata.selection_undo_history.is_empty(), !metadata.selection_redo_history.is_empty())
|
||||
};
|
||||
self.menu_bar_message_handler.single_path_node_compatible_layer_selected = {
|
||||
let selected_nodes = document.network_interface.selected_nodes();
|
||||
let mut selected_layers = selected_nodes.selected_layers(document.metadata());
|
||||
let first_layer = selected_layers.next();
|
||||
let second_layer = selected_layers.next();
|
||||
let has_single_selection = first_layer.is_some() && second_layer.is_none();
|
||||
|
||||
let compatible_type = first_layer.and_then(|layer| {
|
||||
let graph_layer = graph_modification_utils::NodeGraphLayer::new(layer, &document.network_interface);
|
||||
graph_layer.horizontal_layer_flow().nth(1).map(|node_id| {
|
||||
let (output_type, _) = document.network_interface.output_type(&node_id, 0, &[]);
|
||||
format!("type:{}", output_type.nested_type())
|
||||
})
|
||||
});
|
||||
|
||||
let is_compatible = compatible_type.as_deref() == Some("type:Instances<VectorData>");
|
||||
|
||||
let is_modifiable = first_layer.is_some_and(|layer| {
|
||||
let graph_layer = graph_modification_utils::NodeGraphLayer::new(layer, &document.network_interface);
|
||||
matches!(graph_layer.find_input("Path", 1), Some(TaggedValue::VectorModification(_)))
|
||||
});
|
||||
|
||||
first_layer.is_some() && has_single_selection && is_compatible && !is_modifiable
|
||||
}
|
||||
self.menu_bar_message_handler.make_path_editable_is_allowed = make_path_editable_is_allowed(&document.network_interface, document.metadata()).is_some();
|
||||
}
|
||||
|
||||
self.menu_bar_message_handler.process_message(message, responses, ());
|
||||
@@ -365,12 +347,13 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
|
||||
let inspect_node = self.inspect_node_id();
|
||||
if let Ok(message) = self.executor.submit_node_graph_evaluation(
|
||||
self.documents.get_mut(document_id).expect("Tried to render non-existent document"),
|
||||
*document_id,
|
||||
ipp.viewport_bounds.size().as_uvec2(),
|
||||
timing_information,
|
||||
inspect_node,
|
||||
true,
|
||||
) {
|
||||
responses.add(message);
|
||||
responses.add_front(message);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -396,16 +379,19 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
|
||||
PortfolioMessage::NewDocumentWithName { name } => {
|
||||
let mut new_document = DocumentMessageHandler::default();
|
||||
new_document.name = name;
|
||||
responses.add(DocumentMessage::PTZUpdate);
|
||||
let mut new_responses = VecDeque::new();
|
||||
new_responses.add(DocumentMessage::PTZUpdate);
|
||||
|
||||
let document_id = DocumentId(generate_uuid());
|
||||
if self.active_document().is_some() {
|
||||
responses.add(BroadcastEvent::ToolAbort);
|
||||
responses.add(NavigationMessage::CanvasPan { delta: (0., 0.).into() });
|
||||
new_responses.add(BroadcastEvent::ToolAbort);
|
||||
new_responses.add(NavigationMessage::CanvasPan { delta: (0., 0.).into() });
|
||||
}
|
||||
|
||||
self.load_document(new_document, document_id, responses, false);
|
||||
responses.add(PortfolioMessage::SelectDocument { document_id });
|
||||
self.load_document(new_document, document_id, &mut new_responses, false);
|
||||
new_responses.add(PortfolioMessage::SelectDocument { document_id });
|
||||
new_responses.extend(responses.drain(..));
|
||||
*responses = new_responses;
|
||||
}
|
||||
PortfolioMessage::NextDocument => {
|
||||
if let Some(active_document_id) = self.active_document_id {
|
||||
@@ -576,6 +562,99 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
|
||||
}
|
||||
}
|
||||
}
|
||||
// Custom paste implementation for Path tool
|
||||
PortfolioMessage::PasteSerializedVector { data } => {
|
||||
// If using Path tool then send the operation to Path tool
|
||||
if *current_tool == ToolType::Path {
|
||||
responses.add(PathToolMessage::Paste { data });
|
||||
return;
|
||||
}
|
||||
|
||||
// If not using Path tool, create new layers and add paths into those
|
||||
if let Some(document) = self.active_document() {
|
||||
let Ok(data) = serde_json::from_str::<Vec<(LayerNodeIdentifier, Vector, DAffine2)>>(&data) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let mut layers = Vec::new();
|
||||
|
||||
for (_, new_vector, transform) in data {
|
||||
let Some(node_type) = resolve_document_node_type("Path") else {
|
||||
error!("Path node does not exist");
|
||||
continue;
|
||||
};
|
||||
let nodes = vec![(NodeId(0), node_type.default_node_template())];
|
||||
|
||||
let parent = document.new_layer_parent(false);
|
||||
|
||||
let layer = graph_modification_utils::new_custom(NodeId::new(), nodes, parent, responses);
|
||||
layers.push(layer);
|
||||
|
||||
// Adding the transform back into the layer
|
||||
responses.add(GraphOperationMessage::TransformSet {
|
||||
layer,
|
||||
transform,
|
||||
transform_in: TransformIn::Local,
|
||||
skip_rerender: false,
|
||||
});
|
||||
|
||||
// Add default fill and stroke to the layer
|
||||
let fill_color = Color::WHITE;
|
||||
let stroke_color = Color::BLACK;
|
||||
|
||||
let fill = graphene_std::vector::style::Fill::solid(fill_color.to_gamma_srgb());
|
||||
responses.add(GraphOperationMessage::FillSet { layer, fill });
|
||||
|
||||
let stroke = graphene_std::vector::style::Stroke::new(Some(stroke_color.to_gamma_srgb()), DEFAULT_STROKE_WIDTH);
|
||||
responses.add(GraphOperationMessage::StrokeSet { layer, stroke });
|
||||
|
||||
// Create new point ids and add those into the existing Vector path
|
||||
let mut points_map = HashMap::new();
|
||||
for (point, position) in new_vector.point_domain.iter() {
|
||||
let new_point_id = PointId::generate();
|
||||
points_map.insert(point, new_point_id);
|
||||
let modification_type = VectorModificationType::InsertPoint { id: new_point_id, position };
|
||||
responses.add(GraphOperationMessage::Vector { layer, modification_type });
|
||||
}
|
||||
|
||||
// Create new segment ids and add the segments into the existing Vector path
|
||||
let mut segments_map = HashMap::new();
|
||||
for (segment_id, bezier, start, end) in new_vector.segment_bezier_iter() {
|
||||
let new_segment_id = SegmentId::generate();
|
||||
|
||||
segments_map.insert(segment_id, new_segment_id);
|
||||
|
||||
let handles = match bezier.handles {
|
||||
BezierHandles::Linear => [None, None],
|
||||
BezierHandles::Quadratic { handle } => [Some(handle - bezier.start), None],
|
||||
BezierHandles::Cubic { handle_start, handle_end } => [Some(handle_start - bezier.start), Some(handle_end - bezier.end)],
|
||||
};
|
||||
|
||||
let points = [points_map[&start], points_map[&end]];
|
||||
let modification_type = VectorModificationType::InsertSegment { id: new_segment_id, points, handles };
|
||||
responses.add(GraphOperationMessage::Vector { layer, modification_type });
|
||||
}
|
||||
|
||||
// Set G1 continuity
|
||||
for handles in new_vector.colinear_manipulators {
|
||||
let to_new_handle = |handle: HandleId| -> HandleId {
|
||||
HandleId {
|
||||
ty: handle.ty,
|
||||
segment: segments_map[&handle.segment],
|
||||
}
|
||||
};
|
||||
let new_handles = [to_new_handle(handles[0]), to_new_handle(handles[1])];
|
||||
let modification_type = VectorModificationType::SetG1Continuous { handles: new_handles, enabled: true };
|
||||
responses.add(GraphOperationMessage::Vector { layer, modification_type });
|
||||
}
|
||||
}
|
||||
|
||||
responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||
responses.add(Message::Defer(DeferMessage::AfterGraphRun {
|
||||
messages: vec![PortfolioMessage::CenterPastedLayers { layers }.into()],
|
||||
}));
|
||||
}
|
||||
}
|
||||
PortfolioMessage::CenterPastedLayers { layers } => {
|
||||
if let Some(document) = self.active_document_mut() {
|
||||
let viewport_bounds_quad_pixels = Quad::from_box([DVec2::ZERO, ipp.viewport_bounds.size()]);
|
||||
@@ -824,7 +903,7 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
|
||||
transparent_background,
|
||||
..Default::default()
|
||||
};
|
||||
let result = self.executor.submit_document_export(document, export_config);
|
||||
let result = self.executor.submit_document_export(document, self.active_document_id.unwrap(), export_config);
|
||||
|
||||
if let Err(description) = result {
|
||||
responses.add(DialogMessage::DisplayDialogError {
|
||||
@@ -842,6 +921,7 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
|
||||
let inspect_node = self.inspect_node_id();
|
||||
let result = self.executor.submit_node_graph_evaluation(
|
||||
self.documents.get_mut(&document_id).expect("Tried to render non-existent document"),
|
||||
document_id,
|
||||
ipp.viewport_bounds.size().as_uvec2(),
|
||||
timing_information,
|
||||
inspect_node,
|
||||
@@ -855,7 +935,7 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
|
||||
description,
|
||||
});
|
||||
}
|
||||
Ok(message) => responses.add(message),
|
||||
Ok(message) => responses.add_front(message),
|
||||
}
|
||||
}
|
||||
PortfolioMessage::ToggleRulers => {
|
||||
@@ -888,7 +968,7 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
|
||||
responses.add(FrontendMessage::UpdateOpenDocumentsList { open_documents });
|
||||
}
|
||||
PortfolioMessage::UpdateVelloPreference => {
|
||||
let active = if cfg!(target_arch = "wasm32") { false } else { preferences.use_vello };
|
||||
let active = if cfg!(target_family = "wasm") { false } else { preferences.use_vello };
|
||||
responses.add(FrontendMessage::UpdateViewportHolePunch { active });
|
||||
responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||
self.persistent_data.use_vello = preferences.use_vello;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use crate::messages::prelude::*;
|
||||
use crate::node_graph_executor::InspectResult;
|
||||
|
||||
/// The spreadsheet UI allows for instance data to be previewed.
|
||||
/// The spreadsheet UI allows for graph data to be previewed.
|
||||
#[impl_message(Message, PortfolioMessage, Spreadsheet)]
|
||||
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]
|
||||
pub enum SpreadsheetMessage {
|
||||
@@ -12,20 +12,20 @@ pub enum SpreadsheetMessage {
|
||||
inspect_result: InspectResult,
|
||||
},
|
||||
|
||||
PushToInstancePath {
|
||||
PushToElementPath {
|
||||
index: usize,
|
||||
},
|
||||
TruncateInstancePath {
|
||||
TruncateElementPath {
|
||||
len: usize,
|
||||
},
|
||||
|
||||
ViewVectorDataDomain {
|
||||
domain: VectorDataDomain,
|
||||
ViewVectorDomain {
|
||||
domain: VectorDomain,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Clone, Copy, Default, Debug, serde::Serialize, serde::Deserialize)]
|
||||
pub enum VectorDataDomain {
|
||||
pub enum VectorDomain {
|
||||
#[default]
|
||||
Points,
|
||||
Segments,
|
||||
|
||||
@@ -1,28 +1,27 @@
|
||||
use super::VectorDataDomain;
|
||||
use super::VectorDomain;
|
||||
use crate::messages::layout::utility_types::layout_widget::{Layout, LayoutGroup, LayoutTarget, WidgetLayout};
|
||||
use crate::messages::prelude::*;
|
||||
use crate::messages::tool::tool_messages::tool_prelude::*;
|
||||
use graph_craft::document::NodeId;
|
||||
use graphene_std::Color;
|
||||
use graphene_std::Context;
|
||||
use graphene_std::GraphicGroupTable;
|
||||
use graphene_std::instances::Instances;
|
||||
use graphene_std::memo::IORecord;
|
||||
use graphene_std::raster::Image;
|
||||
use graphene_std::vector::{VectorData, VectorDataTable};
|
||||
use graphene_std::{Artboard, ArtboardGroupTable, GraphicElement};
|
||||
use graphene_std::table::Table;
|
||||
use graphene_std::vector::Vector;
|
||||
use graphene_std::{Artboard, Graphic};
|
||||
use std::any::Any;
|
||||
use std::sync::Arc;
|
||||
|
||||
/// The spreadsheet UI allows for instance data to be previewed.
|
||||
/// The spreadsheet UI allows for graph data to be previewed.
|
||||
#[derive(Default, Debug, Clone, ExtractField)]
|
||||
pub struct SpreadsheetMessageHandler {
|
||||
/// Sets whether or not the spreadsheet is drawn.
|
||||
pub spreadsheet_view_open: bool,
|
||||
inspect_node: Option<NodeId>,
|
||||
introspected_data: Option<Arc<dyn Any + Send + Sync>>,
|
||||
instances_path: Vec<usize>,
|
||||
viewing_vector_data_domain: VectorDataDomain,
|
||||
element_path: Vec<usize>,
|
||||
viewing_vector_domain: VectorDomain,
|
||||
}
|
||||
|
||||
#[message_handler_data]
|
||||
@@ -46,17 +45,17 @@ impl MessageHandler<SpreadsheetMessage, ()> for SpreadsheetMessageHandler {
|
||||
self.update_layout(responses)
|
||||
}
|
||||
|
||||
SpreadsheetMessage::PushToInstancePath { index } => {
|
||||
self.instances_path.push(index);
|
||||
SpreadsheetMessage::PushToElementPath { index } => {
|
||||
self.element_path.push(index);
|
||||
self.update_layout(responses);
|
||||
}
|
||||
SpreadsheetMessage::TruncateInstancePath { len } => {
|
||||
self.instances_path.truncate(len);
|
||||
SpreadsheetMessage::TruncateElementPath { len } => {
|
||||
self.element_path.truncate(len);
|
||||
self.update_layout(responses);
|
||||
}
|
||||
|
||||
SpreadsheetMessage::ViewVectorDataDomain { domain } => {
|
||||
self.viewing_vector_data_domain = domain;
|
||||
SpreadsheetMessage::ViewVectorDomain { domain } => {
|
||||
self.viewing_vector_domain = domain;
|
||||
self.update_layout(responses);
|
||||
}
|
||||
}
|
||||
@@ -78,9 +77,9 @@ impl SpreadsheetMessageHandler {
|
||||
}
|
||||
let mut layout_data = LayoutData {
|
||||
current_depth: 0,
|
||||
desired_path: &mut self.instances_path,
|
||||
desired_path: &mut self.element_path,
|
||||
breadcrumbs: Vec::new(),
|
||||
vector_data_domain: self.viewing_vector_data_domain,
|
||||
vector_domain: self.viewing_vector_domain,
|
||||
};
|
||||
let mut layout = self
|
||||
.introspected_data
|
||||
@@ -91,7 +90,7 @@ impl SpreadsheetMessageHandler {
|
||||
|
||||
if layout_data.breadcrumbs.len() > 1 {
|
||||
let breadcrumb = BreadcrumbTrailButtons::new(layout_data.breadcrumbs)
|
||||
.on_update(|&len| SpreadsheetMessage::TruncateInstancePath { len: len as usize }.into())
|
||||
.on_update(|&len| SpreadsheetMessage::TruncateElementPath { len: len as usize }.into())
|
||||
.widget_holder();
|
||||
layout.insert(0, LayoutGroup::Row { widgets: vec![breadcrumb] });
|
||||
}
|
||||
@@ -107,23 +106,23 @@ struct LayoutData<'a> {
|
||||
current_depth: usize,
|
||||
desired_path: &'a mut Vec<usize>,
|
||||
breadcrumbs: Vec<String>,
|
||||
vector_data_domain: VectorDataDomain,
|
||||
vector_domain: VectorDomain,
|
||||
}
|
||||
|
||||
fn generate_layout(introspected_data: &Arc<dyn std::any::Any + Send + Sync + 'static>, data: &mut LayoutData) -> Option<Vec<LayoutGroup>> {
|
||||
// We simply try random types. TODO: better strategy.
|
||||
#[allow(clippy::manual_map)]
|
||||
if let Some(io) = introspected_data.downcast_ref::<IORecord<Context, ArtboardGroupTable>>() {
|
||||
if let Some(io) = introspected_data.downcast_ref::<IORecord<Context, Table<Artboard>>>() {
|
||||
Some(io.output.layout_with_breadcrumb(data))
|
||||
} else if let Some(io) = introspected_data.downcast_ref::<IORecord<(), ArtboardGroupTable>>() {
|
||||
} else if let Some(io) = introspected_data.downcast_ref::<IORecord<(), Table<Artboard>>>() {
|
||||
Some(io.output.layout_with_breadcrumb(data))
|
||||
} else if let Some(io) = introspected_data.downcast_ref::<IORecord<Context, VectorDataTable>>() {
|
||||
} else if let Some(io) = introspected_data.downcast_ref::<IORecord<Context, Table<Vector>>>() {
|
||||
Some(io.output.layout_with_breadcrumb(data))
|
||||
} else if let Some(io) = introspected_data.downcast_ref::<IORecord<(), VectorDataTable>>() {
|
||||
} else if let Some(io) = introspected_data.downcast_ref::<IORecord<(), Table<Vector>>>() {
|
||||
Some(io.output.layout_with_breadcrumb(data))
|
||||
} else if let Some(io) = introspected_data.downcast_ref::<IORecord<Context, GraphicGroupTable>>() {
|
||||
} else if let Some(io) = introspected_data.downcast_ref::<IORecord<Context, Table<Graphic>>>() {
|
||||
Some(io.output.layout_with_breadcrumb(data))
|
||||
} else if let Some(io) = introspected_data.downcast_ref::<IORecord<(), GraphicGroupTable>>() {
|
||||
} else if let Some(io) = introspected_data.downcast_ref::<IORecord<(), Table<Graphic>>>() {
|
||||
Some(io.output.layout_with_breadcrumb(data))
|
||||
} else {
|
||||
None
|
||||
@@ -139,7 +138,7 @@ fn label(x: impl Into<String>) -> Vec<LayoutGroup> {
|
||||
vec![LayoutGroup::Row { widgets: error }]
|
||||
}
|
||||
|
||||
trait InstanceLayout {
|
||||
trait TableRowLayout {
|
||||
fn type_name() -> &'static str;
|
||||
fn identifier(&self) -> String;
|
||||
fn layout_with_breadcrumb(&self, data: &mut LayoutData) -> Vec<LayoutGroup> {
|
||||
@@ -149,66 +148,72 @@ trait InstanceLayout {
|
||||
fn compute_layout(&self, data: &mut LayoutData) -> Vec<LayoutGroup>;
|
||||
}
|
||||
|
||||
impl InstanceLayout for GraphicElement {
|
||||
impl TableRowLayout for Graphic {
|
||||
fn type_name() -> &'static str {
|
||||
"GraphicElement"
|
||||
"Graphic"
|
||||
}
|
||||
fn identifier(&self) -> String {
|
||||
match self {
|
||||
Self::GraphicGroup(instances) => instances.identifier(),
|
||||
Self::VectorData(instances) => instances.identifier(),
|
||||
Self::RasterDataCPU(_) => "RasterDataCPU".to_string(),
|
||||
Self::RasterDataGPU(_) => "RasterDataGPU".to_string(),
|
||||
Self::Graphic(graphic) => graphic.identifier(),
|
||||
Self::Vector(vector) => vector.identifier(),
|
||||
Self::RasterCPU(_) => "Raster (on CPU)".to_string(),
|
||||
Self::RasterGPU(_) => "Raster (on GPU)".to_string(),
|
||||
}
|
||||
}
|
||||
// Don't put a breadcrumb for GraphicElement
|
||||
// Don't put a breadcrumb for Graphic
|
||||
fn layout_with_breadcrumb(&self, data: &mut LayoutData) -> Vec<LayoutGroup> {
|
||||
self.compute_layout(data)
|
||||
}
|
||||
fn compute_layout(&self, data: &mut LayoutData) -> Vec<LayoutGroup> {
|
||||
match self {
|
||||
Self::GraphicGroup(instances) => instances.layout_with_breadcrumb(data),
|
||||
Self::VectorData(instances) => instances.layout_with_breadcrumb(data),
|
||||
Self::RasterDataCPU(_) => label("Raster frame not supported"),
|
||||
Self::RasterDataGPU(_) => label("Raster frame not supported"),
|
||||
Self::Graphic(table) => table.layout_with_breadcrumb(data),
|
||||
Self::Vector(table) => table.layout_with_breadcrumb(data),
|
||||
Self::RasterCPU(_) => label("Raster is not supported"),
|
||||
Self::RasterGPU(_) => label("Raster is not supported"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl InstanceLayout for VectorData {
|
||||
impl TableRowLayout for Vector {
|
||||
fn type_name() -> &'static str {
|
||||
"VectorData"
|
||||
"Vector"
|
||||
}
|
||||
fn identifier(&self) -> String {
|
||||
format!("Vector Data (points={}, segments={})", self.point_domain.ids().len(), self.segment_domain.ids().len())
|
||||
format!(
|
||||
"Vector ({} point{}, {} segment{})",
|
||||
self.point_domain.ids().len(),
|
||||
if self.point_domain.ids().len() == 1 { "" } else { "s" },
|
||||
self.segment_domain.ids().len(),
|
||||
if self.segment_domain.ids().len() == 1 { "" } else { "s" }
|
||||
)
|
||||
}
|
||||
fn compute_layout(&self, data: &mut LayoutData) -> Vec<LayoutGroup> {
|
||||
let colinear = self.colinear_manipulators.iter().map(|[a, b]| format!("[{a} / {b}]")).collect::<Vec<_>>().join(", ");
|
||||
let colinear = if colinear.is_empty() { "None" } else { &colinear };
|
||||
let style = vec![
|
||||
TextLabel::new(format!(
|
||||
"{}\n\nColinear Handle IDs: {}\n\nUpstream Graphic Group Table: {}",
|
||||
"{}\n\nColinear Handle IDs: {}\nPreserves Reference to Upstream Nested Layers for Editing by Tools: {}",
|
||||
self.style,
|
||||
colinear,
|
||||
if self.upstream_graphic_group.is_some() { "Yes" } else { "No" }
|
||||
if self.upstream_nested_layers.is_some() { "Yes" } else { "No" }
|
||||
))
|
||||
.multiline(true)
|
||||
.widget_holder(),
|
||||
];
|
||||
|
||||
let domain_entries = [VectorDataDomain::Points, VectorDataDomain::Segments, VectorDataDomain::Regions]
|
||||
let domain_entries = [VectorDomain::Points, VectorDomain::Segments, VectorDomain::Regions]
|
||||
.into_iter()
|
||||
.map(|domain| {
|
||||
RadioEntryData::new(format!("{domain:?}"))
|
||||
.label(format!("{domain:?}"))
|
||||
.on_update(move |_| SpreadsheetMessage::ViewVectorDataDomain { domain }.into())
|
||||
.on_update(move |_| SpreadsheetMessage::ViewVectorDomain { domain }.into())
|
||||
})
|
||||
.collect();
|
||||
let domain = vec![RadioInput::new(domain_entries).selected_index(Some(data.vector_data_domain as u32)).widget_holder()];
|
||||
let domain = vec![RadioInput::new(domain_entries).selected_index(Some(data.vector_domain as u32)).widget_holder()];
|
||||
|
||||
let mut table_rows = Vec::new();
|
||||
match data.vector_data_domain {
|
||||
VectorDataDomain::Points => {
|
||||
match data.vector_domain {
|
||||
VectorDomain::Points => {
|
||||
table_rows.push(column_headings(&["", "position"]));
|
||||
table_rows.extend(
|
||||
self.point_domain
|
||||
@@ -216,7 +221,7 @@ impl InstanceLayout for VectorData {
|
||||
.map(|(id, position)| vec![TextLabel::new(format!("{}", id.inner())).widget_holder(), TextLabel::new(format!("{}", position)).widget_holder()]),
|
||||
);
|
||||
}
|
||||
VectorDataDomain::Segments => {
|
||||
VectorDomain::Segments => {
|
||||
table_rows.push(column_headings(&["", "start_index", "end_index", "handles"]));
|
||||
table_rows.extend(self.segment_domain.iter().map(|(id, start, end, handles)| {
|
||||
vec![
|
||||
@@ -227,7 +232,7 @@ impl InstanceLayout for VectorData {
|
||||
]
|
||||
}));
|
||||
}
|
||||
VectorDataDomain::Regions => {
|
||||
VectorDomain::Regions => {
|
||||
table_rows.push(column_headings(&["", "segment_range", "fill"]));
|
||||
table_rows.extend(self.region_domain.iter().map(|(id, segment_range, fill)| {
|
||||
vec![
|
||||
@@ -243,20 +248,20 @@ impl InstanceLayout for VectorData {
|
||||
}
|
||||
}
|
||||
|
||||
impl InstanceLayout for Image<Color> {
|
||||
impl TableRowLayout for Image<Color> {
|
||||
fn type_name() -> &'static str {
|
||||
"Image"
|
||||
}
|
||||
fn identifier(&self) -> String {
|
||||
format!("Image (width={}, height={})", self.width, self.height)
|
||||
format!("Image ({}x{})", self.width, self.height)
|
||||
}
|
||||
fn compute_layout(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
|
||||
let rows = vec![vec![TextLabel::new(format!("Image (width={}, height={})", self.width, self.height)).widget_holder()]];
|
||||
let rows = vec![vec![TextLabel::new(format!("Image ({}x{})", self.width, self.height)).widget_holder()]];
|
||||
vec![LayoutGroup::Table { rows }]
|
||||
}
|
||||
}
|
||||
|
||||
impl InstanceLayout for Artboard {
|
||||
impl TableRowLayout for Artboard {
|
||||
fn type_name() -> &'static str {
|
||||
"Artboard"
|
||||
}
|
||||
@@ -264,22 +269,22 @@ impl InstanceLayout for Artboard {
|
||||
self.label.clone()
|
||||
}
|
||||
fn compute_layout(&self, data: &mut LayoutData) -> Vec<LayoutGroup> {
|
||||
self.graphic_group.compute_layout(data)
|
||||
self.content.compute_layout(data)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: InstanceLayout> InstanceLayout for Instances<T> {
|
||||
impl<T: TableRowLayout> TableRowLayout for Table<T> {
|
||||
fn type_name() -> &'static str {
|
||||
"Instances"
|
||||
"Table"
|
||||
}
|
||||
fn identifier(&self) -> String {
|
||||
format!("Instances<{}> (length={})", T::type_name(), self.len())
|
||||
format!("Table<{}> ({} row{})", T::type_name(), self.len(), if self.len() == 1 { "" } else { "s" })
|
||||
}
|
||||
fn compute_layout(&self, data: &mut LayoutData) -> Vec<LayoutGroup> {
|
||||
if let Some(index) = data.desired_path.get(data.current_depth).copied() {
|
||||
if let Some(instance) = self.get(index) {
|
||||
if let Some(row) = self.get(index) {
|
||||
data.current_depth += 1;
|
||||
let result = instance.instance.layout_with_breadcrumb(data);
|
||||
let result = row.element.layout_with_breadcrumb(data);
|
||||
data.current_depth -= 1;
|
||||
return result;
|
||||
} else {
|
||||
@@ -289,16 +294,16 @@ impl<T: InstanceLayout> InstanceLayout for Instances<T> {
|
||||
}
|
||||
|
||||
let mut rows = self
|
||||
.instance_ref_iter()
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(index, instance)| {
|
||||
let (scale, angle, translation) = instance.transform.to_scale_angle_translation();
|
||||
.map(|(index, row)| {
|
||||
let (scale, angle, translation) = row.transform.to_scale_angle_translation();
|
||||
let rotation = if angle == -0. { 0. } else { angle.to_degrees() };
|
||||
let round = |x: f64| (x * 1e3).round() / 1e3;
|
||||
vec![
|
||||
TextLabel::new(format!("{}", index)).widget_holder(),
|
||||
TextButton::new(instance.instance.identifier())
|
||||
.on_update(move |_| SpreadsheetMessage::PushToInstancePath { index }.into())
|
||||
TextLabel::new(format!("{index}")).widget_holder(),
|
||||
TextButton::new(row.element.identifier())
|
||||
.on_update(move |_| SpreadsheetMessage::PushToElementPath { index }.into())
|
||||
.widget_holder(),
|
||||
TextLabel::new(format!(
|
||||
"Location: ({} px, {} px) — Rotation: {rotation:2}° — Scale: ({}x, {}x)",
|
||||
@@ -308,15 +313,14 @@ impl<T: InstanceLayout> InstanceLayout for Instances<T> {
|
||||
round(scale.y)
|
||||
))
|
||||
.widget_holder(),
|
||||
TextLabel::new(format!("{}", instance.alpha_blending)).widget_holder(),
|
||||
TextLabel::new(instance.source_node_id.map_or_else(|| "-".to_string(), |id| format!("{}", id.0))).widget_holder(),
|
||||
TextLabel::new(format!("{}", row.alpha_blending)).widget_holder(),
|
||||
TextLabel::new(row.source_node_id.map_or_else(|| "-".to_string(), |id| format!("{}", id.0))).widget_holder(),
|
||||
]
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
rows.insert(0, column_headings(&["", "instance", "transform", "alpha_blending", "source_node_id"]));
|
||||
rows.insert(0, column_headings(&["", "element", "transform", "alpha_blending", "source_node_id"]));
|
||||
|
||||
let instances = vec![TextLabel::new("Instances:").widget_holder()];
|
||||
vec![LayoutGroup::Row { widgets: instances }, LayoutGroup::Table { rows }]
|
||||
vec![LayoutGroup::Table { rows }]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ use crate::messages::prelude::{DocumentMessageHandler, InputPreprocessorMessageH
|
||||
use crate::messages::tool::common_functionality::graph_modification_utils;
|
||||
use crate::messages::tool::common_functionality::shape_editor::ShapeState;
|
||||
use crate::messages::tool::common_functionality::shapes::arc_shape::ArcGizmoHandler;
|
||||
use crate::messages::tool::common_functionality::shapes::circle_shape::CircleGizmoHandler;
|
||||
use crate::messages::tool::common_functionality::shapes::polygon_shape::PolygonGizmoHandler;
|
||||
use crate::messages::tool::common_functionality::shapes::shape_utility::ShapeGizmoHandler;
|
||||
use crate::messages::tool::common_functionality::shapes::star_shape::StarGizmoHandler;
|
||||
@@ -26,6 +27,7 @@ pub enum ShapeGizmoHandlers {
|
||||
Star(StarGizmoHandler),
|
||||
Polygon(PolygonGizmoHandler),
|
||||
Arc(ArcGizmoHandler),
|
||||
Circle(CircleGizmoHandler),
|
||||
}
|
||||
|
||||
impl ShapeGizmoHandlers {
|
||||
@@ -36,6 +38,7 @@ impl ShapeGizmoHandlers {
|
||||
Self::Star(_) => "star",
|
||||
Self::Polygon(_) => "polygon",
|
||||
Self::Arc(_) => "arc",
|
||||
Self::Circle(_) => "circle",
|
||||
Self::None => "none",
|
||||
}
|
||||
}
|
||||
@@ -46,6 +49,7 @@ impl ShapeGizmoHandlers {
|
||||
Self::Star(h) => h.handle_state(layer, mouse_position, document, responses),
|
||||
Self::Polygon(h) => h.handle_state(layer, mouse_position, document, responses),
|
||||
Self::Arc(h) => h.handle_state(layer, mouse_position, document, responses),
|
||||
Self::Circle(h) => h.handle_state(layer, mouse_position, document, responses),
|
||||
Self::None => {}
|
||||
}
|
||||
}
|
||||
@@ -56,6 +60,7 @@ impl ShapeGizmoHandlers {
|
||||
Self::Star(h) => h.is_any_gizmo_hovered(),
|
||||
Self::Polygon(h) => h.is_any_gizmo_hovered(),
|
||||
Self::Arc(h) => h.is_any_gizmo_hovered(),
|
||||
Self::Circle(h) => h.is_any_gizmo_hovered(),
|
||||
Self::None => false,
|
||||
}
|
||||
}
|
||||
@@ -66,6 +71,7 @@ impl ShapeGizmoHandlers {
|
||||
Self::Star(h) => h.handle_click(),
|
||||
Self::Polygon(h) => h.handle_click(),
|
||||
Self::Arc(h) => h.handle_click(),
|
||||
Self::Circle(h) => h.handle_click(),
|
||||
Self::None => {}
|
||||
}
|
||||
}
|
||||
@@ -76,6 +82,7 @@ impl ShapeGizmoHandlers {
|
||||
Self::Star(h) => h.handle_update(drag_start, document, input, responses),
|
||||
Self::Polygon(h) => h.handle_update(drag_start, document, input, responses),
|
||||
Self::Arc(h) => h.handle_update(drag_start, document, input, responses),
|
||||
Self::Circle(h) => h.handle_update(drag_start, document, input, responses),
|
||||
Self::None => {}
|
||||
}
|
||||
}
|
||||
@@ -86,6 +93,7 @@ impl ShapeGizmoHandlers {
|
||||
Self::Star(h) => h.cleanup(),
|
||||
Self::Polygon(h) => h.cleanup(),
|
||||
Self::Arc(h) => h.cleanup(),
|
||||
Self::Circle(h) => h.cleanup(),
|
||||
Self::None => {}
|
||||
}
|
||||
}
|
||||
@@ -104,6 +112,7 @@ impl ShapeGizmoHandlers {
|
||||
Self::Star(h) => h.overlays(document, layer, input, shape_editor, mouse_position, overlay_context),
|
||||
Self::Polygon(h) => h.overlays(document, layer, input, shape_editor, mouse_position, overlay_context),
|
||||
Self::Arc(h) => h.overlays(document, layer, input, shape_editor, mouse_position, overlay_context),
|
||||
Self::Circle(h) => h.overlays(document, layer, input, shape_editor, mouse_position, overlay_context),
|
||||
Self::None => {}
|
||||
}
|
||||
}
|
||||
@@ -121,6 +130,7 @@ impl ShapeGizmoHandlers {
|
||||
Self::Star(h) => h.dragging_overlays(document, input, shape_editor, mouse_position, overlay_context),
|
||||
Self::Polygon(h) => h.dragging_overlays(document, input, shape_editor, mouse_position, overlay_context),
|
||||
Self::Arc(h) => h.dragging_overlays(document, input, shape_editor, mouse_position, overlay_context),
|
||||
Self::Circle(h) => h.dragging_overlays(document, input, shape_editor, mouse_position, overlay_context),
|
||||
Self::None => {}
|
||||
}
|
||||
}
|
||||
@@ -130,6 +140,7 @@ impl ShapeGizmoHandlers {
|
||||
Self::Star(h) => h.mouse_cursor_icon(),
|
||||
Self::Polygon(h) => h.mouse_cursor_icon(),
|
||||
Self::Arc(h) => h.mouse_cursor_icon(),
|
||||
Self::Circle(h) => h.mouse_cursor_icon(),
|
||||
Self::None => None,
|
||||
}
|
||||
}
|
||||
@@ -169,6 +180,10 @@ impl GizmoManager {
|
||||
if graph_modification_utils::get_arc_id(layer, &document.network_interface).is_some() {
|
||||
return Some(ShapeGizmoHandlers::Arc(ArcGizmoHandler::new()));
|
||||
}
|
||||
// Circle
|
||||
if graph_modification_utils::get_circle_id(layer, &document.network_interface).is_some() {
|
||||
return Some(ShapeGizmoHandlers::Circle(CircleGizmoHandler::default()));
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
+177
@@ -0,0 +1,177 @@
|
||||
use crate::consts::GIZMO_HIDE_THRESHOLD;
|
||||
use crate::messages::frontend::utility_types::MouseCursorIcon;
|
||||
use crate::messages::message::Message;
|
||||
use crate::messages::portfolio::document::overlays::utility_types::OverlayContext;
|
||||
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
|
||||
use crate::messages::portfolio::document::utility_types::network_interface::InputConnector;
|
||||
use crate::messages::prelude::{DocumentMessageHandler, InputPreprocessorMessageHandler, NodeGraphMessage};
|
||||
use crate::messages::prelude::{FrontendMessage, Responses};
|
||||
use crate::messages::tool::common_functionality::graph_modification_utils::{self, get_arc_id, get_stroke_width};
|
||||
use crate::messages::tool::common_functionality::shapes::shape_utility::{extract_arc_parameters, extract_circle_radius};
|
||||
use glam::{DAffine2, DVec2};
|
||||
use graph_craft::document::NodeInput;
|
||||
use graph_craft::document::value::TaggedValue;
|
||||
use std::collections::VecDeque;
|
||||
use std::f64::consts::FRAC_PI_2;
|
||||
|
||||
#[derive(Clone, Debug, Default, PartialEq)]
|
||||
pub enum RadiusHandleState {
|
||||
#[default]
|
||||
Inactive,
|
||||
Hover,
|
||||
Dragging,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, PartialEq)]
|
||||
pub struct RadiusHandle {
|
||||
pub layer: Option<LayerNodeIdentifier>,
|
||||
initial_radius: f64,
|
||||
handle_state: RadiusHandleState,
|
||||
angle: f64,
|
||||
previous_mouse_position: DVec2,
|
||||
}
|
||||
|
||||
impl RadiusHandle {
|
||||
pub fn cleanup(&mut self) {
|
||||
self.handle_state = RadiusHandleState::Inactive;
|
||||
self.layer = None;
|
||||
}
|
||||
|
||||
pub fn hovered(&self) -> bool {
|
||||
self.handle_state == RadiusHandleState::Hover
|
||||
}
|
||||
|
||||
pub fn is_dragging(&self) -> bool {
|
||||
self.handle_state == RadiusHandleState::Dragging
|
||||
}
|
||||
|
||||
pub fn update_state(&mut self, state: RadiusHandleState) {
|
||||
self.handle_state = state;
|
||||
}
|
||||
|
||||
pub fn check_if_inside_dash_lines(angle: f64, mouse_position: DVec2, viewport: DAffine2, radius: f64, document: &DocumentMessageHandler, layer: LayerNodeIdentifier) -> bool {
|
||||
let center = viewport.transform_point2(DVec2::ZERO);
|
||||
if let Some(stroke_width) = get_stroke_width(layer, &document.network_interface) {
|
||||
let circle_point = calculate_circle_point_position(angle, radius.abs());
|
||||
let direction = circle_point.normalize();
|
||||
let mouse_distance = mouse_position.distance(center);
|
||||
|
||||
let spacing = Self::calculate_extra_spacing(viewport, radius, center, stroke_width, 15.);
|
||||
|
||||
let inner_point = viewport.transform_point2(circle_point - direction * spacing).distance(center);
|
||||
let outer_point = viewport.transform_point2(circle_point + direction * spacing).distance(center);
|
||||
|
||||
mouse_distance >= inner_point && mouse_distance <= outer_point
|
||||
} else {
|
||||
let point_position = viewport.transform_point2(calculate_circle_point_position(angle, radius.abs()));
|
||||
mouse_position.distance(center) <= point_position.distance(center)
|
||||
}
|
||||
}
|
||||
|
||||
fn calculate_extra_spacing(viewport: DAffine2, radius: f64, viewport_center: DVec2, stroke_width: f64, threshold: f64) -> f64 {
|
||||
let start_point = viewport.transform_point2(calculate_circle_point_position(0., radius)).distance(viewport_center);
|
||||
let end_point = viewport.transform_point2(calculate_circle_point_position(FRAC_PI_2, radius)).distance(viewport_center);
|
||||
let min_radius = start_point.min(end_point);
|
||||
let extra_spacing = if min_radius < threshold { 10. * (min_radius / threshold) } else { 10. };
|
||||
|
||||
stroke_width + extra_spacing
|
||||
}
|
||||
|
||||
pub fn handle_actions(&mut self, layer: LayerNodeIdentifier, document: &DocumentMessageHandler, mouse_position: DVec2, responses: &mut VecDeque<Message>) {
|
||||
match &self.handle_state {
|
||||
RadiusHandleState::Inactive => {
|
||||
let Some(radius) = extract_circle_radius(layer, document).or(extract_arc_parameters(Some(layer), document).map(|(r, _, _, _)| r)) else {
|
||||
return;
|
||||
};
|
||||
let viewport = document.metadata().transform_to_viewport(layer);
|
||||
let angle = viewport.inverse().transform_point2(mouse_position).angle_to(DVec2::X);
|
||||
let point_position = viewport.transform_point2(calculate_circle_point_position(angle, radius.abs()));
|
||||
let center = viewport.transform_point2(DVec2::ZERO);
|
||||
|
||||
if point_position.distance(center) < GIZMO_HIDE_THRESHOLD {
|
||||
return;
|
||||
}
|
||||
|
||||
if Self::check_if_inside_dash_lines(angle, mouse_position, viewport, radius.abs(), document, layer) {
|
||||
self.layer = Some(layer);
|
||||
self.initial_radius = radius;
|
||||
self.previous_mouse_position = mouse_position;
|
||||
self.angle = angle;
|
||||
|
||||
self.update_state(RadiusHandleState::Hover);
|
||||
|
||||
responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::EWResize });
|
||||
}
|
||||
}
|
||||
RadiusHandleState::Dragging | RadiusHandleState::Hover => {}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn overlays(&self, document: &DocumentMessageHandler, overlay_context: &mut OverlayContext) {
|
||||
match &self.handle_state {
|
||||
RadiusHandleState::Inactive => {}
|
||||
RadiusHandleState::Dragging | RadiusHandleState::Hover => {
|
||||
let Some(layer) = self.layer else { return };
|
||||
let Some(radius) = extract_circle_radius(layer, document).or(extract_arc_parameters(Some(layer), document).map(|(r, _, _, _)| r)) else {
|
||||
return;
|
||||
};
|
||||
let viewport = document.metadata().transform_to_viewport(layer);
|
||||
let center = viewport.transform_point2(DVec2::ZERO);
|
||||
|
||||
let x_point = viewport.transform_point2(calculate_circle_point_position(0., radius));
|
||||
let y_point = viewport.transform_point2(calculate_circle_point_position(FRAC_PI_2, radius));
|
||||
|
||||
let direction_x = viewport.transform_vector2(DVec2::X);
|
||||
let direction_y = viewport.transform_vector2(-DVec2::Y);
|
||||
|
||||
if let Some(stroke_width) = get_stroke_width(layer, &document.network_interface) {
|
||||
let spacing = Self::calculate_extra_spacing(viewport, radius, center, stroke_width, 15.);
|
||||
let smaller_radius_x = (x_point - direction_x * spacing).distance(center);
|
||||
let smaller_radius_y = (y_point - direction_y * spacing).distance(center);
|
||||
|
||||
let larger_radius_x = (x_point + direction_x * spacing).distance(center);
|
||||
let larger_radius_y = (y_point + direction_y * spacing).distance(center);
|
||||
|
||||
overlay_context.dashed_ellipse(center, smaller_radius_x, smaller_radius_y, None, None, None, None, None, None, Some(4.), Some(4.), Some(0.5));
|
||||
overlay_context.dashed_ellipse(center, larger_radius_x, larger_radius_y, None, None, None, None, None, None, Some(4.), Some(4.), Some(0.5));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
let radius_x = x_point.distance(center);
|
||||
let radius_y = y_point.distance(center);
|
||||
overlay_context.dashed_ellipse(center, radius_x, radius_y, None, None, None, None, None, None, Some(4.), Some(4.), Some(0.5));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_inner_radius(&mut self, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, responses: &mut VecDeque<Message>, drag_start: DVec2) {
|
||||
let Some(layer) = self.layer else { return };
|
||||
let Some(node_id) = graph_modification_utils::get_circle_id(layer, &document.network_interface).or(get_arc_id(layer, &document.network_interface)) else {
|
||||
return;
|
||||
};
|
||||
let Some(current_radius) = extract_circle_radius(layer, document).or(extract_arc_parameters(Some(layer), document).map(|(r, _, _, _)| r)) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let viewport_transform = document.network_interface.document_metadata().transform_to_viewport(layer);
|
||||
let center = viewport_transform.transform_point2(DVec2::ZERO);
|
||||
|
||||
let delta_vector = viewport_transform.inverse().transform_point2(input.mouse.position) - viewport_transform.inverse().transform_point2(self.previous_mouse_position);
|
||||
let radius = drag_start - center;
|
||||
let sign = radius.dot(delta_vector).signum();
|
||||
|
||||
let net_delta = delta_vector.length() * sign * self.initial_radius.signum();
|
||||
self.previous_mouse_position = input.mouse.position;
|
||||
|
||||
responses.add(NodeGraphMessage::SetInput {
|
||||
input_connector: InputConnector::node(node_id, 1),
|
||||
input: NodeInput::value(TaggedValue::F64(current_radius + net_delta), false),
|
||||
});
|
||||
responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||
}
|
||||
}
|
||||
|
||||
fn calculate_circle_point_position(theta: f64, radius: f64) -> DVec2 {
|
||||
DVec2::new(radius * theta.cos(), -radius * theta.sin())
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
pub mod circle_arc_radius_handle;
|
||||
pub mod number_of_points_dial;
|
||||
pub mod point_radius_handle;
|
||||
pub mod sweep_angle_gizmo;
|
||||
|
||||
+2
-2
@@ -189,8 +189,8 @@ impl NumberOfPointsDial {
|
||||
}
|
||||
|
||||
pub fn update_number_of_sides(&self, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, responses: &mut VecDeque<Message>, drag_start: DVec2) {
|
||||
let delta = input.mouse.position - document.metadata().document_to_viewport.transform_point2(drag_start);
|
||||
let sign = (input.mouse.position.x - document.metadata().document_to_viewport.transform_point2(drag_start).x).signum();
|
||||
let delta = input.mouse.position - drag_start;
|
||||
let sign = (input.mouse.position.x - drag_start.x).signum();
|
||||
let net_delta = (delta.length() / 25.).round() * sign;
|
||||
|
||||
let Some(layer) = self.layer else { return };
|
||||
|
||||
+47
-48
@@ -142,14 +142,7 @@ impl PointRadiusHandle {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn overlays(
|
||||
&self,
|
||||
selected_star_layer: Option<LayerNodeIdentifier>,
|
||||
document: &DocumentMessageHandler,
|
||||
input: &InputPreprocessorMessageHandler,
|
||||
mouse_position: DVec2,
|
||||
overlay_context: &mut OverlayContext,
|
||||
) {
|
||||
pub fn overlays(&self, selected_star_layer: Option<LayerNodeIdentifier>, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, overlay_context: &mut OverlayContext) {
|
||||
match &self.handle_state {
|
||||
PointRadiusHandleState::Inactive => {
|
||||
let Some(layer) = selected_star_layer else { return };
|
||||
@@ -161,25 +154,12 @@ impl PointRadiusHandle {
|
||||
for i in 0..(2 * sides) {
|
||||
let point = star_vertex_position(viewport, i as i32, sides, radius1, radius2);
|
||||
let center = viewport.transform_point2(DVec2::ZERO);
|
||||
let viewport_diagonal = input.viewport_bounds.size().length();
|
||||
|
||||
// If the user zooms out such that shape is very small hide the gizmo
|
||||
if point.distance(center) < GIZMO_HIDE_THRESHOLD {
|
||||
return;
|
||||
}
|
||||
|
||||
if point.distance(mouse_position) < 5. {
|
||||
let Some(direction) = (point - center).try_normalize() else { continue };
|
||||
|
||||
overlay_context.manipulator_handle(point, true, None);
|
||||
let angle = ((i as f64) * PI) / (sides as f64);
|
||||
overlay_context.line(center, center + direction * viewport_diagonal, None, None);
|
||||
|
||||
draw_snapping_ticks(&self.snap_radii, direction, viewport, angle, overlay_context);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
overlay_context.manipulator_handle(point, false, None);
|
||||
}
|
||||
}
|
||||
@@ -191,22 +171,12 @@ impl PointRadiusHandle {
|
||||
for i in 0..sides {
|
||||
let point = polygon_vertex_position(viewport, i as i32, sides, radius);
|
||||
let center = viewport.transform_point2(DVec2::ZERO);
|
||||
let viewport_diagonal = input.viewport_bounds.size().length();
|
||||
|
||||
// If the user zooms out such that shape is very small hide the gizmo
|
||||
if point.distance(center) < GIZMO_HIDE_THRESHOLD {
|
||||
return;
|
||||
}
|
||||
|
||||
if point.distance(mouse_position) < 5. {
|
||||
let Some(direction) = (point - center).try_normalize() else { continue };
|
||||
|
||||
overlay_context.manipulator_handle(point, true, None);
|
||||
overlay_context.line(center, center + direction * viewport_diagonal, None, None);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
overlay_context.manipulator_handle(point, false, None);
|
||||
}
|
||||
}
|
||||
@@ -232,12 +202,9 @@ impl PointRadiusHandle {
|
||||
star_outline(Some(layer), document, overlay_context);
|
||||
|
||||
// Make the ticks for snapping
|
||||
|
||||
// If dragging to make radius negative don't show the
|
||||
if (mouse_position - center).dot(direction) < 0. {
|
||||
return;
|
||||
if (radius1.signum() * radius2.signum()).is_sign_positive() {
|
||||
draw_snapping_ticks(&self.snap_radii, direction, viewport, angle, overlay_context);
|
||||
}
|
||||
draw_snapping_ticks(&self.snap_radii, direction, viewport, angle, overlay_context);
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -368,25 +335,36 @@ impl PointRadiusHandle {
|
||||
return snap_radii;
|
||||
};
|
||||
|
||||
let other_index = if radius_index == 3 { 2 } else { 3 };
|
||||
|
||||
let Some(&TaggedValue::F64(other_radius)) = node_inputs[other_index].as_value() else {
|
||||
let (Some(&TaggedValue::F64(radius_1)), Some(&TaggedValue::F64(radius_2))) = (node_inputs[2].as_value(), node_inputs[3].as_value()) else {
|
||||
return snap_radii;
|
||||
};
|
||||
|
||||
let other_radius = if radius_index == 3 { radius_1 } else { radius_2 };
|
||||
|
||||
let Some(&TaggedValue::U32(sides)) = node_inputs[1].as_value() else {
|
||||
return snap_radii;
|
||||
};
|
||||
|
||||
let both_radii_negative = radius_1.is_sign_negative() && radius_2.is_sign_negative();
|
||||
let both_radii_same_sign = (radius_1.signum() * radius_2.signum()).is_sign_positive();
|
||||
|
||||
// When only one of the radii is negative, no need for snapping
|
||||
if !both_radii_same_sign {
|
||||
return snap_radii;
|
||||
}
|
||||
|
||||
let sign = if both_radii_negative { -1. } else { 1. };
|
||||
|
||||
// Inner radius for 90°
|
||||
let b = FRAC_PI_4 * 3. - PI / (sides as f64);
|
||||
let angle = b.sin();
|
||||
let required_radius = (other_radius / angle) * FRAC_1_SQRT_2;
|
||||
let required_radius = (other_radius.abs() * sign / angle) * FRAC_1_SQRT_2;
|
||||
|
||||
snap_radii.push(required_radius);
|
||||
|
||||
// Also push the case when the when it length increases more than the other
|
||||
|
||||
let flipped = other_radius * angle * SQRT_2;
|
||||
let flipped = other_radius.abs() * sign * angle * SQRT_2;
|
||||
|
||||
snap_radii.push(flipped);
|
||||
|
||||
@@ -401,11 +379,11 @@ impl PointRadiusHandle {
|
||||
break;
|
||||
}
|
||||
|
||||
if other_radius * factor > 1e-6 {
|
||||
snap_radii.push(other_radius * factor);
|
||||
if other_radius.abs() * factor > 1e-6 {
|
||||
snap_radii.push(other_radius.abs() * sign * factor);
|
||||
}
|
||||
|
||||
snap_radii.push((other_radius * 1.) / factor);
|
||||
snap_radii.push((other_radius.abs() * sign) / factor);
|
||||
}
|
||||
|
||||
snap_radii
|
||||
@@ -441,21 +419,23 @@ impl PointRadiusHandle {
|
||||
};
|
||||
|
||||
let viewport_transform = document.network_interface.document_metadata().transform_to_viewport(layer);
|
||||
let document_transform = document.network_interface.document_metadata().transform_to_document(layer);
|
||||
let center = viewport_transform.transform_point2(DVec2::ZERO);
|
||||
let radius_index = self.radius_index;
|
||||
|
||||
let original_radius = self.initial_radius;
|
||||
|
||||
let delta = viewport_transform.inverse().transform_point2(input.mouse.position) - document_transform.inverse().transform_point2(drag_start);
|
||||
let radius = document.metadata().document_to_viewport.transform_point2(drag_start) - center;
|
||||
let delta = viewport_transform.inverse().transform_point2(input.mouse.position) - viewport_transform.inverse().transform_point2(drag_start);
|
||||
let radius = drag_start - center;
|
||||
let projection = delta.project_onto(radius);
|
||||
let sign = radius.dot(delta).signum();
|
||||
|
||||
let mut net_delta = projection.length() * sign;
|
||||
let mut net_delta = projection.length() * sign * original_radius.signum();
|
||||
let new_radius = original_radius + net_delta;
|
||||
|
||||
self.update_state(PointRadiusHandleState::Dragging);
|
||||
|
||||
self.check_if_radius_flipped(original_radius, new_radius, document, layer, radius_index);
|
||||
|
||||
if let Some((index, snapped_delta)) = self.check_snapping(new_radius, original_radius) {
|
||||
net_delta = snapped_delta;
|
||||
self.update_state(PointRadiusHandleState::Snapped(index));
|
||||
@@ -467,4 +447,23 @@ impl PointRadiusHandle {
|
||||
});
|
||||
responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||
}
|
||||
|
||||
fn check_if_radius_flipped(&mut self, original_radius: f64, new_radius: f64, document: &DocumentMessageHandler, layer: LayerNodeIdentifier, radius_index: usize) {
|
||||
let Some(node_inputs) = NodeGraphLayer::new(layer, &document.network_interface).find_node_inputs("Star") else {
|
||||
return;
|
||||
};
|
||||
|
||||
let (Some(&TaggedValue::F64(radius_1)), Some(&TaggedValue::F64(radius_2))) = (node_inputs[2].as_value(), node_inputs[3].as_value()) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let other_radius = if radius_index == 3 { radius_1 } else { radius_2 };
|
||||
|
||||
let flipped = (other_radius.is_sign_positive() && original_radius.is_sign_negative() && new_radius.is_sign_positive())
|
||||
|| (other_radius.is_sign_negative() && original_radius.is_sign_positive() && new_radius.is_sign_negative());
|
||||
|
||||
if flipped {
|
||||
self.snap_radii = Self::calculate_snap_radii(document, layer, radius_index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+15
-9
@@ -1,4 +1,4 @@
|
||||
use crate::consts::{ARC_SNAP_THRESHOLD, COLOR_OVERLAY_RED, GIZMO_HIDE_THRESHOLD};
|
||||
use crate::consts::{ARC_SNAP_THRESHOLD, GIZMO_HIDE_THRESHOLD};
|
||||
use crate::messages::message::Message;
|
||||
use crate::messages::portfolio::document::overlays::utility_types::OverlayContext;
|
||||
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
|
||||
@@ -104,17 +104,17 @@ impl SweepAngleGizmo {
|
||||
|
||||
match self.handle_state {
|
||||
SweepAngleGizmoState::Inactive => {
|
||||
// Draw both endpoint handles if an arc is selected
|
||||
let Some((point1, point2)) = arc_end_points(selected_arc_layer, document) else { return };
|
||||
overlay_context.manipulator_handle(point1, false, Some(COLOR_OVERLAY_RED));
|
||||
overlay_context.manipulator_handle(point2, false, Some(COLOR_OVERLAY_RED));
|
||||
overlay_context.manipulator_handle(point1, false, None);
|
||||
overlay_context.manipulator_handle(point2, false, None);
|
||||
}
|
||||
SweepAngleGizmoState::Hover => {
|
||||
// Highlight the currently hovered endpoint only
|
||||
let Some((point1, point2)) = arc_end_points(self.layer, document) else { return };
|
||||
|
||||
let point = if self.endpoint == EndpointType::Start { point1 } else { point2 };
|
||||
overlay_context.manipulator_handle(point, true, Some(COLOR_OVERLAY_RED));
|
||||
let (point, other_point) = if self.endpoint == EndpointType::Start { (point1, point2) } else { (point2, point1) };
|
||||
overlay_context.manipulator_handle(point, true, None);
|
||||
overlay_context.manipulator_handle(other_point, false, None);
|
||||
}
|
||||
SweepAngleGizmoState::Dragging => {
|
||||
// Show snapping guides and angle arc while dragging
|
||||
@@ -123,11 +123,17 @@ impl SweepAngleGizmo {
|
||||
let viewport = document.metadata().transform_to_viewport(layer);
|
||||
|
||||
// Depending on which endpoint is being dragged, draw guides relative to the static point
|
||||
let point = if self.endpoint == EndpointType::End { current_end } else { current_start };
|
||||
let (point, other_point) = if self.endpoint == EndpointType::End {
|
||||
(current_end, current_start)
|
||||
} else {
|
||||
(current_start, current_end)
|
||||
};
|
||||
|
||||
// Draw the dashed line from center to drag start position
|
||||
overlay_context.dashed_line(self.position_before_rotation, viewport.transform_point2(DVec2::ZERO), None, None, Some(5.), Some(5.), Some(0.5));
|
||||
|
||||
overlay_context.manipulator_handle(other_point, false, None);
|
||||
|
||||
// Draw the angle, text and the bold line
|
||||
self.dragging_snapping_overlays(self.position_before_rotation, point, tilt_offset, viewport, overlay_context);
|
||||
}
|
||||
@@ -143,8 +149,8 @@ impl SweepAngleGizmo {
|
||||
self.dragging_snapping_overlays(a, b, tilt_offset, viewport, overlay_context);
|
||||
|
||||
// Draw lines from endpoints to the arc center
|
||||
overlay_context.line(start, center, Some(COLOR_OVERLAY_RED), Some(2.));
|
||||
overlay_context.line(end, center, Some(COLOR_OVERLAY_RED), Some(2.));
|
||||
overlay_context.line(start, center, None, Some(2.));
|
||||
overlay_context.line(end, center, None, Some(2.));
|
||||
|
||||
// Draw the line from drag start to arc center
|
||||
overlay_context.dashed_line(self.position_before_rotation, center, None, None, Some(5.), Some(5.), Some(0.5));
|
||||
|
||||
@@ -11,10 +11,12 @@ use graph_craft::{ProtoNodeIdentifier, concrete};
|
||||
use graphene_std::Color;
|
||||
use graphene_std::NodeInputDecleration;
|
||||
use graphene_std::raster::BlendMode;
|
||||
use graphene_std::raster_types::{CPU, GPU, RasterDataTable};
|
||||
use graphene_std::raster_types::{CPU, GPU, Raster};
|
||||
use graphene_std::table::Table;
|
||||
use graphene_std::text::{Font, TypesettingConfig};
|
||||
use graphene_std::vector::misc::ManipulatorPointId;
|
||||
use graphene_std::vector::style::Gradient;
|
||||
use graphene_std::vector::{ManipulatorPointId, PointId, SegmentId, VectorModificationType};
|
||||
use graphene_std::vector::{PointId, SegmentId, VectorModificationType};
|
||||
use std::collections::VecDeque;
|
||||
|
||||
/// Returns the ID of the first Spline node in the horizontal flow which is not followed by a `Path` node, or `None` if none exists.
|
||||
@@ -33,7 +35,7 @@ pub fn merge_layers(document: &DocumentMessageHandler, first_layer: LayerNodeIde
|
||||
if first_layer == second_layer {
|
||||
return;
|
||||
}
|
||||
// Calculate the downstream transforms in order to bring the other vector data into the same layer space
|
||||
// Calculate the downstream transforms in order to bring the other vector geometry into the same layer space
|
||||
let first_layer_transform = document.metadata().downstream_transform_to_document(first_layer);
|
||||
let second_layer_transform = document.metadata().downstream_transform_to_document(second_layer);
|
||||
|
||||
@@ -161,24 +163,24 @@ pub fn merge_layers(document: &DocumentMessageHandler, first_layer: LayerNodeIde
|
||||
/// Merge the `first_endpoint` with `second_endpoint`.
|
||||
pub fn merge_points(document: &DocumentMessageHandler, layer: LayerNodeIdentifier, first_endpoint: PointId, second_endpont: PointId, responses: &mut VecDeque<Message>) {
|
||||
let transform = document.metadata().transform_to_document(layer);
|
||||
let Some(vector_data) = document.network_interface.compute_modified_vector(layer) else { return };
|
||||
let Some(vector) = document.network_interface.compute_modified_vector(layer) else { return };
|
||||
|
||||
let segment = vector_data.segment_bezier_iter().find(|(_, _, start, end)| *end == second_endpont || *start == second_endpont);
|
||||
let segment = vector.segment_bezier_iter().find(|(_, _, start, end)| *end == second_endpont || *start == second_endpont);
|
||||
let Some((segment, _, mut segment_start_point, mut segment_end_point)) = segment else {
|
||||
log::error!("Could not get the segment for second_endpoint.");
|
||||
return;
|
||||
};
|
||||
|
||||
let mut handles = [None; 2];
|
||||
if let Some(handle_position) = ManipulatorPointId::PrimaryHandle(segment).get_position(&vector_data) {
|
||||
let anchor_position = ManipulatorPointId::Anchor(segment_start_point).get_position(&vector_data).unwrap();
|
||||
if let Some(handle_position) = ManipulatorPointId::PrimaryHandle(segment).get_position(&vector) {
|
||||
let anchor_position = ManipulatorPointId::Anchor(segment_start_point).get_position(&vector).unwrap();
|
||||
let handle_position = transform.transform_point2(handle_position);
|
||||
let anchor_position = transform.transform_point2(anchor_position);
|
||||
let anchor_to_handle = handle_position - anchor_position;
|
||||
handles[0] = Some(anchor_to_handle);
|
||||
}
|
||||
if let Some(handle_position) = ManipulatorPointId::EndHandle(segment).get_position(&vector_data) {
|
||||
let anchor_position = ManipulatorPointId::Anchor(segment_end_point).get_position(&vector_data).unwrap();
|
||||
if let Some(handle_position) = ManipulatorPointId::EndHandle(segment).get_position(&vector) {
|
||||
let anchor_position = ManipulatorPointId::Anchor(segment_end_point).get_position(&vector).unwrap();
|
||||
let handle_position = transform.transform_point2(handle_position);
|
||||
let anchor_position = transform.transform_point2(anchor_position);
|
||||
let anchor_to_handle = handle_position - anchor_position;
|
||||
@@ -211,7 +213,7 @@ pub fn new_vector_layer(subpaths: Vec<Subpath<PointId>>, id: NodeId, parent: Lay
|
||||
}
|
||||
|
||||
/// Create a new bitmap layer.
|
||||
pub fn new_image_layer(image_frame: RasterDataTable<CPU>, id: NodeId, parent: LayerNodeIdentifier, responses: &mut VecDeque<Message>) -> LayerNodeIdentifier {
|
||||
pub fn new_image_layer(image_frame: Table<Raster<CPU>>, id: NodeId, parent: LayerNodeIdentifier, responses: &mut VecDeque<Message>) -> LayerNodeIdentifier {
|
||||
let insert_index = 0;
|
||||
responses.add(GraphOperationMessage::NewBitmapLayer {
|
||||
id,
|
||||
@@ -333,6 +335,10 @@ pub fn get_fill_id(layer: LayerNodeIdentifier, network_interface: &NodeNetworkIn
|
||||
NodeGraphLayer::new(layer, network_interface).upstream_node_id_from_name("Fill")
|
||||
}
|
||||
|
||||
pub fn get_circle_id(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option<NodeId> {
|
||||
NodeGraphLayer::new(layer, network_interface).upstream_node_id_from_name("Circle")
|
||||
}
|
||||
|
||||
pub fn get_ellipse_id(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option<NodeId> {
|
||||
NodeGraphLayer::new(layer, network_interface).upstream_node_id_from_name("Ellipse")
|
||||
}
|
||||
@@ -429,6 +435,16 @@ impl<'a> NodeGraphLayer<'a> {
|
||||
.find(|node_id| self.network_interface.reference(node_id, &[]).is_some_and(|reference| *reference == Some(node_name.to_string())))
|
||||
}
|
||||
|
||||
/// Node id of a visible node if it exists in the layer's primary flow until another layer
|
||||
pub fn upstream_visible_node_id_from_name_in_layer(&self, node_name: &str) -> Option<NodeId> {
|
||||
// `.skip(1)` is used to skip self
|
||||
self.horizontal_layer_flow()
|
||||
.skip(1)
|
||||
.take_while(|node_id| !self.network_interface.is_layer(node_id, &[]))
|
||||
.filter(|node_id| self.network_interface.is_visible(node_id, &[]))
|
||||
.find(|node_id| self.network_interface.reference(node_id, &[]).is_some_and(|reference| *reference == Some(node_name.to_string())))
|
||||
}
|
||||
|
||||
/// Node id of a protonode if it exists in the layer's primary flow
|
||||
pub fn upstream_node_id_from_protonode(&self, protonode_identifier: ProtoNodeIdentifier) -> Option<NodeId> {
|
||||
self.horizontal_layer_flow()
|
||||
@@ -443,10 +459,11 @@ impl<'a> NodeGraphLayer<'a> {
|
||||
|
||||
/// Find all of the inputs of a specific node within the layer's primary flow, up until the next layer is reached.
|
||||
pub fn find_node_inputs(&self, node_name: &str) -> Option<&'a Vec<NodeInput>> {
|
||||
// `.skip(1)` is used to skip self
|
||||
self.horizontal_layer_flow()
|
||||
.skip(1)// Skip self
|
||||
.take_while(|node_id| !self.network_interface.is_layer(node_id,&[]))
|
||||
.find(|node_id| self.network_interface.reference(node_id,&[]).is_some_and(|reference| *reference == Some(node_name.to_string())))
|
||||
.skip(1)
|
||||
.take_while(|node_id| !self.network_interface.is_layer(node_id, &[]))
|
||||
.find(|node_id| self.network_interface.reference(node_id, &[]).is_some_and(|reference| *reference == Some(node_name.to_string())))
|
||||
.and_then(|node_id| self.network_interface.document_network().nodes.get(&node_id).map(|node| &node.inputs))
|
||||
}
|
||||
|
||||
@@ -460,6 +477,6 @@ impl<'a> NodeGraphLayer<'a> {
|
||||
pub fn is_raster_layer(layer: LayerNodeIdentifier, network_interface: &mut NodeNetworkInterface) -> bool {
|
||||
let layer_input_type = network_interface.input_type(&InputConnector::node(layer.to_node(), 1), &[]).0.nested_type().clone();
|
||||
|
||||
layer_input_type == concrete!(RasterDataTable<CPU>) || layer_input_type == concrete!(RasterDataTable<GPU>)
|
||||
layer_input_type == concrete!(Table<Raster<CPU>>) || layer_input_type == concrete!(Table<Raster<GPU>>)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,8 @@ 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, vector::ManipulatorPointId};
|
||||
use graphene_std::transform::ReferencePoint;
|
||||
use graphene_std::vector::misc::ManipulatorPointId;
|
||||
use std::fmt;
|
||||
|
||||
pub fn pin_pivot_widget(active: bool, enabled: bool, source: PivotToolSource) -> WidgetHolder {
|
||||
|
||||
@@ -48,56 +48,11 @@ impl Resize {
|
||||
/// Compute the drag start and end based on the current mouse position. Ignores the state of the layer.
|
||||
/// If you want to only draw whilst a layer exists, use [`Resize::calculate_points`].
|
||||
pub fn calculate_points_ignore_layer(&mut self, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, center: Key, lock_ratio: Key, in_document: bool) -> [DVec2; 2] {
|
||||
let start = self.viewport_drag_start(document);
|
||||
let mouse = input.mouse.position;
|
||||
let document_to_viewport = document.navigation_handler.calculate_offset_transform(input.viewport_bounds.center(), &document.document_ptz);
|
||||
let document_mouse = document_to_viewport.inverse().transform_point2(mouse);
|
||||
let mut points_viewport = [start, mouse];
|
||||
let ignore = if let Some(layer) = self.layer { vec![layer] } else { vec![] };
|
||||
let ratio = input.keyboard.get(lock_ratio as usize);
|
||||
let center = input.keyboard.get(center as usize);
|
||||
let snap_data = SnapData::ignore(document, input, &ignore);
|
||||
let config = SnapTypeConfiguration::default();
|
||||
if ratio {
|
||||
let viewport_size = points_viewport[1] - points_viewport[0];
|
||||
let raw_size = if in_document { document_to_viewport.inverse() } else { DAffine2::IDENTITY }.transform_vector2(viewport_size);
|
||||
let adjusted_size = raw_size.abs().max(raw_size.abs().yx()) * raw_size.signum();
|
||||
let size = if in_document { document_to_viewport.transform_vector2(adjusted_size) } else { adjusted_size };
|
||||
points_viewport[1] = points_viewport[0] + size;
|
||||
|
||||
let end_document = document_to_viewport.inverse().transform_point2(points_viewport[1]);
|
||||
let constraint = SnapConstraint::Line {
|
||||
origin: self.drag_start,
|
||||
direction: end_document - self.drag_start,
|
||||
};
|
||||
if center {
|
||||
let snapped = self.snap_manager.constrained_snap(&snap_data, &SnapCandidatePoint::handle(end_document), constraint, config);
|
||||
let far = SnapCandidatePoint::handle(2. * self.drag_start - end_document);
|
||||
let snapped_far = self.snap_manager.constrained_snap(&snap_data, &far, constraint, config);
|
||||
let best = if snapped_far.other_snap_better(&snapped) { snapped } else { snapped_far };
|
||||
points_viewport[0] = document_to_viewport.transform_point2(best.snapped_point_document);
|
||||
points_viewport[1] = document_to_viewport.transform_point2(self.drag_start * 2. - best.snapped_point_document);
|
||||
self.snap_manager.update_indicator(best);
|
||||
} else {
|
||||
let snapped = self.snap_manager.constrained_snap(&snap_data, &SnapCandidatePoint::handle(end_document), constraint, config);
|
||||
points_viewport[1] = document_to_viewport.transform_point2(snapped.snapped_point_document);
|
||||
self.snap_manager.update_indicator(snapped);
|
||||
}
|
||||
} else if center {
|
||||
let snapped = self.snap_manager.free_snap(&snap_data, &SnapCandidatePoint::handle(document_mouse), config);
|
||||
let opposite = 2. * self.drag_start - document_mouse;
|
||||
let snapped_far = self.snap_manager.free_snap(&snap_data, &SnapCandidatePoint::handle(opposite), config);
|
||||
let best = if snapped_far.other_snap_better(&snapped) { snapped } else { snapped_far };
|
||||
points_viewport[0] = document_to_viewport.transform_point2(best.snapped_point_document);
|
||||
points_viewport[1] = document_to_viewport.transform_point2(self.drag_start * 2. - best.snapped_point_document);
|
||||
self.snap_manager.update_indicator(best);
|
||||
} else {
|
||||
let snapped = self.snap_manager.free_snap(&snap_data, &SnapCandidatePoint::handle(document_mouse), config);
|
||||
points_viewport[1] = document_to_viewport.transform_point2(snapped.snapped_point_document);
|
||||
self.snap_manager.update_indicator(snapped);
|
||||
}
|
||||
|
||||
points_viewport
|
||||
// Use shared snapping logic with optional center and ratio constraints, considering if coordinates are in document space.
|
||||
self.compute_snapped_resize_points(document, input, center, ratio, in_document)
|
||||
}
|
||||
|
||||
pub fn calculate_transform(&mut self, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, center: Key, lock_ratio: Key, skip_rerender: bool) -> Option<Message> {
|
||||
@@ -113,6 +68,81 @@ impl Resize {
|
||||
)
|
||||
}
|
||||
|
||||
pub fn calculate_circle_points(&mut self, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, center: Key) -> [DVec2; 2] {
|
||||
let center = input.keyboard.get(center as usize);
|
||||
|
||||
// Use shared snapping logic with enforced aspect ratio and optional center snapping.
|
||||
self.compute_snapped_resize_points(document, input, center, true, false)
|
||||
}
|
||||
|
||||
/// Calculates two points in viewport space from a drag, applying snapping, optional center mode, and aspect ratio locking.
|
||||
fn compute_snapped_resize_points(&mut self, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, center: bool, lock_ratio: bool, in_document: bool) -> [DVec2; 2] {
|
||||
let start = self.viewport_drag_start(document);
|
||||
let mouse = input.mouse.position;
|
||||
let document_to_viewport = document.navigation_handler.calculate_offset_transform(input.viewport_bounds.center(), &document.document_ptz);
|
||||
let drag_start = self.drag_start;
|
||||
let mut points_viewport = [start, mouse];
|
||||
|
||||
let ignore = if let Some(layer) = self.layer { vec![layer] } else { vec![] };
|
||||
let snap_data = &SnapData::ignore(document, input, &ignore);
|
||||
|
||||
if lock_ratio {
|
||||
let viewport_size = points_viewport[1] - points_viewport[0];
|
||||
let raw_size = if in_document {
|
||||
document_to_viewport.inverse().transform_vector2(viewport_size)
|
||||
} else {
|
||||
viewport_size
|
||||
};
|
||||
|
||||
let adjusted_size = raw_size.abs().max(raw_size.abs().yx()) * raw_size.signum();
|
||||
let size = if in_document { document_to_viewport.transform_vector2(adjusted_size) } else { adjusted_size };
|
||||
|
||||
points_viewport[1] = points_viewport[0] + size;
|
||||
let end_document = document_to_viewport.inverse().transform_point2(points_viewport[1]);
|
||||
let constraint = SnapConstraint::Line {
|
||||
origin: drag_start,
|
||||
direction: end_document - drag_start,
|
||||
};
|
||||
|
||||
if center {
|
||||
let snapped = self
|
||||
.snap_manager
|
||||
.constrained_snap(snap_data, &SnapCandidatePoint::handle(end_document), constraint, SnapTypeConfiguration::default());
|
||||
let far = SnapCandidatePoint::handle(2. * drag_start - end_document);
|
||||
let snapped_far = self.snap_manager.constrained_snap(snap_data, &far, constraint, SnapTypeConfiguration::default());
|
||||
let best = if snapped_far.other_snap_better(&snapped) { snapped } else { snapped_far };
|
||||
|
||||
points_viewport[0] = document_to_viewport.transform_point2(best.snapped_point_document);
|
||||
points_viewport[1] = document_to_viewport.transform_point2(drag_start * 2. - best.snapped_point_document);
|
||||
self.snap_manager.update_indicator(best);
|
||||
} else {
|
||||
let snapped = self
|
||||
.snap_manager
|
||||
.constrained_snap(snap_data, &SnapCandidatePoint::handle(end_document), constraint, SnapTypeConfiguration::default());
|
||||
points_viewport[1] = document_to_viewport.transform_point2(snapped.snapped_point_document);
|
||||
self.snap_manager.update_indicator(snapped);
|
||||
}
|
||||
} else {
|
||||
let document_mouse = document_to_viewport.inverse().transform_point2(mouse);
|
||||
if center {
|
||||
let snapped = self.snap_manager.free_snap(snap_data, &SnapCandidatePoint::handle(document_mouse), SnapTypeConfiguration::default());
|
||||
let opposite = 2. * drag_start - document_mouse;
|
||||
let snapped_far = self.snap_manager.free_snap(snap_data, &SnapCandidatePoint::handle(opposite), SnapTypeConfiguration::default());
|
||||
let best = if snapped_far.other_snap_better(&snapped) { snapped } else { snapped_far };
|
||||
|
||||
points_viewport[0] = document_to_viewport.transform_point2(best.snapped_point_document);
|
||||
points_viewport[1] = document_to_viewport.transform_point2(drag_start * 2. - best.snapped_point_document);
|
||||
self.snap_manager.update_indicator(best);
|
||||
} else {
|
||||
let snapped = self.snap_manager.free_snap(snap_data, &SnapCandidatePoint::handle(document_mouse), SnapTypeConfiguration::default());
|
||||
points_viewport[1] = document_to_viewport.transform_point2(snapped.snapped_point_document);
|
||||
self.snap_manager.update_indicator(snapped);
|
||||
}
|
||||
}
|
||||
|
||||
points_viewport
|
||||
}
|
||||
|
||||
pub fn cleanup(&mut self, responses: &mut VecDeque<Message>) {
|
||||
self.snap_manager.cleanup(responses);
|
||||
self.layer = None;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -4,6 +4,7 @@ use crate::messages::portfolio::document::graph_operation::utility_types::Transf
|
||||
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::{InputConnector, NodeTemplate};
|
||||
use crate::messages::tool::common_functionality::gizmos::shape_gizmos::circle_arc_radius_handle::{RadiusHandle, RadiusHandleState};
|
||||
use crate::messages::tool::common_functionality::gizmos::shape_gizmos::sweep_angle_gizmo::{SweepAngleGizmo, SweepAngleGizmoState};
|
||||
use crate::messages::tool::common_functionality::graph_modification_utils;
|
||||
use crate::messages::tool::common_functionality::shapes::shape_utility::{ShapeGizmoHandler, arc_outline};
|
||||
@@ -17,6 +18,7 @@ use std::collections::VecDeque;
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct ArcGizmoHandler {
|
||||
sweep_angle_gizmo: SweepAngleGizmo,
|
||||
arc_radius_handle: RadiusHandle,
|
||||
}
|
||||
|
||||
impl ArcGizmoHandler {
|
||||
@@ -26,24 +28,40 @@ impl ArcGizmoHandler {
|
||||
}
|
||||
|
||||
impl ShapeGizmoHandler for ArcGizmoHandler {
|
||||
fn handle_state(&mut self, selected_shape_layers: LayerNodeIdentifier, mouse_position: DVec2, document: &DocumentMessageHandler, _responses: &mut VecDeque<Message>) {
|
||||
self.sweep_angle_gizmo.handle_actions(selected_shape_layers, document, mouse_position);
|
||||
fn handle_state(&mut self, selected_shape_layer: LayerNodeIdentifier, mouse_position: DVec2, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>) {
|
||||
self.sweep_angle_gizmo.handle_actions(selected_shape_layer, document, mouse_position);
|
||||
self.arc_radius_handle.handle_actions(selected_shape_layer, document, mouse_position, responses);
|
||||
}
|
||||
|
||||
fn is_any_gizmo_hovered(&self) -> bool {
|
||||
self.sweep_angle_gizmo.hovered()
|
||||
self.sweep_angle_gizmo.hovered() || self.arc_radius_handle.hovered()
|
||||
}
|
||||
|
||||
fn handle_click(&mut self) {
|
||||
// If hovering over both the gizmos give priority to sweep angle gizmo
|
||||
if self.sweep_angle_gizmo.hovered() && self.arc_radius_handle.hovered() {
|
||||
self.sweep_angle_gizmo.update_state(SweepAngleGizmoState::Dragging);
|
||||
self.arc_radius_handle.update_state(RadiusHandleState::Inactive);
|
||||
return;
|
||||
}
|
||||
|
||||
if self.sweep_angle_gizmo.hovered() {
|
||||
self.sweep_angle_gizmo.update_state(SweepAngleGizmoState::Dragging);
|
||||
}
|
||||
|
||||
if self.arc_radius_handle.hovered() {
|
||||
self.arc_radius_handle.update_state(RadiusHandleState::Dragging);
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_update(&mut self, _drag_start: DVec2, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, responses: &mut VecDeque<Message>) {
|
||||
fn handle_update(&mut self, drag_start: DVec2, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, responses: &mut VecDeque<Message>) {
|
||||
if self.sweep_angle_gizmo.is_dragging_or_snapped() {
|
||||
self.sweep_angle_gizmo.update_arc(document, input, responses);
|
||||
}
|
||||
|
||||
if self.arc_radius_handle.is_dragging() {
|
||||
self.arc_radius_handle.update_inner_radius(document, input, responses, drag_start);
|
||||
}
|
||||
}
|
||||
|
||||
fn dragging_overlays(
|
||||
@@ -58,20 +76,39 @@ impl ShapeGizmoHandler for ArcGizmoHandler {
|
||||
self.sweep_angle_gizmo.overlays(None, document, input, mouse_position, overlay_context);
|
||||
arc_outline(self.sweep_angle_gizmo.layer, document, overlay_context);
|
||||
}
|
||||
|
||||
if self.arc_radius_handle.is_dragging() {
|
||||
self.sweep_angle_gizmo.overlays(self.arc_radius_handle.layer, document, input, mouse_position, overlay_context);
|
||||
self.arc_radius_handle.overlays(document, overlay_context);
|
||||
}
|
||||
}
|
||||
|
||||
fn overlays(
|
||||
&self,
|
||||
document: &DocumentMessageHandler,
|
||||
selected_shape_layers: Option<LayerNodeIdentifier>,
|
||||
selected_shape_layer: Option<LayerNodeIdentifier>,
|
||||
input: &InputPreprocessorMessageHandler,
|
||||
_shape_editor: &mut &mut crate::messages::tool::common_functionality::shape_editor::ShapeState,
|
||||
mouse_position: DVec2,
|
||||
overlay_context: &mut crate::messages::portfolio::document::overlays::utility_types::OverlayContext,
|
||||
) {
|
||||
self.sweep_angle_gizmo.overlays(selected_shape_layers, document, input, mouse_position, overlay_context);
|
||||
// If hovering over both the gizmos give priority to sweep angle gizmo
|
||||
if self.sweep_angle_gizmo.hovered() && self.arc_radius_handle.hovered() {
|
||||
self.sweep_angle_gizmo.overlays(selected_shape_layer, document, input, mouse_position, overlay_context);
|
||||
return;
|
||||
}
|
||||
|
||||
arc_outline(selected_shape_layers.or(self.sweep_angle_gizmo.layer), document, overlay_context);
|
||||
if self.arc_radius_handle.hovered() {
|
||||
let layer = self.arc_radius_handle.layer;
|
||||
|
||||
self.arc_radius_handle.overlays(document, overlay_context);
|
||||
self.sweep_angle_gizmo.overlays(layer, document, input, mouse_position, overlay_context);
|
||||
}
|
||||
|
||||
self.sweep_angle_gizmo.overlays(selected_shape_layer, document, input, mouse_position, overlay_context);
|
||||
self.arc_radius_handle.overlays(document, overlay_context);
|
||||
|
||||
arc_outline(selected_shape_layer.or(self.sweep_angle_gizmo.layer), document, overlay_context);
|
||||
}
|
||||
|
||||
fn mouse_cursor_icon(&self) -> Option<MouseCursorIcon> {
|
||||
@@ -79,11 +116,16 @@ impl ShapeGizmoHandler for ArcGizmoHandler {
|
||||
return Some(MouseCursorIcon::Default);
|
||||
}
|
||||
|
||||
if self.arc_radius_handle.hovered() || self.arc_radius_handle.is_dragging() {
|
||||
return Some(MouseCursorIcon::EWResize);
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
fn cleanup(&mut self) {
|
||||
self.sweep_angle_gizmo.cleanup();
|
||||
self.arc_radius_handle.cleanup();
|
||||
}
|
||||
}
|
||||
#[derive(Default)]
|
||||
@@ -122,11 +164,9 @@ impl Arc {
|
||||
// We keep the smaller dimension's scale at 1 and scale the other dimension accordingly
|
||||
if dimensions.x > dimensions.y {
|
||||
scale.x = dimensions.x / dimensions.y;
|
||||
scale.y = 1.;
|
||||
radius = dimensions.y / 2.;
|
||||
} else {
|
||||
scale.y = dimensions.y / dimensions.x;
|
||||
scale.x = 1.;
|
||||
radius = dimensions.x / 2.;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,125 @@
|
||||
use crate::messages::portfolio::document::graph_operation::utility_types::TransformIn;
|
||||
use crate::messages::portfolio::document::node_graph::document_node_definitions::resolve_document_node_type;
|
||||
use crate::messages::portfolio::document::overlays::utility_types::OverlayContext;
|
||||
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
|
||||
use crate::messages::portfolio::document::utility_types::network_interface::{InputConnector, NodeTemplate};
|
||||
use crate::messages::tool::common_functionality::gizmos::shape_gizmos::circle_arc_radius_handle::{RadiusHandle, RadiusHandleState};
|
||||
use crate::messages::tool::common_functionality::graph_modification_utils;
|
||||
use crate::messages::tool::common_functionality::shape_editor::ShapeState;
|
||||
use crate::messages::tool::common_functionality::shapes::shape_utility::{ShapeGizmoHandler, ShapeToolModifierKey};
|
||||
use crate::messages::tool::tool_messages::shape_tool::ShapeToolData;
|
||||
use crate::messages::tool::tool_messages::tool_prelude::*;
|
||||
use glam::DAffine2;
|
||||
use graph_craft::document::NodeInput;
|
||||
use graph_craft::document::value::TaggedValue;
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct CircleGizmoHandler {
|
||||
circle_radius_handle: RadiusHandle,
|
||||
}
|
||||
|
||||
impl ShapeGizmoHandler for CircleGizmoHandler {
|
||||
fn is_any_gizmo_hovered(&self) -> bool {
|
||||
self.circle_radius_handle.hovered()
|
||||
}
|
||||
|
||||
fn handle_state(&mut self, selected_circle_layer: LayerNodeIdentifier, mouse_position: DVec2, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>) {
|
||||
self.circle_radius_handle.handle_actions(selected_circle_layer, document, mouse_position, responses);
|
||||
}
|
||||
|
||||
fn handle_click(&mut self) {
|
||||
if self.circle_radius_handle.hovered() {
|
||||
self.circle_radius_handle.update_state(RadiusHandleState::Dragging);
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_update(&mut self, drag_start: DVec2, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, responses: &mut VecDeque<Message>) {
|
||||
if self.circle_radius_handle.is_dragging() {
|
||||
self.circle_radius_handle.update_inner_radius(document, input, responses, drag_start);
|
||||
}
|
||||
}
|
||||
|
||||
fn overlays(
|
||||
&self,
|
||||
document: &DocumentMessageHandler,
|
||||
_selected_circle_layer: Option<LayerNodeIdentifier>,
|
||||
_input: &InputPreprocessorMessageHandler,
|
||||
_shape_editor: &mut &mut ShapeState,
|
||||
_mouse_position: DVec2,
|
||||
overlay_context: &mut OverlayContext,
|
||||
) {
|
||||
self.circle_radius_handle.overlays(document, overlay_context);
|
||||
}
|
||||
|
||||
fn dragging_overlays(
|
||||
&self,
|
||||
document: &DocumentMessageHandler,
|
||||
_input: &InputPreprocessorMessageHandler,
|
||||
_shape_editor: &mut &mut ShapeState,
|
||||
_mouse_position: DVec2,
|
||||
overlay_context: &mut OverlayContext,
|
||||
) {
|
||||
if self.circle_radius_handle.is_dragging() {
|
||||
self.circle_radius_handle.overlays(document, overlay_context);
|
||||
}
|
||||
}
|
||||
|
||||
fn cleanup(&mut self) {
|
||||
self.circle_radius_handle.cleanup();
|
||||
}
|
||||
|
||||
fn mouse_cursor_icon(&self) -> Option<MouseCursorIcon> {
|
||||
if self.circle_radius_handle.hovered() || self.circle_radius_handle.is_dragging() {
|
||||
return Some(MouseCursorIcon::EWResize);
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Circle;
|
||||
|
||||
impl Circle {
|
||||
pub fn create_node() -> NodeTemplate {
|
||||
let node_type = resolve_document_node_type("Circle").expect("Circle can't be found");
|
||||
node_type.node_template_input_override([None, Some(NodeInput::value(TaggedValue::F64(0.), false))])
|
||||
}
|
||||
|
||||
pub fn update_shape(
|
||||
document: &DocumentMessageHandler,
|
||||
ipp: &InputPreprocessorMessageHandler,
|
||||
layer: LayerNodeIdentifier,
|
||||
shape_tool_data: &mut ShapeToolData,
|
||||
modifier: ShapeToolModifierKey,
|
||||
responses: &mut VecDeque<Message>,
|
||||
) {
|
||||
let center = modifier[0];
|
||||
let [start, end] = shape_tool_data.data.calculate_circle_points(document, ipp, center);
|
||||
let Some(node_id) = graph_modification_utils::get_circle_id(layer, &document.network_interface) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let dimensions = (start - end).abs();
|
||||
let radius: f64;
|
||||
|
||||
// We keep the smaller dimension's scale at 1 and scale the other dimension accordingly
|
||||
if dimensions.x > dimensions.y {
|
||||
radius = dimensions.y / 2.;
|
||||
} else {
|
||||
radius = dimensions.x / 2.;
|
||||
}
|
||||
|
||||
responses.add(NodeGraphMessage::SetInput {
|
||||
input_connector: InputConnector::node(node_id, 1),
|
||||
input: NodeInput::value(TaggedValue::F64(radius), false),
|
||||
});
|
||||
|
||||
responses.add(GraphOperationMessage::TransformSet {
|
||||
layer,
|
||||
transform: DAffine2::from_scale_angle_translation(DVec2::ONE, 0., start.midpoint(end)),
|
||||
transform_in: TransformIn::Viewport,
|
||||
skip_rerender: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
pub mod arc_shape;
|
||||
pub mod circle_shape;
|
||||
pub mod ellipse_shape;
|
||||
pub mod line_shape;
|
||||
pub mod polygon_shape;
|
||||
|
||||
@@ -67,7 +67,7 @@ impl ShapeGizmoHandler for PolygonGizmoHandler {
|
||||
overlay_context: &mut OverlayContext,
|
||||
) {
|
||||
self.number_of_points_dial.overlays(document, selected_polygon_layer, shape_editor, mouse_position, overlay_context);
|
||||
self.point_radius_handle.overlays(selected_polygon_layer, document, input, mouse_position, overlay_context);
|
||||
self.point_radius_handle.overlays(selected_polygon_layer, document, input, overlay_context);
|
||||
|
||||
polygon_outline(selected_polygon_layer, document, overlay_context);
|
||||
}
|
||||
@@ -85,7 +85,7 @@ impl ShapeGizmoHandler for PolygonGizmoHandler {
|
||||
}
|
||||
|
||||
if self.point_radius_handle.is_dragging_or_snapped() {
|
||||
self.point_radius_handle.overlays(None, document, input, mouse_position, overlay_context);
|
||||
self.point_radius_handle.overlays(None, document, input, overlay_context);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@ pub enum ShapeType {
|
||||
#[default]
|
||||
Polygon = 0,
|
||||
Star,
|
||||
Circle,
|
||||
Arc,
|
||||
Rectangle,
|
||||
Ellipse,
|
||||
@@ -37,6 +38,7 @@ impl ShapeType {
|
||||
(match self {
|
||||
Self::Polygon => "Polygon",
|
||||
Self::Star => "Star",
|
||||
Self::Circle => "Circle",
|
||||
Self::Arc => "Arc",
|
||||
Self::Rectangle => "Rectangle",
|
||||
Self::Ellipse => "Ellipse",
|
||||
@@ -203,12 +205,12 @@ pub fn transform_cage_overlays(document: &DocumentMessageHandler, tool_data: &mu
|
||||
|
||||
pub fn anchor_overlays(document: &DocumentMessageHandler, overlay_context: &mut OverlayContext) {
|
||||
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 Some(vector) = document.network_interface.compute_modified_vector(layer) else { continue };
|
||||
let transform = document.metadata().transform_to_viewport(layer);
|
||||
|
||||
overlay_context.outline_vector(&vector_data, transform);
|
||||
overlay_context.outline_vector(&vector, transform);
|
||||
|
||||
for (_, &position) in vector_data.point_domain.ids().iter().zip(vector_data.point_domain.positions()) {
|
||||
for (_, &position) in vector.point_domain.ids().iter().zip(vector.point_domain.positions()) {
|
||||
overlay_context.manipulator_anchor(transform.transform_point2(position), false, None);
|
||||
}
|
||||
}
|
||||
@@ -280,6 +282,19 @@ pub fn arc_end_points_ignore_layer(radius: f64, start_angle: f64, sweep_angle: f
|
||||
}
|
||||
|
||||
/// Calculate the viewport position of a star vertex given its index
|
||||
/// Extract the node input values of Circle.
|
||||
/// Returns an option of (radius).
|
||||
pub fn extract_circle_radius(layer: LayerNodeIdentifier, document: &DocumentMessageHandler) -> Option<f64> {
|
||||
let node_inputs = NodeGraphLayer::new(layer, &document.network_interface).find_node_inputs("Circle")?;
|
||||
|
||||
let Some(&TaggedValue::F64(radius)) = node_inputs.get(1)?.as_value() else {
|
||||
return None;
|
||||
};
|
||||
|
||||
Some(radius)
|
||||
}
|
||||
|
||||
/// Calculate the viewport position of as a star vertex given its index
|
||||
pub fn star_vertex_position(viewport: DAffine2, vertex_index: i32, n: u32, radius1: f64, radius2: f64) -> DVec2 {
|
||||
let angle = ((vertex_index as f64) * PI) / (n as f64);
|
||||
let radius = if vertex_index % 2 == 0 { radius1 } else { radius2 };
|
||||
|
||||
@@ -64,7 +64,7 @@ impl ShapeGizmoHandler for StarGizmoHandler {
|
||||
overlay_context: &mut OverlayContext,
|
||||
) {
|
||||
self.number_of_points_dial.overlays(document, selected_star_layer, shape_editor, mouse_position, overlay_context);
|
||||
self.point_radius_handle.overlays(selected_star_layer, document, input, mouse_position, overlay_context);
|
||||
self.point_radius_handle.overlays(selected_star_layer, document, input, overlay_context);
|
||||
|
||||
star_outline(selected_star_layer, document, overlay_context);
|
||||
}
|
||||
@@ -82,7 +82,7 @@ impl ShapeGizmoHandler for StarGizmoHandler {
|
||||
}
|
||||
|
||||
if self.point_radius_handle.is_dragging_or_snapped() {
|
||||
self.point_radius_handle.overlays(None, document, input, mouse_position, overlay_context);
|
||||
self.point_radius_handle.overlays(None, document, input, overlay_context);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -455,8 +455,8 @@ fn subpath_anchor_snap_points(layer: LayerNodeIdentifier, subpath: &Subpath<Poin
|
||||
}
|
||||
|
||||
// Anchors
|
||||
for (index, group) in subpath.manipulator_groups().iter().enumerate() {
|
||||
if snap_data.ignore_manipulator(layer, group.id) {
|
||||
for (index, manipulators) in subpath.manipulator_groups().iter().enumerate() {
|
||||
if snap_data.ignore_manipulator(layer, manipulators.id) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -464,12 +464,12 @@ fn subpath_anchor_snap_points(layer: LayerNodeIdentifier, subpath: &Subpath<Poin
|
||||
return;
|
||||
}
|
||||
|
||||
let colinear = are_manipulator_handles_colinear(group, to_document, subpath, index);
|
||||
let colinear = are_manipulator_handles_colinear(manipulators, to_document, subpath, index);
|
||||
|
||||
// Colinear handles
|
||||
if colinear && document.snapping_state.target_enabled(SnapTarget::Path(PathSnapTarget::AnchorPointWithColinearHandles)) {
|
||||
points.push(SnapCandidatePoint::new(
|
||||
to_document.transform_point2(group.anchor),
|
||||
to_document.transform_point2(manipulators.anchor),
|
||||
SnapSource::Path(PathSnapSource::AnchorPointWithColinearHandles),
|
||||
SnapTarget::Path(PathSnapTarget::AnchorPointWithColinearHandles),
|
||||
Some(layer),
|
||||
@@ -478,7 +478,7 @@ fn subpath_anchor_snap_points(layer: LayerNodeIdentifier, subpath: &Subpath<Poin
|
||||
// Free handles
|
||||
else if !colinear && document.snapping_state.target_enabled(SnapTarget::Path(PathSnapTarget::AnchorPointWithFreeHandles)) {
|
||||
points.push(SnapCandidatePoint::new(
|
||||
to_document.transform_point2(group.anchor),
|
||||
to_document.transform_point2(manipulators.anchor),
|
||||
SnapSource::Path(PathSnapSource::AnchorPointWithFreeHandles),
|
||||
SnapTarget::Path(PathSnapTarget::AnchorPointWithFreeHandles),
|
||||
Some(layer),
|
||||
@@ -487,10 +487,10 @@ fn subpath_anchor_snap_points(layer: LayerNodeIdentifier, subpath: &Subpath<Poin
|
||||
}
|
||||
}
|
||||
|
||||
pub fn are_manipulator_handles_colinear(group: &bezier_rs::ManipulatorGroup<PointId>, to_document: DAffine2, subpath: &Subpath<PointId>, index: usize) -> bool {
|
||||
let anchor = group.anchor;
|
||||
let handle_in = group.in_handle.map(|handle| anchor - handle).filter(handle_not_under(to_document));
|
||||
let handle_out = group.out_handle.map(|handle| handle - anchor).filter(handle_not_under(to_document));
|
||||
pub fn are_manipulator_handles_colinear(manipulators: &bezier_rs::ManipulatorGroup<PointId>, to_document: DAffine2, subpath: &Subpath<PointId>, index: usize) -> bool {
|
||||
let anchor = manipulators.anchor;
|
||||
let handle_in = manipulators.in_handle.map(|handle| anchor - handle).filter(handle_not_under(to_document));
|
||||
let handle_out = manipulators.out_handle.map(|handle| handle - anchor).filter(handle_not_under(to_document));
|
||||
let anchor_is_endpoint = !subpath.closed() && (index == 0 || index == subpath.len() - 1);
|
||||
|
||||
// Unless this is an endpoint, check if both handles are colinear (within an angular epsilon)
|
||||
|
||||
@@ -1,18 +1,23 @@
|
||||
use super::snapping::{SnapCandidatePoint, SnapData, SnapManager};
|
||||
use super::transformation_cage::{BoundingBoxManager, SizeSnapData};
|
||||
use crate::consts::ROTATE_INCREMENT;
|
||||
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
|
||||
use crate::messages::portfolio::document::utility_types::document_metadata::{DocumentMetadata, LayerNodeIdentifier};
|
||||
use crate::messages::portfolio::document::utility_types::network_interface::NodeNetworkInterface;
|
||||
use crate::messages::portfolio::document::utility_types::transformation::Selected;
|
||||
use crate::messages::prelude::*;
|
||||
use crate::messages::tool::common_functionality::graph_modification_utils::get_text;
|
||||
use crate::messages::tool::common_functionality::graph_modification_utils::{NodeGraphLayer, get_text};
|
||||
use crate::messages::tool::common_functionality::transformation_cage::SelectedEdges;
|
||||
use crate::messages::tool::tool_messages::path_tool::PathOverlayMode;
|
||||
use crate::messages::tool::utility_types::ToolType;
|
||||
use bezier_rs::{Bezier, BezierHandles};
|
||||
use glam::{DAffine2, DVec2};
|
||||
use graph_craft::concrete;
|
||||
use graph_craft::document::value::TaggedValue;
|
||||
use graphene_std::renderer::Quad;
|
||||
use graphene_std::table::Table;
|
||||
use graphene_std::text::{FontCache, load_font};
|
||||
use graphene_std::vector::{HandleExt, HandleId, ManipulatorPointId, PointId, SegmentId, VectorData, VectorModificationType};
|
||||
use graphene_std::vector::misc::{HandleId, ManipulatorPointId};
|
||||
use graphene_std::vector::{HandleExt, PointId, SegmentId, Vector, VectorModification, VectorModificationType};
|
||||
use kurbo::{CubicBez, Line, ParamCurveExtrema, PathSeg, Point, QuadBez};
|
||||
|
||||
/// Determines if a path should be extended. Goal in viewport space. Returns the path and if it is extending from the start, if applicable.
|
||||
@@ -43,14 +48,12 @@ where
|
||||
let mut best_distance_squared = max_distance * max_distance;
|
||||
for layer in layers {
|
||||
let viewspace = document.metadata().transform_to_viewport(layer);
|
||||
let Some(vector_data) = document.network_interface.compute_modified_vector(layer) else {
|
||||
continue;
|
||||
};
|
||||
for id in vector_data.extendable_points(preferences.vector_meshes) {
|
||||
let Some(vector) = document.network_interface.compute_modified_vector(layer) else { continue };
|
||||
for id in vector.extendable_points(preferences.vector_meshes) {
|
||||
if exclude(id) {
|
||||
continue;
|
||||
}
|
||||
let Some(point) = vector_data.point_domain.position_from_id(id) else { continue };
|
||||
let Some(point) = vector.point_domain.position_from_id(id) else { continue };
|
||||
|
||||
let distance_squared = viewspace.transform_point2(point).distance_squared(goal);
|
||||
|
||||
@@ -73,7 +76,7 @@ pub fn text_bounding_box(layer: LayerNodeIdentifier, document: &DocumentMessageH
|
||||
let font_data = font_cache.get(font).map(|data| load_font(data));
|
||||
let far = graphene_std::text::bounding_box(text, font_data, typesetting, false);
|
||||
|
||||
// TODO: Once the instances refactor is complete and per_glyph_instances can be removed (since it'll be the default),
|
||||
// TODO: Once the instance tables refactor is complete and per_glyph_instances can be removed (since it'll be the default),
|
||||
// TODO: remove this because the top of the dashed bounding overlay should no longer be based on the first line's baseline.
|
||||
let vertical_offset = if per_glyph_instances {
|
||||
DVec2::NEG_Y * typesetting.font_size * (1. + (typesetting.line_height_ratio - 1.) / 2.)
|
||||
@@ -84,16 +87,16 @@ pub fn text_bounding_box(layer: LayerNodeIdentifier, document: &DocumentMessageH
|
||||
Quad::from_box([DVec2::ZERO + vertical_offset, far + vertical_offset])
|
||||
}
|
||||
|
||||
pub fn calculate_segment_angle(anchor: PointId, segment: SegmentId, vector_data: &VectorData, prefer_handle_direction: bool) -> Option<f64> {
|
||||
let is_start = |point: PointId, segment: SegmentId| vector_data.segment_start_from_id(segment) == Some(point);
|
||||
let anchor_position = vector_data.point_domain.position_from_id(anchor)?;
|
||||
let end_handle = ManipulatorPointId::EndHandle(segment).get_position(vector_data);
|
||||
let start_handle = ManipulatorPointId::PrimaryHandle(segment).get_position(vector_data);
|
||||
pub fn calculate_segment_angle(anchor: PointId, segment: SegmentId, vector: &Vector, prefer_handle_direction: bool) -> Option<f64> {
|
||||
let is_start = |point: PointId, segment: SegmentId| vector.segment_start_from_id(segment) == Some(point);
|
||||
let anchor_position = vector.point_domain.position_from_id(anchor)?;
|
||||
let end_handle = ManipulatorPointId::EndHandle(segment).get_position(vector);
|
||||
let start_handle = ManipulatorPointId::PrimaryHandle(segment).get_position(vector);
|
||||
|
||||
let start_point = if is_start(anchor, segment) {
|
||||
vector_data.segment_end_from_id(segment).and_then(|id| vector_data.point_domain.position_from_id(id))
|
||||
vector.segment_end_from_id(segment).and_then(|id| vector.point_domain.position_from_id(id))
|
||||
} else {
|
||||
vector_data.segment_start_from_id(segment).and_then(|id| vector_data.point_domain.position_from_id(id))
|
||||
vector.segment_start_from_id(segment).and_then(|id| vector.point_domain.position_from_id(id))
|
||||
};
|
||||
|
||||
let required_handle = if is_start(anchor, segment) {
|
||||
@@ -111,9 +114,9 @@ pub fn calculate_segment_angle(anchor: PointId, segment: SegmentId, vector_data:
|
||||
required_handle.map(|handle| -(handle - anchor_position).angle_to(DVec2::X))
|
||||
}
|
||||
|
||||
pub fn adjust_handle_colinearity(handle: HandleId, anchor_position: DVec2, target_control_point: DVec2, vector_data: &VectorData, layer: LayerNodeIdentifier, responses: &mut VecDeque<Message>) {
|
||||
let Some(other_handle) = vector_data.other_colinear_handle(handle) else { return };
|
||||
let Some(handle_position) = other_handle.to_manipulator_point().get_position(vector_data) else {
|
||||
pub fn adjust_handle_colinearity(handle: HandleId, anchor_position: DVec2, target_control_point: DVec2, vector: &Vector, layer: LayerNodeIdentifier, responses: &mut VecDeque<Message>) {
|
||||
let Some(other_handle) = vector.other_colinear_handle(handle) else { return };
|
||||
let Some(handle_position) = other_handle.to_manipulator_point().get_position(vector) else {
|
||||
return;
|
||||
};
|
||||
let Some(direction) = (anchor_position - target_control_point).try_normalize() else { return };
|
||||
@@ -128,12 +131,12 @@ pub fn restore_previous_handle_position(
|
||||
handle: HandleId,
|
||||
original_c: DVec2,
|
||||
anchor_position: DVec2,
|
||||
vector_data: &VectorData,
|
||||
vector: &Vector,
|
||||
layer: LayerNodeIdentifier,
|
||||
responses: &mut VecDeque<Message>,
|
||||
) -> Option<HandleId> {
|
||||
let other_handle = vector_data.other_colinear_handle(handle)?;
|
||||
let handle_position = other_handle.to_manipulator_point().get_position(vector_data)?;
|
||||
let other_handle = vector.other_colinear_handle(handle)?;
|
||||
let handle_position = other_handle.to_manipulator_point().get_position(vector)?;
|
||||
let direction = (anchor_position - original_c).try_normalize()?;
|
||||
|
||||
let old_relative_position = (handle_position - anchor_position).length() * direction;
|
||||
@@ -147,16 +150,8 @@ pub fn restore_previous_handle_position(
|
||||
Some(other_handle)
|
||||
}
|
||||
|
||||
pub fn restore_g1_continuity(
|
||||
handle: HandleId,
|
||||
other_handle: HandleId,
|
||||
control_point: DVec2,
|
||||
anchor_position: DVec2,
|
||||
vector_data: &VectorData,
|
||||
layer: LayerNodeIdentifier,
|
||||
responses: &mut VecDeque<Message>,
|
||||
) {
|
||||
let Some(handle_position) = other_handle.to_manipulator_point().get_position(vector_data) else {
|
||||
pub fn restore_g1_continuity(handle: HandleId, other_handle: HandleId, control_point: DVec2, anchor_position: DVec2, vector: &Vector, layer: LayerNodeIdentifier, responses: &mut VecDeque<Message>) {
|
||||
let Some(handle_position) = other_handle.to_manipulator_point().get_position(vector) else {
|
||||
return;
|
||||
};
|
||||
let Some(direction) = (anchor_position - control_point).try_normalize() else { return };
|
||||
@@ -173,9 +168,9 @@ pub fn restore_g1_continuity(
|
||||
/// Check whether a point is visible in the current overlay mode.
|
||||
pub fn is_visible_point(
|
||||
manipulator_point_id: ManipulatorPointId,
|
||||
vector_data: &VectorData,
|
||||
vector: &Vector,
|
||||
path_overlay_mode: PathOverlayMode,
|
||||
frontier_handles_info: Option<HashMap<SegmentId, Vec<PointId>>>,
|
||||
frontier_handles_info: &Option<HashMap<SegmentId, Vec<PointId>>>,
|
||||
selected_segments: Vec<SegmentId>,
|
||||
selected_points: &HashSet<ManipulatorPointId>,
|
||||
) -> bool {
|
||||
@@ -190,18 +185,18 @@ pub fn is_visible_point(
|
||||
}
|
||||
|
||||
// Either the segment is a part of selected segments or the opposite handle is a part of existing selection
|
||||
let Some(handle_pair) = manipulator_point_id.get_handle_pair(vector_data) else { return false };
|
||||
let Some(handle_pair) = manipulator_point_id.get_handle_pair(vector) else { return false };
|
||||
let other_handle = handle_pair[1].to_manipulator_point();
|
||||
|
||||
// Return whether the list of selected points contain the other handle
|
||||
selected_points.contains(&other_handle)
|
||||
}
|
||||
(PathOverlayMode::FrontierHandles, false) => {
|
||||
let Some(anchor) = manipulator_point_id.get_anchor(vector_data) else {
|
||||
let Some(anchor) = manipulator_point_id.get_anchor(vector) else {
|
||||
warn!("No anchor for selected handle");
|
||||
return false;
|
||||
};
|
||||
let Some(frontier_handles) = &frontier_handles_info else {
|
||||
let Some(frontier_handles) = frontier_handles_info else {
|
||||
warn!("No frontier handles info provided");
|
||||
return false;
|
||||
};
|
||||
@@ -586,3 +581,36 @@ pub fn find_two_param_best_approximate(p1: DVec2, p3: DVec2, d1: DVec2, d2: DVec
|
||||
|
||||
(d1 * len1, d2 * len2)
|
||||
}
|
||||
|
||||
pub fn make_path_editable_is_allowed(network_interface: &NodeNetworkInterface, metadata: &DocumentMetadata) -> Option<LayerNodeIdentifier> {
|
||||
// Must have exactly one layer selected
|
||||
let selected_nodes = network_interface.selected_nodes();
|
||||
let mut selected_layers = selected_nodes.selected_layers(metadata);
|
||||
let first_layer = selected_layers.next()?;
|
||||
if selected_layers.next().is_some() {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Must be a layer of type Table<Vector>
|
||||
let compatible_type = NodeGraphLayer::new(first_layer, network_interface)
|
||||
.horizontal_layer_flow()
|
||||
.nth(1)
|
||||
.map(|node_id| {
|
||||
let (output_type, _) = network_interface.output_type(&node_id, 0, &[]);
|
||||
output_type.nested_type() == concrete!(Table<Vector>).nested_type()
|
||||
})
|
||||
.unwrap_or_default();
|
||||
if !compatible_type {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Must not already have an existing Path node, in the right-most part of the layer chain, which has an empty set of modifications
|
||||
// (otherwise users could repeatedly keep running this command and stacking up empty Path nodes)
|
||||
if let Some(TaggedValue::VectorModification(modifications)) = NodeGraphLayer::new(first_layer, network_interface).find_input("Path", 1) {
|
||||
if modifications.as_ref() == &VectorModification::default() {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
|
||||
Some(first_layer)
|
||||
}
|
||||
|
||||
@@ -11,7 +11,9 @@ use crate::messages::tool::common_functionality::snapping::SnapData;
|
||||
use crate::messages::tool::common_functionality::snapping::SnapManager;
|
||||
use crate::messages::tool::common_functionality::transformation_cage::*;
|
||||
use graph_craft::document::NodeId;
|
||||
use graphene_std::Artboard;
|
||||
use graphene_std::renderer::Quad;
|
||||
use graphene_std::table::Table;
|
||||
|
||||
#[derive(Default, ExtractField)]
|
||||
pub struct ArtboardTool {
|
||||
@@ -336,8 +338,8 @@ impl Fsm for ArtboardToolFsmState {
|
||||
|
||||
responses.add(GraphOperationMessage::NewArtboard {
|
||||
id,
|
||||
artboard: graphene_std::Artboard {
|
||||
graphic_group: graphene_std::GraphicGroupTable::default(),
|
||||
artboard: Artboard {
|
||||
content: Table::new(),
|
||||
label: String::from("Artboard"),
|
||||
location: start.min(end).round().as_ivec2(),
|
||||
dimensions: (start.round() - end.round()).abs().as_ivec2(),
|
||||
@@ -562,13 +564,17 @@ impl Fsm for ArtboardToolFsmState {
|
||||
#[cfg(test)]
|
||||
mod test_artboard {
|
||||
pub use crate::test_utils::test_prelude::*;
|
||||
use graphene_std::table::Table;
|
||||
|
||||
async fn get_artboards(editor: &mut EditorTestUtils) -> Vec<graphene_std::Artboard> {
|
||||
async fn get_artboards(editor: &mut EditorTestUtils) -> Table<graphene_std::Artboard> {
|
||||
let instrumented = match editor.eval_graph().await {
|
||||
Ok(instrumented) => instrumented,
|
||||
Err(e) => panic!("Failed to evaluate graph: {}", e),
|
||||
};
|
||||
instrumented.grab_all_input::<graphene_std::graphic_element::append_artboard::ArtboardInput>(&editor.runtime).collect()
|
||||
instrumented
|
||||
.grab_all_input::<graphene_std::graphic::extend::NewInput<graphene_std::Artboard>>(&editor.runtime)
|
||||
.flatten()
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -580,8 +586,8 @@ mod test_artboard {
|
||||
let artboards = get_artboards(&mut editor).await;
|
||||
|
||||
assert_eq!(artboards.len(), 1);
|
||||
assert_eq!(artboards[0].location, IVec2::new(10, 0));
|
||||
assert_eq!(artboards[0].dimensions, IVec2::new(10, 11));
|
||||
assert_eq!(artboards.get(0).unwrap().element.location, IVec2::new(10, 0));
|
||||
assert_eq!(artboards.get(0).unwrap().element.dimensions, IVec2::new(10, 11));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -592,8 +598,8 @@ mod test_artboard {
|
||||
|
||||
let artboards = get_artboards(&mut editor).await;
|
||||
assert_eq!(artboards.len(), 1);
|
||||
assert_eq!(artboards[0].location, IVec2::new(-10, 10));
|
||||
assert_eq!(artboards[0].dimensions, IVec2::new(20, 20));
|
||||
assert_eq!(artboards.get(0).unwrap().element.location, IVec2::new(-10, 10));
|
||||
assert_eq!(artboards.get(0).unwrap().element.dimensions, IVec2::new(20, 20));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -611,9 +617,9 @@ mod test_artboard {
|
||||
|
||||
let artboards = get_artboards(&mut editor).await;
|
||||
assert_eq!(artboards.len(), 1);
|
||||
assert_eq!(artboards[0].location, IVec2::new(0, 0));
|
||||
assert_eq!(artboards.get(0).unwrap().element.location, IVec2::new(0, 0));
|
||||
let desired_size = DVec2::splat(f64::consts::FRAC_1_SQRT_2 * 10.);
|
||||
assert_eq!(artboards[0].dimensions, desired_size.round().as_ivec2());
|
||||
assert_eq!(artboards.get(0).unwrap().element.dimensions, desired_size.round().as_ivec2());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -632,9 +638,9 @@ mod test_artboard {
|
||||
|
||||
let artboards = get_artboards(&mut editor).await;
|
||||
assert_eq!(artboards.len(), 1);
|
||||
assert_eq!(artboards[0].location, DVec2::splat(f64::consts::FRAC_1_SQRT_2 * -10.).as_ivec2());
|
||||
assert_eq!(artboards.get(0).unwrap().element.location, DVec2::splat(f64::consts::FRAC_1_SQRT_2 * -10.).as_ivec2());
|
||||
let desired_size = DVec2::splat(f64::consts::FRAC_1_SQRT_2 * 20.);
|
||||
assert_eq!(artboards[0].dimensions, desired_size.round().as_ivec2());
|
||||
assert_eq!(artboards.get(0).unwrap().element.dimensions, desired_size.round().as_ivec2());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
|
||||
@@ -163,8 +163,8 @@ impl LayoutHolder for BrushTool {
|
||||
|
||||
let blend_mode_entries: Vec<Vec<_>> = BlendMode::list()
|
||||
.iter()
|
||||
.map(|group| {
|
||||
group
|
||||
.map(|section| {
|
||||
section
|
||||
.iter()
|
||||
.map(|blend_mode| {
|
||||
MenuListEntry::new(format!("{blend_mode:?}"))
|
||||
|
||||
@@ -251,12 +251,8 @@ impl Fsm for FreehandToolFsmState {
|
||||
let nodes = vec![(NodeId(0), node)];
|
||||
|
||||
let layer = graph_modification_utils::new_custom(NodeId::new(), nodes, parent, responses);
|
||||
let defered_responses = &mut VecDeque::new();
|
||||
tool_options.fill.apply_fill(layer, defered_responses);
|
||||
tool_options.stroke.apply_stroke(tool_data.weight, layer, defered_responses);
|
||||
responses.add(DeferMessage::AfterGraphRun {
|
||||
messages: defered_responses.drain(..).collect(),
|
||||
});
|
||||
tool_options.fill.apply_fill(layer, responses);
|
||||
tool_options.stroke.apply_stroke(tool_data.weight, layer, responses);
|
||||
tool_data.layer = Some(layer);
|
||||
|
||||
FreehandToolFsmState::Drawing
|
||||
@@ -356,45 +352,38 @@ fn extend_path_with_next_segment(tool_data: &mut FreehandToolData, position: DVe
|
||||
mod test_freehand {
|
||||
use crate::messages::input_mapper::utility_types::input_mouse::{EditorMouseState, MouseKeys, ScrollDelta};
|
||||
use crate::messages::portfolio::document::graph_operation::utility_types::TransformIn;
|
||||
use crate::messages::tool::common_functionality::graph_modification_utils::get_stroke_width;
|
||||
use crate::messages::tool::common_functionality::graph_modification_utils::{NodeGraphLayer, get_stroke_width};
|
||||
use crate::messages::tool::tool_messages::freehand_tool::FreehandOptionsUpdate;
|
||||
use crate::test_utils::test_prelude::*;
|
||||
use glam::{DAffine2, DVec2};
|
||||
use graphene_std::vector::VectorData;
|
||||
use graphene_std::vector::Vector;
|
||||
|
||||
async fn get_vector_data(editor: &mut EditorTestUtils) -> Vec<(VectorData, DAffine2)> {
|
||||
async fn get_vector_and_transform_list(editor: &mut EditorTestUtils) -> Vec<(Vector, DAffine2)> {
|
||||
let document = editor.active_document();
|
||||
let layers = document.metadata().all_layers();
|
||||
|
||||
layers
|
||||
.filter_map(|layer| {
|
||||
let vector_data = document.network_interface.compute_modified_vector(layer)?;
|
||||
let graph_layer = NodeGraphLayer::new(layer, &document.network_interface);
|
||||
// Only get layers with path nodes
|
||||
let _ = graph_layer.upstream_visible_node_id_from_name_in_layer("Path")?;
|
||||
|
||||
let vector = document.network_interface.compute_modified_vector(layer)?;
|
||||
let transform = document.metadata().transform_to_viewport(layer);
|
||||
Some((vector_data, transform))
|
||||
Some((vector, transform))
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn verify_path_points(vector_data_list: &[(VectorData, DAffine2)], expected_captured_points: &[DVec2], tolerance: f64) -> Result<(), String> {
|
||||
if vector_data_list.len() == 0 {
|
||||
return Err("No vector data found after drawing".to_string());
|
||||
}
|
||||
fn verify_path_points(vector_and_transform_list: &[(Vector, DAffine2)], expected_captured_points: &[DVec2], tolerance: f64) -> Result<(), String> {
|
||||
assert_eq!(vector_and_transform_list.len(), 1, "There should be one row of Vector geometry");
|
||||
|
||||
let path_data = vector_data_list.iter().find(|(data, _)| data.point_domain.ids().len() > 0).ok_or("Could not find path data")?;
|
||||
let (vector, transform) = vector_and_transform_list.iter().find(|(data, _)| data.point_domain.ids().len() > 0).ok_or("Could not find path data")?;
|
||||
|
||||
let (vector_data, transform) = path_data;
|
||||
let point_count = vector_data.point_domain.ids().len();
|
||||
let segment_count = vector_data.segment_domain.ids().len();
|
||||
let point_count = vector.point_domain.ids().len();
|
||||
let segment_count = vector.segment_domain.ids().len();
|
||||
|
||||
let actual_positions: Vec<DVec2> = vector_data
|
||||
.point_domain
|
||||
.ids()
|
||||
.iter()
|
||||
.filter_map(|&point_id| {
|
||||
let position = vector_data.point_domain.position_from_id(point_id)?;
|
||||
Some(transform.transform_point2(position))
|
||||
})
|
||||
.collect();
|
||||
let actual_positions: Vec<DVec2> = vector.point_domain.positions().iter().map(|&position| transform.transform_point2(position)).collect();
|
||||
|
||||
if segment_count != point_count - 1 {
|
||||
return Err(format!("Expected segments to be one less than points, got {} segments for {} points", segment_count, point_count));
|
||||
@@ -441,8 +430,8 @@ mod test_freehand {
|
||||
let expected_captured_points = &mouse_points[1..];
|
||||
editor.drag_path(&mouse_points, ModifierKeys::empty()).await;
|
||||
|
||||
let vector_data_list = get_vector_data(&mut editor).await;
|
||||
verify_path_points(&vector_data_list, expected_captured_points, 1.).expect("Path points verification failed");
|
||||
let vector_and_transform_list = get_vector_and_transform_list(&mut editor).await;
|
||||
verify_path_points(&vector_and_transform_list, expected_captured_points, 1.).expect("Path points verification failed");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -474,12 +463,12 @@ mod test_freehand {
|
||||
)
|
||||
.await;
|
||||
|
||||
let initial_vector_data = get_vector_data(&mut editor).await;
|
||||
assert!(!initial_vector_data.is_empty(), "No vector data found after initial drawing");
|
||||
let initial_vector_and_transform_list = get_vector_and_transform_list(&mut editor).await;
|
||||
assert!(!initial_vector_and_transform_list.is_empty(), "No Vector geometry found after initial drawing");
|
||||
|
||||
let (initial_data, transform) = &initial_vector_data[0];
|
||||
let initial_point_count = initial_data.point_domain.ids().len();
|
||||
let initial_segment_count = initial_data.segment_domain.ids().len();
|
||||
let (initial_vector, initial_transform) = &initial_vector_and_transform_list[0];
|
||||
let initial_point_count = initial_vector.point_domain.ids().len();
|
||||
let initial_segment_count = initial_vector.segment_domain.ids().len();
|
||||
|
||||
assert!(initial_point_count >= 2, "Expected at least 2 points in initial path, found {}", initial_point_count);
|
||||
assert_eq!(
|
||||
@@ -490,15 +479,15 @@ mod test_freehand {
|
||||
initial_segment_count
|
||||
);
|
||||
|
||||
let extendable_points = initial_data.extendable_points(false).collect::<Vec<_>>();
|
||||
let extendable_points = initial_vector.extendable_points(false).collect::<Vec<_>>();
|
||||
assert!(!extendable_points.is_empty(), "No extendable points found in the path");
|
||||
|
||||
let endpoint_id = extendable_points[0];
|
||||
let endpoint_pos_option = initial_data.point_domain.position_from_id(endpoint_id);
|
||||
let endpoint_pos_option = initial_vector.point_domain.position_from_id(endpoint_id);
|
||||
assert!(endpoint_pos_option.is_some(), "Could not find position for endpoint");
|
||||
|
||||
let endpoint_pos = endpoint_pos_option.unwrap();
|
||||
let endpoint_viewport_pos = transform.transform_point2(endpoint_pos);
|
||||
let endpoint_viewport_pos = initial_transform.transform_point2(endpoint_pos);
|
||||
|
||||
assert!(endpoint_viewport_pos.is_finite(), "Endpoint position is not finite");
|
||||
|
||||
@@ -533,12 +522,12 @@ mod test_freehand {
|
||||
)
|
||||
.await;
|
||||
|
||||
let extended_vector_data = get_vector_data(&mut editor).await;
|
||||
assert!(!extended_vector_data.is_empty(), "No vector data found after extension");
|
||||
let extended_vector_and_transform = get_vector_and_transform_list(&mut editor).await;
|
||||
assert!(!extended_vector_and_transform.is_empty(), "No Vector geometry found after extension");
|
||||
|
||||
let (extended_data, _) = &extended_vector_data[0];
|
||||
let extended_point_count = extended_data.point_domain.ids().len();
|
||||
let extended_segment_count = extended_data.segment_domain.ids().len();
|
||||
let (extended_vector, _) = &extended_vector_and_transform[0];
|
||||
let extended_point_count = extended_vector.point_domain.ids().len();
|
||||
let extended_segment_count = extended_vector.segment_domain.ids().len();
|
||||
|
||||
assert!(
|
||||
extended_point_count > initial_point_count,
|
||||
@@ -591,12 +580,12 @@ mod test_freehand {
|
||||
)
|
||||
.await;
|
||||
|
||||
let initial_vector_data = get_vector_data(&mut editor).await;
|
||||
assert!(!initial_vector_data.is_empty(), "No vector data found after initial drawing");
|
||||
let initial_vector_and_transform = get_vector_and_transform_list(&mut editor).await;
|
||||
assert!(!initial_vector_and_transform.is_empty(), "No vector geometry found after initial drawing");
|
||||
|
||||
let (initial_data, _) = &initial_vector_data[0];
|
||||
let initial_point_count = initial_data.point_domain.ids().len();
|
||||
let initial_segment_count = initial_data.segment_domain.ids().len();
|
||||
let (initial_vector, _) = &initial_vector_and_transform[0];
|
||||
let initial_point_count = initial_vector.point_domain.ids().len();
|
||||
let initial_segment_count = initial_vector.segment_domain.ids().len();
|
||||
|
||||
let existing_layer_id = {
|
||||
let document = editor.active_document();
|
||||
@@ -642,8 +631,8 @@ mod test_freehand {
|
||||
)
|
||||
.await;
|
||||
|
||||
let final_vector_data = get_vector_data(&mut editor).await;
|
||||
assert!(!final_vector_data.is_empty(), "No vector data found after second drawing");
|
||||
let final_vector_and_transform = get_vector_and_transform_list(&mut editor).await;
|
||||
assert!(!final_vector_and_transform.is_empty(), "No vector geometry found after second drawing");
|
||||
|
||||
// Verify we still have only one layer
|
||||
let layer_count = {
|
||||
@@ -652,9 +641,9 @@ mod test_freehand {
|
||||
};
|
||||
assert_eq!(layer_count, 1, "Expected only one layer after drawing with Shift key");
|
||||
|
||||
let (final_data, _) = &final_vector_data[0];
|
||||
let final_point_count = final_data.point_domain.ids().len();
|
||||
let final_segment_count = final_data.segment_domain.ids().len();
|
||||
let (final_vector, _) = &final_vector_and_transform[0];
|
||||
let final_point_count = final_vector.point_domain.ids().len();
|
||||
let final_segment_count = final_vector.segment_domain.ids().len();
|
||||
|
||||
assert!(
|
||||
final_point_count > initial_point_count,
|
||||
|
||||
@@ -674,6 +674,7 @@ mod test_gradient {
|
||||
let folder = layers.next().unwrap();
|
||||
let rectangle = layers.next().unwrap();
|
||||
assert_eq!(rectangle.parent(metadata), Some(folder));
|
||||
|
||||
// Transform the group
|
||||
editor
|
||||
.handle_message(GraphOperationMessage::TransformSet {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -14,8 +14,8 @@ use crate::messages::tool::common_functionality::utility_functions::{calculate_s
|
||||
use bezier_rs::{Bezier, BezierHandles};
|
||||
use graph_craft::document::NodeId;
|
||||
use graphene_std::Color;
|
||||
use graphene_std::vector::{HandleId, ManipulatorPointId, NoHashBuilder, SegmentId, StrokeId, VectorData};
|
||||
use graphene_std::vector::{PointId, VectorModificationType};
|
||||
use graphene_std::vector::misc::{HandleId, ManipulatorPointId};
|
||||
use graphene_std::vector::{NoHashBuilder, PointId, SegmentId, StrokeId, Vector, VectorModificationType};
|
||||
|
||||
#[derive(Default, ExtractField)]
|
||||
pub struct PenTool {
|
||||
@@ -395,11 +395,11 @@ impl PenToolData {
|
||||
}
|
||||
|
||||
/// Check whether target handle is primary, end, or `self.handle_end`
|
||||
fn check_end_handle_type(&self, vector_data: &VectorData) -> TargetHandle {
|
||||
fn check_end_handle_type(&self, vector: &Vector) -> TargetHandle {
|
||||
match (self.handle_end, self.prior_segment_endpoint, self.prior_segment, self.path_closed) {
|
||||
(Some(_), _, _, false) => TargetHandle::PreviewInHandle,
|
||||
(None, Some(point), Some(segment), false) | (Some(_), Some(point), Some(segment), true) => {
|
||||
if vector_data.segment_start_from_id(segment) == Some(point) {
|
||||
if vector.segment_start_from_id(segment) == Some(point) {
|
||||
TargetHandle::PriorOutHandle(segment)
|
||||
} else {
|
||||
TargetHandle::PriorInHandle(segment)
|
||||
@@ -409,23 +409,23 @@ impl PenToolData {
|
||||
}
|
||||
}
|
||||
|
||||
fn check_grs_end_handle(&self, vector_data: &VectorData) -> TargetHandle {
|
||||
fn check_grs_end_handle(&self, vector: &Vector) -> TargetHandle {
|
||||
let Some(point) = self.latest_point().map(|point| point.id) else { return TargetHandle::None };
|
||||
let Some(segment) = self.prior_segment else { return TargetHandle::None };
|
||||
|
||||
if vector_data.segment_start_from_id(segment) == Some(point) {
|
||||
if vector.segment_start_from_id(segment) == Some(point) {
|
||||
TargetHandle::PriorOutHandle(segment)
|
||||
} else {
|
||||
TargetHandle::PriorInHandle(segment)
|
||||
}
|
||||
}
|
||||
|
||||
fn get_opposite_handle_type(&self, handle_type: TargetHandle, vector_data: &VectorData) -> TargetHandle {
|
||||
fn get_opposite_handle_type(&self, handle_type: TargetHandle, vector: &Vector) -> TargetHandle {
|
||||
match handle_type {
|
||||
TargetHandle::FuturePreviewOutHandle => self.check_end_handle_type(vector_data),
|
||||
TargetHandle::FuturePreviewOutHandle => self.check_end_handle_type(vector),
|
||||
TargetHandle::PreviewInHandle => match (self.path_closed, self.prior_segment_endpoint, self.prior_segment) {
|
||||
(true, Some(point), Some(segment)) => {
|
||||
if vector_data.segment_start_from_id(segment) == Some(point) {
|
||||
if vector.segment_start_from_id(segment) == Some(point) {
|
||||
TargetHandle::PriorOutHandle(segment)
|
||||
} else {
|
||||
TargetHandle::PriorInHandle(segment)
|
||||
@@ -472,10 +472,10 @@ impl PenToolData {
|
||||
}
|
||||
}
|
||||
|
||||
fn target_handle_position(&self, handle_type: TargetHandle, vector_data: &VectorData) -> Option<DVec2> {
|
||||
fn target_handle_position(&self, handle_type: TargetHandle, vector: &Vector) -> Option<DVec2> {
|
||||
match handle_type {
|
||||
TargetHandle::PriorOutHandle(segment) => ManipulatorPointId::PrimaryHandle(segment).get_position(vector_data),
|
||||
TargetHandle::PriorInHandle(segment) => ManipulatorPointId::EndHandle(segment).get_position(vector_data),
|
||||
TargetHandle::PriorOutHandle(segment) => ManipulatorPointId::PrimaryHandle(segment).get_position(vector),
|
||||
TargetHandle::PriorInHandle(segment) => ManipulatorPointId::EndHandle(segment).get_position(vector),
|
||||
TargetHandle::PreviewInHandle => self.handle_end,
|
||||
TargetHandle::FuturePreviewOutHandle => Some(self.next_handle_start),
|
||||
TargetHandle::None => None,
|
||||
@@ -488,11 +488,11 @@ impl PenToolData {
|
||||
return;
|
||||
};
|
||||
|
||||
let Some(vector_data) = layer.and_then(|layer| document.network_interface.compute_modified_vector(layer)) else {
|
||||
let Some(vector) = layer.and_then(|layer| document.network_interface.compute_modified_vector(layer)) else {
|
||||
return;
|
||||
};
|
||||
|
||||
match self.check_end_handle_type(&vector_data) {
|
||||
match self.check_end_handle_type(&vector) {
|
||||
TargetHandle::PriorInHandle(segment) => shape_state.deselect_point(ManipulatorPointId::EndHandle(segment)),
|
||||
TargetHandle::PriorOutHandle(segment) => shape_state.deselect_point(ManipulatorPointId::PrimaryHandle(segment)),
|
||||
_ => {}
|
||||
@@ -518,18 +518,14 @@ impl PenToolData {
|
||||
self.latest_points.len() == 1 && self.latest_point().is_some_and(|point| point.pos == self.next_point)
|
||||
}
|
||||
|
||||
// When the vector data transform changes, the positions of the points must be recalculated.
|
||||
// When the vector transform changes, the positions of the points must be recalculated.
|
||||
fn recalculate_latest_points_position(&mut self, document: &DocumentMessageHandler) {
|
||||
let selected_nodes = document.network_interface.selected_nodes();
|
||||
let mut selected_layers = selected_nodes.selected_layers(document.metadata());
|
||||
if let (Some(layer), None) = (selected_layers.next(), selected_layers.next()) {
|
||||
let Some(vector_data) = document.network_interface.compute_modified_vector(layer) else {
|
||||
return;
|
||||
};
|
||||
let Some(vector) = document.network_interface.compute_modified_vector(layer) else { return };
|
||||
for point in &mut self.latest_points {
|
||||
let Some(pos) = vector_data.point_domain.position_from_id(point.id) else {
|
||||
continue;
|
||||
};
|
||||
let Some(pos) = vector.point_domain.position_from_id(point.id) else { continue };
|
||||
point.pos = pos;
|
||||
point.handle_start = point.pos;
|
||||
}
|
||||
@@ -549,7 +545,7 @@ impl PenToolData {
|
||||
self.g1_continuous = true;
|
||||
let document = snap_data.document;
|
||||
self.next_handle_start = self.next_point;
|
||||
let vector_data = document.network_interface.compute_modified_vector(layer).unwrap();
|
||||
let vector = document.network_interface.compute_modified_vector(layer).unwrap();
|
||||
self.update_handle_type(TargetHandle::FuturePreviewOutHandle);
|
||||
self.handle_mode = HandleMode::ColinearLocked;
|
||||
|
||||
@@ -565,7 +561,7 @@ impl PenToolData {
|
||||
self.store_clicked_endpoint(document, &transform, snap_data.input, preferences);
|
||||
|
||||
if self.modifiers.lock_angle {
|
||||
self.set_lock_angle(&vector_data, id, self.prior_segment);
|
||||
self.set_lock_angle(&vector, id, self.prior_segment);
|
||||
let last_segment = self.prior_segment;
|
||||
let Some(point) = self.latest_point_mut() else { return };
|
||||
point.in_segment = last_segment;
|
||||
@@ -579,7 +575,7 @@ impl PenToolData {
|
||||
}
|
||||
|
||||
// Closing path
|
||||
let closing_path_on_point = self.close_path_on_point(snap_data, &vector_data, document, preferences, id, &transform);
|
||||
let closing_path_on_point = self.close_path_on_point(snap_data, &vector, document, preferences, id, &transform);
|
||||
if !closing_path_on_point && preferences.vector_meshes {
|
||||
// Attempt to find nearest segment and close path on segment by creating an anchor point on it
|
||||
let tolerance = crate::consts::SNAP_POINT_TOLERANCE;
|
||||
@@ -600,24 +596,16 @@ impl PenToolData {
|
||||
|
||||
self.handle_mode = HandleMode::Free;
|
||||
if let (true, Some(prior_endpoint)) = (self.modifiers.lock_angle, self.prior_segment_endpoint) {
|
||||
self.set_lock_angle(&vector_data, prior_endpoint, self.prior_segment);
|
||||
self.set_lock_angle(&vector, prior_endpoint, self.prior_segment);
|
||||
self.switch_to_free_on_ctrl_release = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn close_path_on_point(
|
||||
&mut self,
|
||||
snap_data: SnapData,
|
||||
vector_data: &VectorData,
|
||||
document: &DocumentMessageHandler,
|
||||
preferences: &PreferencesMessageHandler,
|
||||
id: PointId,
|
||||
transform: &DAffine2,
|
||||
) -> bool {
|
||||
for id in vector_data.extendable_points(preferences.vector_meshes).filter(|&point| point != id) {
|
||||
let Some(pos) = vector_data.point_domain.position_from_id(id) else { continue };
|
||||
fn close_path_on_point(&mut self, snap_data: SnapData, vector: &Vector, document: &DocumentMessageHandler, preferences: &PreferencesMessageHandler, id: PointId, transform: &DAffine2) -> bool {
|
||||
for id in vector.extendable_points(preferences.vector_meshes).filter(|&point| point != id) {
|
||||
let Some(pos) = vector.point_domain.position_from_id(id) else { continue };
|
||||
let transformed_distance_between_squared = transform.transform_point2(pos).distance_squared(transform.transform_point2(self.next_point));
|
||||
let snap_point_tolerance_squared = crate::consts::SNAP_POINT_TOLERANCE.powi(2);
|
||||
|
||||
@@ -629,7 +617,7 @@ impl PenToolData {
|
||||
self.store_clicked_endpoint(document, transform, snap_data.input, preferences);
|
||||
self.handle_mode = HandleMode::Free;
|
||||
if let (true, Some(prior_endpoint)) = (self.modifiers.lock_angle, self.prior_segment_endpoint) {
|
||||
self.set_lock_angle(vector_data, prior_endpoint, self.prior_segment);
|
||||
self.set_lock_angle(vector, prior_endpoint, self.prior_segment);
|
||||
self.switch_to_free_on_ctrl_release = true;
|
||||
}
|
||||
return true;
|
||||
@@ -662,11 +650,11 @@ impl PenToolData {
|
||||
let selected_nodes = document.network_interface.selected_nodes();
|
||||
let mut selected_layers = selected_nodes.selected_layers(document.metadata());
|
||||
let layer = selected_layers.next().filter(|_| selected_layers.next().is_none()).or(self.current_layer)?;
|
||||
let vector_data = document.network_interface.compute_modified_vector(layer)?;
|
||||
let vector = document.network_interface.compute_modified_vector(layer)?;
|
||||
let start = self.latest_point()?.id;
|
||||
let transform = document.metadata().document_to_viewport * transform;
|
||||
for id in vector_data.extendable_points(preferences.vector_meshes).filter(|&point| point != start) {
|
||||
let Some(pos) = vector_data.point_domain.position_from_id(id) else { continue };
|
||||
for id in vector.extendable_points(preferences.vector_meshes).filter(|&point| point != start) {
|
||||
let Some(pos) = vector.point_domain.position_from_id(id) else { continue };
|
||||
let transformed_distance_between_squared = transform.transform_point2(pos).distance_squared(transform.transform_point2(next_point));
|
||||
let snap_point_tolerance_squared = crate::consts::SNAP_POINT_TOLERANCE.powi(2);
|
||||
if transformed_distance_between_squared < snap_point_tolerance_squared {
|
||||
@@ -687,15 +675,15 @@ impl PenToolData {
|
||||
// Store the segment
|
||||
let id = SegmentId::generate();
|
||||
if self.path_closed {
|
||||
if let Some((handles, handle1_pos)) = match self.get_opposite_handle_type(TargetHandle::PreviewInHandle, &vector_data) {
|
||||
if let Some((handles, handle1_pos)) = match self.get_opposite_handle_type(TargetHandle::PreviewInHandle, &vector) {
|
||||
TargetHandle::PriorOutHandle(segment) => {
|
||||
let handles = [HandleId::end(id), HandleId::primary(segment)];
|
||||
let handle1_pos = handles[1].to_manipulator_point().get_position(&vector_data);
|
||||
let handle1_pos = handles[1].to_manipulator_point().get_position(&vector);
|
||||
handle1_pos.map(|pos| (handles, pos))
|
||||
}
|
||||
TargetHandle::PriorInHandle(segment) => {
|
||||
let handles = [HandleId::end(id), HandleId::end(segment)];
|
||||
let handle1_pos = handles[1].to_manipulator_point().get_position(&vector_data);
|
||||
let handle1_pos = handles[1].to_manipulator_point().get_position(&vector);
|
||||
handle1_pos.map(|pos| (handles, pos))
|
||||
}
|
||||
_ => None,
|
||||
@@ -719,14 +707,14 @@ impl PenToolData {
|
||||
|
||||
// Mirror
|
||||
if let Some((last_segment, last_point)) = self.latest_point().and_then(|point| point.in_segment).zip(self.latest_point()) {
|
||||
let end = vector_data.segment_end_from_id(last_segment) == Some(last_point.id);
|
||||
let end = vector.segment_end_from_id(last_segment) == Some(last_point.id);
|
||||
let handles = if end {
|
||||
[HandleId::end(last_segment), HandleId::primary(id)]
|
||||
} else {
|
||||
[HandleId::primary(last_segment), HandleId::primary(id)]
|
||||
};
|
||||
|
||||
if let Some(h1) = handles[0].to_manipulator_point().get_position(&vector_data) {
|
||||
if let Some(h1) = handles[0].to_manipulator_point().get_position(&vector) {
|
||||
let angle = (h1 - last_point.pos).angle_to(last_point.handle_start - last_point.pos);
|
||||
let pi = std::f64::consts::PI;
|
||||
let colinear = (angle - pi).abs() < 1e-6 || (angle + pi).abs() < 1e-6;
|
||||
@@ -758,13 +746,13 @@ impl PenToolData {
|
||||
transform: &DAffine2,
|
||||
snap_data: &SnapData<'_>,
|
||||
mouse: &DVec2,
|
||||
vector_data: &VectorData,
|
||||
vector: &Vector,
|
||||
input: &InputPreprocessorMessageHandler,
|
||||
) -> Option<DVec2> {
|
||||
let reference_handle = if self.path_closed { TargetHandle::PreviewInHandle } else { TargetHandle::FuturePreviewOutHandle };
|
||||
let end_handle = self.get_opposite_handle_type(reference_handle, vector_data);
|
||||
let end_handle_pos = self.target_handle_position(end_handle, vector_data);
|
||||
let ref_pos = self.target_handle_position(reference_handle, vector_data)?;
|
||||
let end_handle = self.get_opposite_handle_type(reference_handle, vector);
|
||||
let end_handle_pos = self.target_handle_position(end_handle, vector);
|
||||
let ref_pos = self.target_handle_position(reference_handle, vector)?;
|
||||
let snap = &mut self.snap_manager;
|
||||
let snap_data = SnapData::new_snap_cache(snap_data.document, input, &self.snap_cache);
|
||||
|
||||
@@ -826,7 +814,7 @@ impl PenToolData {
|
||||
responses: &mut VecDeque<Message>,
|
||||
) {
|
||||
// Validate necessary data exists
|
||||
let Some(vector_data) = layer.and_then(|layer| document.network_interface.compute_modified_vector(layer)) else {
|
||||
let Some(vector) = layer.and_then(|layer| document.network_interface.compute_modified_vector(layer)) else {
|
||||
return;
|
||||
};
|
||||
|
||||
@@ -842,9 +830,9 @@ impl PenToolData {
|
||||
let should_swap_to_start = !self.path_closed && !matches!(self.handle_type, TargetHandle::None | TargetHandle::FuturePreviewOutHandle);
|
||||
|
||||
if should_swap_to_opposite {
|
||||
let opposite_type = self.get_opposite_handle_type(self.handle_type, &vector_data);
|
||||
let opposite_type = self.get_opposite_handle_type(self.handle_type, &vector);
|
||||
// Update offset
|
||||
let Some(handle_pos) = self.target_handle_position(opposite_type, &vector_data) else {
|
||||
let Some(handle_pos) = self.target_handle_position(opposite_type, &vector) else {
|
||||
self.handle_swapped = false;
|
||||
return;
|
||||
};
|
||||
@@ -894,7 +882,7 @@ impl PenToolData {
|
||||
Some(PenToolFsmState::DraggingHandle(self.handle_mode))
|
||||
}
|
||||
|
||||
fn move_anchor_and_handles(&mut self, delta: DVec2, layer: LayerNodeIdentifier, responses: &mut VecDeque<Message>, vector_data: &VectorData) {
|
||||
fn move_anchor_and_handles(&mut self, delta: DVec2, layer: LayerNodeIdentifier, responses: &mut VecDeque<Message>, vector: &Vector) {
|
||||
if self.handle_end.is_none() {
|
||||
if let Some(latest_pt) = self.latest_point_mut() {
|
||||
latest_pt.pos += delta;
|
||||
@@ -912,10 +900,10 @@ impl PenToolData {
|
||||
let reference_handle = if self.path_closed { TargetHandle::PreviewInHandle } else { TargetHandle::FuturePreviewOutHandle };
|
||||
|
||||
// Move the end handle
|
||||
let end_handle_type = self.get_opposite_handle_type(reference_handle, vector_data);
|
||||
let end_handle_type = self.get_opposite_handle_type(reference_handle, vector);
|
||||
match end_handle_type {
|
||||
TargetHandle::PriorInHandle(..) | TargetHandle::PriorOutHandle(..) => {
|
||||
let Some(handle_pos) = self.target_handle_position(end_handle_type, vector_data) else { return };
|
||||
let Some(handle_pos) = self.target_handle_position(end_handle_type, vector) else { return };
|
||||
self.update_target_handle_pos(end_handle_type, self.next_point, responses, handle_pos + delta, layer);
|
||||
}
|
||||
_ => {}
|
||||
@@ -934,13 +922,13 @@ impl PenToolData {
|
||||
let colinear = (self.handle_mode == HandleMode::ColinearEquidistant && self.modifiers.break_handle) || (self.handle_mode == HandleMode::ColinearLocked && !self.modifiers.break_handle);
|
||||
let document = snap_data.document;
|
||||
let Some(layer) = layer else { return Some(PenToolFsmState::DraggingHandle(self.handle_mode)) };
|
||||
let vector_data = document.network_interface.compute_modified_vector(layer)?;
|
||||
let vector = document.network_interface.compute_modified_vector(layer)?;
|
||||
let viewport_to_document = document.metadata().document_to_viewport.inverse();
|
||||
let mut mouse_pos = mouse;
|
||||
|
||||
// Handles pressing Space to drag anchor and its handles
|
||||
if self.modifiers.move_anchor_with_handles {
|
||||
let Some(delta) = self.space_anchor_handle_snap(&viewport_to_document, &transform, &snap_data, &mouse, &vector_data, input) else {
|
||||
let Some(delta) = self.space_anchor_handle_snap(&viewport_to_document, &transform, &snap_data, &mouse, &vector, input) else {
|
||||
return Some(PenToolFsmState::DraggingHandle(self.handle_mode));
|
||||
};
|
||||
|
||||
@@ -959,7 +947,7 @@ impl PenToolData {
|
||||
};
|
||||
}
|
||||
|
||||
self.move_anchor_and_handles(delta, layer, responses, &vector_data);
|
||||
self.move_anchor_and_handles(delta, layer, responses, &vector);
|
||||
|
||||
responses.add(OverlaysMessage::Draw);
|
||||
return Some(PenToolFsmState::DraggingHandle(self.handle_mode));
|
||||
@@ -994,8 +982,8 @@ impl PenToolData {
|
||||
match self.handle_mode {
|
||||
HandleMode::ColinearLocked | HandleMode::ColinearEquidistant => {
|
||||
self.g1_continuous = true;
|
||||
self.apply_colinear_constraint(responses, layer, self.next_point, &vector_data);
|
||||
self.adjust_handle_length(responses, layer, &vector_data);
|
||||
self.apply_colinear_constraint(responses, layer, self.next_point, &vector);
|
||||
self.adjust_handle_length(responses, layer, &vector);
|
||||
}
|
||||
HandleMode::Free => {
|
||||
self.g1_continuous = false;
|
||||
@@ -1006,7 +994,7 @@ impl PenToolData {
|
||||
let Some(endpoint) = self.prior_segment_endpoint else {
|
||||
return Some(PenToolFsmState::DraggingHandle(self.handle_mode));
|
||||
};
|
||||
self.set_lock_angle(&vector_data, endpoint, self.prior_segment);
|
||||
self.set_lock_angle(&vector, endpoint, self.prior_segment);
|
||||
self.switch_to_free_on_ctrl_release = true;
|
||||
let last_segment = self.prior_segment;
|
||||
if let Some(latest) = self.latest_point_mut() {
|
||||
@@ -1020,19 +1008,19 @@ impl PenToolData {
|
||||
}
|
||||
|
||||
/// Makes the opposite handle equidistant or locks its length.
|
||||
fn adjust_handle_length(&mut self, responses: &mut VecDeque<Message>, layer: LayerNodeIdentifier, vector_data: &VectorData) {
|
||||
let opposite_handle_type = self.get_opposite_handle_type(self.handle_type, vector_data);
|
||||
fn adjust_handle_length(&mut self, responses: &mut VecDeque<Message>, layer: LayerNodeIdentifier, vector: &Vector) {
|
||||
let opposite_handle_type = self.get_opposite_handle_type(self.handle_type, vector);
|
||||
match self.handle_mode {
|
||||
HandleMode::ColinearEquidistant => {
|
||||
if self.modifiers.break_handle {
|
||||
// Store handle for later restoration only when Alt is first pressed
|
||||
if !self.alt_pressed {
|
||||
self.previous_handle_end_pos = self.target_handle_position(opposite_handle_type, vector_data);
|
||||
self.previous_handle_end_pos = self.target_handle_position(opposite_handle_type, vector);
|
||||
self.alt_pressed = true;
|
||||
}
|
||||
|
||||
// Set handle to opposite position of the other handle
|
||||
let Some(new_position) = self.target_handle_position(self.handle_type, vector_data).map(|handle| self.next_point * 2. - handle) else {
|
||||
let Some(new_position) = self.target_handle_position(self.handle_type, vector).map(|handle| self.next_point * 2. - handle) else {
|
||||
return;
|
||||
};
|
||||
self.update_target_handle_pos(opposite_handle_type, self.next_point, responses, new_position, layer);
|
||||
@@ -1047,7 +1035,7 @@ impl PenToolData {
|
||||
}
|
||||
HandleMode::ColinearLocked => {
|
||||
if !self.modifiers.break_handle {
|
||||
let Some(new_position) = self.target_handle_position(self.handle_type, vector_data).map(|handle| self.next_point * 2. - handle) else {
|
||||
let Some(new_position) = self.target_handle_position(self.handle_type, vector).map(|handle| self.next_point * 2. - handle) else {
|
||||
return;
|
||||
};
|
||||
self.update_target_handle_pos(opposite_handle_type, self.next_point, responses, new_position, layer);
|
||||
@@ -1057,23 +1045,23 @@ impl PenToolData {
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_colinear_constraint(&mut self, responses: &mut VecDeque<Message>, layer: LayerNodeIdentifier, anchor_pos: DVec2, vector_data: &VectorData) {
|
||||
let Some(handle) = self.target_handle_position(self.handle_type, vector_data) else {
|
||||
return;
|
||||
};
|
||||
fn apply_colinear_constraint(&mut self, responses: &mut VecDeque<Message>, layer: LayerNodeIdentifier, anchor_pos: DVec2, vector: &Vector) {
|
||||
let Some(handle) = self.target_handle_position(self.handle_type, vector) else { return };
|
||||
|
||||
if (anchor_pos - handle).length() < 1e-6 && self.modifiers.lock_angle {
|
||||
return;
|
||||
}
|
||||
|
||||
let Some(direction) = (anchor_pos - handle).try_normalize() else {
|
||||
return;
|
||||
};
|
||||
let opposite_handle = self.get_opposite_handle_type(self.handle_type, vector_data);
|
||||
let Some(handle_offset) = self.target_handle_position(opposite_handle, vector_data).map(|handle| (handle - anchor_pos).length()) else {
|
||||
let Some(direction) = (anchor_pos - handle).try_normalize() else { return };
|
||||
|
||||
let opposite_handle = self.get_opposite_handle_type(self.handle_type, vector);
|
||||
|
||||
let Some(handle_offset) = self.target_handle_position(opposite_handle, vector).map(|handle| (handle - anchor_pos).length()) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let new_handle_position = anchor_pos + handle_offset * direction;
|
||||
|
||||
self.update_target_handle_pos(opposite_handle, self.next_point, responses, new_handle_position, layer);
|
||||
}
|
||||
|
||||
@@ -1086,10 +1074,10 @@ impl PenToolData {
|
||||
let selected_nodes = document.network_interface.selected_nodes();
|
||||
let mut selected_layers = selected_nodes.selected_layers(document.metadata());
|
||||
let layer = selected_layers.next().filter(|_| selected_layers.next().is_none()).or(self.current_layer)?;
|
||||
let vector_data = document.network_interface.compute_modified_vector(layer)?;
|
||||
let vector = document.network_interface.compute_modified_vector(layer)?;
|
||||
let transform = document.metadata().document_to_viewport * transform;
|
||||
for point in vector_data.extendable_points(preferences.vector_meshes) {
|
||||
let Some(pos) = vector_data.point_domain.position_from_id(point) else { continue };
|
||||
for point in vector.extendable_points(preferences.vector_meshes) {
|
||||
let Some(pos) = vector.point_domain.position_from_id(point) else { continue };
|
||||
let transformed_distance_between_squared = transform.transform_point2(pos).distance_squared(transform.transform_point2(self.next_point));
|
||||
let snap_point_tolerance_squared = crate::consts::SNAP_POINT_TOLERANCE.powi(2);
|
||||
if transformed_distance_between_squared < snap_point_tolerance_squared {
|
||||
@@ -1217,11 +1205,11 @@ impl PenToolData {
|
||||
|
||||
if append {
|
||||
if let Some((layer, point, _)) = closest_point(document, viewport, tolerance, document.metadata().all_layers(), |_| false, preferences) {
|
||||
let vector_data = document.network_interface.compute_modified_vector(layer).unwrap();
|
||||
let segment = vector_data.all_connected(point).collect::<Vec<_>>().first().map(|s| s.segment);
|
||||
let vector = document.network_interface.compute_modified_vector(layer).unwrap();
|
||||
let segment = vector.all_connected(point).collect::<Vec<_>>().first().map(|s| s.segment);
|
||||
|
||||
if self.modifiers.lock_angle {
|
||||
self.set_lock_angle(&vector_data, point, segment);
|
||||
self.set_lock_angle(&vector, point, segment);
|
||||
self.switch_to_free_on_ctrl_release = true;
|
||||
}
|
||||
}
|
||||
@@ -1235,11 +1223,11 @@ impl PenToolData {
|
||||
}
|
||||
|
||||
if let Some((layer, point, _position)) = closest_point(document, viewport, tolerance, document.metadata().all_layers(), |_| false, preferences) {
|
||||
let vector_data = document.network_interface.compute_modified_vector(layer).unwrap();
|
||||
let segment = vector_data.all_connected(point).collect::<Vec<_>>().first().map(|s| s.segment);
|
||||
let vector = document.network_interface.compute_modified_vector(layer).unwrap();
|
||||
let segment = vector.all_connected(point).collect::<Vec<_>>().first().map(|s| s.segment);
|
||||
self.handle_mode = HandleMode::Free;
|
||||
if self.modifiers.lock_angle {
|
||||
self.set_lock_angle(&vector_data, point, segment);
|
||||
self.set_lock_angle(&vector, point, segment);
|
||||
self.switch_to_free_on_ctrl_release = true;
|
||||
}
|
||||
}
|
||||
@@ -1261,13 +1249,14 @@ impl PenToolData {
|
||||
responses.add(DeferMessage::AfterGraphRun {
|
||||
messages: vec![PenToolMessage::AddPointLayerPosition { layer, viewport }.into()],
|
||||
});
|
||||
responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||
}
|
||||
|
||||
/// Perform extension of an existing path
|
||||
fn extend_existing_path(&mut self, document: &DocumentMessageHandler, layer: LayerNodeIdentifier, point: PointId, position: DVec2) {
|
||||
let vector_data = document.network_interface.compute_modified_vector(layer);
|
||||
let (handle_start, in_segment) = if let Some(vector_data) = &vector_data {
|
||||
vector_data
|
||||
let vector = document.network_interface.compute_modified_vector(layer);
|
||||
let (handle_start, in_segment) = if let Some(vector) = &vector {
|
||||
vector
|
||||
.segment_bezier_iter()
|
||||
.find_map(|(segment_id, bezier, start, end)| {
|
||||
let is_end = point == end;
|
||||
@@ -1310,12 +1299,12 @@ impl PenToolData {
|
||||
|
||||
self.next_point = position;
|
||||
self.next_handle_start = handle_start;
|
||||
let vector_data = document.network_interface.compute_modified_vector(layer).unwrap();
|
||||
let segment = vector_data.all_connected(point).collect::<Vec<_>>().first().map(|s| s.segment);
|
||||
let vector = document.network_interface.compute_modified_vector(layer).unwrap();
|
||||
let segment = vector.all_connected(point).collect::<Vec<_>>().first().map(|s| s.segment);
|
||||
self.handle_mode = HandleMode::Free;
|
||||
|
||||
if self.modifiers.lock_angle {
|
||||
self.set_lock_angle(&vector_data, point, segment);
|
||||
self.set_lock_angle(&vector, point, segment);
|
||||
self.switch_to_free_on_ctrl_release = true;
|
||||
}
|
||||
}
|
||||
@@ -1340,11 +1329,11 @@ impl PenToolData {
|
||||
if let Some((layer, point, _position)) = closest_point(document, viewport, tolerance, document.metadata().all_layers(), |_| false, preferences) {
|
||||
self.prior_segment_endpoint = Some(point);
|
||||
self.prior_segment_layer = Some(layer);
|
||||
let vector_data = document.network_interface.compute_modified_vector(layer).unwrap();
|
||||
let segment = vector_data.all_connected(point).collect::<Vec<_>>().first().map(|s| s.segment);
|
||||
let vector = document.network_interface.compute_modified_vector(layer).unwrap();
|
||||
let segment = vector.all_connected(point).collect::<Vec<_>>().first().map(|s| s.segment);
|
||||
self.prior_segment = segment;
|
||||
layer_manipulators.insert(point);
|
||||
for (&id, &position) in vector_data.point_domain.ids().iter().zip(vector_data.point_domain.positions()) {
|
||||
for (&id, &position) in vector.point_domain.ids().iter().zip(vector.point_domain.positions()) {
|
||||
if id == point {
|
||||
continue;
|
||||
}
|
||||
@@ -1355,8 +1344,8 @@ impl PenToolData {
|
||||
}
|
||||
}
|
||||
|
||||
fn set_lock_angle(&mut self, vector_data: &VectorData, anchor: PointId, segment: Option<SegmentId>) {
|
||||
let anchor_position = vector_data.point_domain.position_from_id(anchor);
|
||||
fn set_lock_angle(&mut self, vector: &Vector, anchor: PointId, segment: Option<SegmentId>) {
|
||||
let anchor_position = vector.point_domain.position_from_id(anchor);
|
||||
|
||||
let Some((anchor_position, segment)) = anchor_position.zip(segment) else {
|
||||
self.handle_mode = HandleMode::Free;
|
||||
@@ -1365,7 +1354,7 @@ impl PenToolData {
|
||||
|
||||
match (self.handle_type, self.path_closed) {
|
||||
(TargetHandle::FuturePreviewOutHandle, _) | (TargetHandle::PreviewInHandle, true) => {
|
||||
if let Some(required_handle) = calculate_segment_angle(anchor, segment, vector_data, true) {
|
||||
if let Some(required_handle) = calculate_segment_angle(anchor, segment, vector, true) {
|
||||
self.angle = required_handle;
|
||||
self.handle_mode = HandleMode::ColinearEquidistant;
|
||||
}
|
||||
@@ -1460,12 +1449,12 @@ impl Fsm for PenToolFsmState {
|
||||
responses.add(TransformLayerMessage::BeginScalePen { last_point, handle });
|
||||
}
|
||||
|
||||
let vector_data = document.network_interface.compute_modified_vector(layer).unwrap();
|
||||
let vector = document.network_interface.compute_modified_vector(layer).unwrap();
|
||||
tool_data.previous_handle_start_pos = latest.handle_start;
|
||||
let opposite_handle = tool_data.check_grs_end_handle(&vector_data);
|
||||
tool_data.previous_handle_end_pos = tool_data.target_handle_position(opposite_handle, &vector_data);
|
||||
let opposite_handle = tool_data.check_grs_end_handle(&vector);
|
||||
tool_data.previous_handle_end_pos = tool_data.target_handle_position(opposite_handle, &vector);
|
||||
let handle1 = latest_handle_start - latest_pos;
|
||||
let Some(opposite_handle_pos) = tool_data.target_handle_position(opposite_handle, &vector_data) else {
|
||||
let Some(opposite_handle_pos) = tool_data.target_handle_position(opposite_handle, &vector) else {
|
||||
return PenToolFsmState::GRSHandle;
|
||||
};
|
||||
let handle2 = opposite_handle_pos - latest_pos;
|
||||
@@ -1476,8 +1465,8 @@ impl Fsm for PenToolFsmState {
|
||||
}
|
||||
(PenToolFsmState::GRSHandle, PenToolMessage::FinalPosition { final_position }) => {
|
||||
let Some(layer) = layer else { return PenToolFsmState::GRSHandle };
|
||||
let vector_data = document.network_interface.compute_modified_vector(layer);
|
||||
let Some(vector_data) = vector_data else { return PenToolFsmState::GRSHandle };
|
||||
let vector = document.network_interface.compute_modified_vector(layer);
|
||||
let Some(vector) = vector else { return PenToolFsmState::GRSHandle };
|
||||
|
||||
if let Some(latest_pt) = tool_data.latest_point_mut() {
|
||||
let layer_space_to_viewport = document.metadata().transform_to_viewport(layer);
|
||||
@@ -1489,8 +1478,8 @@ impl Fsm for PenToolFsmState {
|
||||
let Some(latest) = tool_data.latest_point() else {
|
||||
return PenToolFsmState::GRSHandle;
|
||||
};
|
||||
let opposite_handle = tool_data.check_grs_end_handle(&vector_data);
|
||||
let Some(opposite_handle_pos) = tool_data.target_handle_position(opposite_handle, &vector_data) else {
|
||||
let opposite_handle = tool_data.check_grs_end_handle(&vector);
|
||||
let Some(opposite_handle_pos) = tool_data.target_handle_position(opposite_handle, &vector) else {
|
||||
return PenToolFsmState::GRSHandle;
|
||||
};
|
||||
|
||||
@@ -1531,8 +1520,8 @@ impl Fsm for PenToolFsmState {
|
||||
tool_data.next_handle_start = input.mouse.position;
|
||||
|
||||
let Some(layer) = layer else { return PenToolFsmState::GRSHandle };
|
||||
let vector_data = document.network_interface.compute_modified_vector(layer).unwrap();
|
||||
let opposite_handle = tool_data.check_grs_end_handle(&vector_data);
|
||||
let vector = document.network_interface.compute_modified_vector(layer).unwrap();
|
||||
let opposite_handle = tool_data.check_grs_end_handle(&vector);
|
||||
|
||||
let previous = tool_data.previous_handle_start_pos;
|
||||
if let Some(latest) = tool_data.latest_point_mut() {
|
||||
@@ -1722,10 +1711,10 @@ impl Fsm for PenToolFsmState {
|
||||
let start = latest_point.id;
|
||||
|
||||
if let Some(layer) = layer
|
||||
&& let Some(mut vector_data) = document.network_interface.compute_modified_vector(layer)
|
||||
&& let Some(mut vector) = document.network_interface.compute_modified_vector(layer)
|
||||
{
|
||||
let closest_point = vector_data.extendable_points(preferences.vector_meshes).filter(|&id| id != start).find(|&id| {
|
||||
vector_data.point_domain.position_from_id(id).map_or(false, |pos| {
|
||||
let closest_point = vector.extendable_points(preferences.vector_meshes).filter(|&id| id != start).find(|&id| {
|
||||
vector.point_domain.position_from_id(id).is_some_and(|pos| {
|
||||
let dist_sq = transform.transform_point2(pos).distance_squared(transform.transform_point2(next_point));
|
||||
dist_sq < crate::consts::SNAP_POINT_TOLERANCE.powi(2)
|
||||
})
|
||||
@@ -1734,21 +1723,21 @@ impl Fsm for PenToolFsmState {
|
||||
// We have the point. Join the 2 vertices and check if any path is closed.
|
||||
if let Some(end) = closest_point {
|
||||
let segment_id = SegmentId::generate();
|
||||
vector_data.push(segment_id, start, end, BezierHandles::Cubic { handle_start, handle_end }, StrokeId::ZERO);
|
||||
vector.push(segment_id, start, end, BezierHandles::Cubic { handle_start, handle_end }, StrokeId::ZERO);
|
||||
|
||||
let grouped_segments = vector_data.auto_join_paths();
|
||||
let grouped_segments = vector.auto_join_paths();
|
||||
let closed_paths = grouped_segments.iter().filter(|path| path.is_closed() && path.contains(segment_id));
|
||||
|
||||
let subpaths: Vec<_> = closed_paths
|
||||
.filter_map(|path| {
|
||||
let segments = path.edges.iter().filter_map(|edge| {
|
||||
vector_data
|
||||
vector
|
||||
.segment_domain
|
||||
.iter()
|
||||
.find(|(id, _, _, _)| id == &edge.id)
|
||||
.map(|(_, start, end, bezier)| if start == edge.start { (bezier, start, end) } else { (bezier.reversed(), end, start) })
|
||||
});
|
||||
vector_data.subpath_from_segments_ignore_discontinuities(segments)
|
||||
vector.subpath_from_segments_ignore_discontinuities(segments)
|
||||
})
|
||||
.collect();
|
||||
|
||||
@@ -1817,7 +1806,8 @@ impl Fsm for PenToolFsmState {
|
||||
}
|
||||
// Merge two layers if the point is connected to the end point of another path
|
||||
|
||||
// This might not be the correct solution to artboards being included as the other layer, which occurs due to the compute_modified_vector call in should_extend using the click targets for a layer instead of vector data.
|
||||
// This might not be the correct solution to artboards being included as the other layer,
|
||||
// which occurs due to the `compute_modified_vector` call in `should_extend` using the click targets for a layer instead of vector.
|
||||
let layers = LayerNodeIdentifier::ROOT_PARENT
|
||||
.descendants(document.metadata())
|
||||
.filter(|layer| !document.network_interface.is_artboard(&layer.to_node(), &[]));
|
||||
@@ -1887,7 +1877,7 @@ impl Fsm for PenToolFsmState {
|
||||
tool_data.toggle_colinear_debounce = true;
|
||||
}
|
||||
|
||||
let Some(vector_data) = layer.and_then(|layer| document.network_interface.compute_modified_vector(layer)) else {
|
||||
let Some(vector) = layer.and_then(|layer| document.network_interface.compute_modified_vector(layer)) else {
|
||||
return self;
|
||||
};
|
||||
|
||||
@@ -1901,7 +1891,7 @@ impl Fsm for PenToolFsmState {
|
||||
document
|
||||
.metadata()
|
||||
.transform_to_viewport(layer)
|
||||
.transform_point2(tool_data.target_handle_position(reference_handle, &vector_data).unwrap())
|
||||
.transform_point2(tool_data.target_handle_position(reference_handle, &vector).unwrap())
|
||||
});
|
||||
tool_data.handle_start_offset = handle_start.map(|start| start - input.mouse.position);
|
||||
tool_data.space_pressed = true;
|
||||
@@ -2075,8 +2065,8 @@ impl Fsm for PenToolFsmState {
|
||||
}
|
||||
(PenToolFsmState::DraggingHandle(..), PenToolMessage::Confirm) => {
|
||||
// Confirm to end path
|
||||
if let Some((vector_data, layer)) = layer.and_then(|layer| document.network_interface.compute_modified_vector(layer)).zip(layer) {
|
||||
let single_point_in_layer = vector_data.point_domain.ids().len() == 1;
|
||||
if let Some((vector, layer)) = layer.and_then(|layer| document.network_interface.compute_modified_vector(layer)).zip(layer) {
|
||||
let single_point_in_layer = vector.point_domain.ids().len() == 1;
|
||||
tool_data.finish_placing_handle(SnapData::new(document, input), transform, preferences, responses);
|
||||
let latest_points = tool_data.latest_points.len() == 1;
|
||||
|
||||
@@ -2129,8 +2119,8 @@ impl Fsm for PenToolFsmState {
|
||||
}
|
||||
}
|
||||
(PenToolFsmState::PlacingAnchor, PenToolMessage::Abort) => {
|
||||
let should_delete_layer = if let Some(vector_data) = layer.and_then(|layer| document.network_interface.compute_modified_vector(layer)) {
|
||||
vector_data.point_domain.ids().len() == 1
|
||||
let should_delete_layer = if let Some(vector) = layer.and_then(|layer| document.network_interface.compute_modified_vector(layer)) {
|
||||
vector.point_domain.ids().len() == 1
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
@@ -11,6 +11,7 @@ use crate::messages::tool::common_functionality::graph_modification_utils;
|
||||
use crate::messages::tool::common_functionality::graph_modification_utils::NodeGraphLayer;
|
||||
use crate::messages::tool::common_functionality::resize::Resize;
|
||||
use crate::messages::tool::common_functionality::shapes::arc_shape::Arc;
|
||||
use crate::messages::tool::common_functionality::shapes::circle_shape::Circle;
|
||||
use crate::messages::tool::common_functionality::shapes::line_shape::{LineToolData, clicked_on_line_endpoints};
|
||||
use crate::messages::tool::common_functionality::shapes::polygon_shape::Polygon;
|
||||
use crate::messages::tool::common_functionality::shapes::shape_utility::{ShapeToolModifierKey, ShapeType, anchor_overlays, transform_cage_overlays};
|
||||
@@ -110,6 +111,9 @@ fn create_shape_option_widget(shape_type: ShapeType) -> WidgetHolder {
|
||||
MenuListEntry::new("Star")
|
||||
.label("Star")
|
||||
.on_commit(move |_| ShapeToolMessage::UpdateOptions(ShapeOptionsUpdate::ShapeType(ShapeType::Star)).into()),
|
||||
MenuListEntry::new("Circle")
|
||||
.label("Circle")
|
||||
.on_commit(move |_| ShapeToolMessage::UpdateOptions(ShapeOptionsUpdate::ShapeType(ShapeType::Circle)).into()),
|
||||
MenuListEntry::new("Arc")
|
||||
.label("Arc")
|
||||
.on_commit(move |_| ShapeToolMessage::UpdateOptions(ShapeOptionsUpdate::ShapeType(ShapeType::Arc)).into()),
|
||||
@@ -229,7 +233,7 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionMessageContext<'a>> for Shap
|
||||
}
|
||||
}
|
||||
|
||||
self.fsm_state.update_hints(responses);
|
||||
update_dynamic_hints(&self.fsm_state, responses, &self.tool_data);
|
||||
self.send_layout(responses, LayoutTarget::ToolOptions);
|
||||
}
|
||||
|
||||
@@ -472,6 +476,9 @@ impl Fsm for ShapeToolFsmState {
|
||||
|
||||
if matches!(self, ShapeToolFsmState::Drawing(_) | ShapeToolFsmState::DraggingLineEndpoints) {
|
||||
Line::overlays(document, tool_data, &mut overlay_context);
|
||||
if tool_options.shape_type == ShapeType::Circle {
|
||||
tool_data.gizmo_manager.overlays(document, input, shape_editor, mouse_position, &mut overlay_context);
|
||||
}
|
||||
}
|
||||
|
||||
self
|
||||
@@ -650,7 +657,7 @@ impl Fsm for ShapeToolFsmState {
|
||||
};
|
||||
|
||||
match tool_data.current_shape {
|
||||
ShapeType::Polygon | ShapeType::Star | ShapeType::Ellipse | ShapeType::Arc | ShapeType::Rectangle => tool_data.data.start(document, input),
|
||||
ShapeType::Polygon | ShapeType::Star | ShapeType::Circle | ShapeType::Arc | ShapeType::Rectangle | ShapeType::Ellipse => tool_data.data.start(document, input),
|
||||
ShapeType::Line => {
|
||||
let point = SnapCandidatePoint::handle(document.metadata().document_to_viewport.inverse().transform_point2(input.mouse.position));
|
||||
let snapped = tool_data.data.snap_manager.free_snap(&SnapData::new(document, input), &point, SnapTypeConfiguration::default());
|
||||
@@ -663,6 +670,7 @@ impl Fsm for ShapeToolFsmState {
|
||||
let node = match tool_data.current_shape {
|
||||
ShapeType::Polygon => Polygon::create_node(tool_options.vertices),
|
||||
ShapeType::Star => Star::create_node(tool_options.vertices),
|
||||
ShapeType::Circle => Circle::create_node(),
|
||||
ShapeType::Arc => Arc::create_node(tool_options.arc_type),
|
||||
ShapeType::Rectangle => Rectangle::create_node(),
|
||||
ShapeType::Ellipse => Ellipse::create_node(),
|
||||
@@ -675,7 +683,7 @@ impl Fsm for ShapeToolFsmState {
|
||||
let defered_responses = &mut VecDeque::new();
|
||||
|
||||
match tool_data.current_shape {
|
||||
ShapeType::Ellipse | ShapeType::Rectangle | ShapeType::Arc | ShapeType::Polygon | ShapeType::Star => {
|
||||
ShapeType::Polygon | ShapeType::Star | ShapeType::Circle | ShapeType::Arc | ShapeType::Rectangle | ShapeType::Ellipse => {
|
||||
defered_responses.add(GraphOperationMessage::TransformSet {
|
||||
layer,
|
||||
transform: DAffine2::from_scale_angle_translation(DVec2::ONE, 0., input.mouse.position),
|
||||
@@ -698,6 +706,7 @@ impl Fsm for ShapeToolFsmState {
|
||||
responses.add(DeferMessage::AfterGraphRun {
|
||||
messages: defered_responses.drain(..).collect(),
|
||||
});
|
||||
responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||
|
||||
ShapeToolFsmState::Drawing(tool_data.current_shape)
|
||||
}
|
||||
@@ -707,12 +716,13 @@ impl Fsm for ShapeToolFsmState {
|
||||
};
|
||||
|
||||
match tool_data.current_shape {
|
||||
ShapeType::Polygon => Polygon::update_shape(document, input, layer, tool_data, modifier, responses),
|
||||
ShapeType::Star => Star::update_shape(document, input, layer, tool_data, modifier, responses),
|
||||
ShapeType::Circle => Circle::update_shape(document, input, layer, tool_data, modifier, responses),
|
||||
ShapeType::Arc => Arc::update_shape(document, input, layer, tool_data, modifier, responses),
|
||||
ShapeType::Rectangle => Rectangle::update_shape(document, input, layer, tool_data, modifier, responses),
|
||||
ShapeType::Ellipse => Ellipse::update_shape(document, input, layer, tool_data, modifier, responses),
|
||||
ShapeType::Line => Line::update_shape(document, input, layer, tool_data, modifier, responses),
|
||||
ShapeType::Polygon => Polygon::update_shape(document, input, layer, tool_data, modifier, responses),
|
||||
ShapeType::Star => Star::update_shape(document, input, layer, tool_data, modifier, responses),
|
||||
ShapeType::Arc => Arc::update_shape(document, input, layer, tool_data, modifier, responses),
|
||||
}
|
||||
|
||||
// Auto-panning
|
||||
@@ -734,7 +744,7 @@ impl Fsm for ShapeToolFsmState {
|
||||
self
|
||||
}
|
||||
(ShapeToolFsmState::ModifyingGizmo, ShapeToolMessage::PointerMove(..)) => {
|
||||
tool_data.gizmo_manager.handle_update(tool_data.data.drag_start, document, input, responses);
|
||||
tool_data.gizmo_manager.handle_update(tool_data.data.viewport_drag_start(document), document, input, responses);
|
||||
|
||||
responses.add(OverlaysMessage::Draw);
|
||||
|
||||
@@ -892,6 +902,7 @@ impl Fsm for ShapeToolFsmState {
|
||||
tool_data.data.cleanup(responses);
|
||||
tool_data.current_shape = shape;
|
||||
|
||||
responses.add(ShapeToolMessage::UpdateOptions(ShapeOptionsUpdate::ShapeType(shape)));
|
||||
ShapeToolFsmState::Ready(shape)
|
||||
}
|
||||
(_, ShapeToolMessage::HideShapeTypeWidget(hide)) => {
|
||||
@@ -903,85 +914,100 @@ impl Fsm for ShapeToolFsmState {
|
||||
}
|
||||
}
|
||||
|
||||
fn update_hints(&self, responses: &mut VecDeque<Message>) {
|
||||
let hint_data = match self {
|
||||
ShapeToolFsmState::Ready(shape) => {
|
||||
let hint_groups = match shape {
|
||||
ShapeType::Polygon | ShapeType::Star | ShapeType::Arc => vec![
|
||||
HintGroup(vec![
|
||||
HintInfo::mouse(MouseMotion::LmbDrag, "Draw Polygon"),
|
||||
HintInfo::keys([Key::Shift], "Constrain Regular").prepend_plus(),
|
||||
HintInfo::keys([Key::Alt], "From Center").prepend_plus(),
|
||||
]),
|
||||
HintGroup(vec![HintInfo::multi_keys([[Key::BracketLeft], [Key::BracketRight]], "Decrease/Increase Sides")]),
|
||||
],
|
||||
ShapeType::Ellipse => vec![HintGroup(vec![
|
||||
HintInfo::mouse(MouseMotion::LmbDrag, "Draw Ellipse"),
|
||||
HintInfo::keys([Key::Shift], "Constrain Circular").prepend_plus(),
|
||||
HintInfo::keys([Key::Alt], "From Center").prepend_plus(),
|
||||
])],
|
||||
ShapeType::Line => vec![HintGroup(vec![
|
||||
HintInfo::mouse(MouseMotion::LmbDrag, "Draw Line"),
|
||||
HintInfo::keys([Key::Shift], "15° Increments").prepend_plus(),
|
||||
HintInfo::keys([Key::Alt], "From Center").prepend_plus(),
|
||||
HintInfo::keys([Key::Control], "Lock Angle").prepend_plus(),
|
||||
])],
|
||||
ShapeType::Rectangle => vec![HintGroup(vec![
|
||||
HintInfo::mouse(MouseMotion::LmbDrag, "Draw Rectangle"),
|
||||
HintInfo::keys([Key::Shift], "Constrain Square").prepend_plus(),
|
||||
HintInfo::keys([Key::Alt], "From Center").prepend_plus(),
|
||||
])],
|
||||
};
|
||||
HintData(hint_groups)
|
||||
}
|
||||
ShapeToolFsmState::Drawing(shape) => {
|
||||
let mut common_hint_group = vec![HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, ""), HintInfo::keys([Key::Escape], "Cancel").prepend_slash()])];
|
||||
let tool_hint_group = match shape {
|
||||
ShapeType::Polygon | ShapeType::Star | ShapeType::Arc => HintGroup(vec![HintInfo::keys([Key::Shift], "Constrain Regular"), HintInfo::keys([Key::Alt], "From Center")]),
|
||||
ShapeType::Rectangle => HintGroup(vec![HintInfo::keys([Key::Shift], "Constrain Square"), HintInfo::keys([Key::Alt], "From Center")]),
|
||||
ShapeType::Ellipse => HintGroup(vec![HintInfo::keys([Key::Shift], "Constrain Circular"), HintInfo::keys([Key::Alt], "From Center")]),
|
||||
ShapeType::Line => HintGroup(vec![
|
||||
HintInfo::keys([Key::Shift], "15° Increments"),
|
||||
HintInfo::keys([Key::Alt], "From Center"),
|
||||
HintInfo::keys([Key::Control], "Lock Angle"),
|
||||
]),
|
||||
};
|
||||
|
||||
common_hint_group.push(tool_hint_group);
|
||||
|
||||
if matches!(shape, ShapeType::Polygon | ShapeType::Star) {
|
||||
common_hint_group.push(HintGroup(vec![HintInfo::multi_keys([[Key::BracketLeft], [Key::BracketRight]], "Decrease/Increase Sides")]));
|
||||
}
|
||||
|
||||
HintData(common_hint_group)
|
||||
}
|
||||
ShapeToolFsmState::DraggingLineEndpoints => HintData(vec![
|
||||
HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, ""), HintInfo::keys([Key::Escape], "Cancel").prepend_slash()]),
|
||||
HintGroup(vec![
|
||||
HintInfo::keys([Key::Shift], "15° Increments"),
|
||||
HintInfo::keys([Key::Alt], "From Center"),
|
||||
HintInfo::keys([Key::Control], "Lock Angle"),
|
||||
]),
|
||||
]),
|
||||
ShapeToolFsmState::ResizingBounds => HintData(vec![
|
||||
HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, ""), HintInfo::keys([Key::Escape], "Cancel").prepend_slash()]),
|
||||
HintGroup(vec![HintInfo::keys([Key::Alt], "From Pivot"), HintInfo::keys([Key::Shift], "Preserve Aspect Ratio")]),
|
||||
]),
|
||||
ShapeToolFsmState::RotatingBounds => HintData(vec![
|
||||
HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, ""), HintInfo::keys([Key::Escape], "Cancel").prepend_slash()]),
|
||||
HintGroup(vec![HintInfo::keys([Key::Shift], "15° Increments")]),
|
||||
]),
|
||||
ShapeToolFsmState::SkewingBounds { .. } => HintData(vec![
|
||||
HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, ""), HintInfo::keys([Key::Escape], "Cancel").prepend_slash()]),
|
||||
HintGroup(vec![HintInfo::keys([Key::Control], "Unlock Slide")]),
|
||||
]),
|
||||
ShapeToolFsmState::ModifyingGizmo => HintData(vec![HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, ""), HintInfo::keys([Key::Escape], "Cancel").prepend_slash()])]),
|
||||
};
|
||||
|
||||
responses.add(FrontendMessage::UpdateInputHints { hint_data });
|
||||
fn update_hints(&self, _responses: &mut VecDeque<Message>) {
|
||||
// Moved logic to update_dynamic_hints
|
||||
}
|
||||
|
||||
fn update_cursor(&self, responses: &mut VecDeque<Message>) {
|
||||
responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::Crosshair });
|
||||
}
|
||||
}
|
||||
|
||||
fn update_dynamic_hints(state: &ShapeToolFsmState, responses: &mut VecDeque<Message>, tool_data: &ShapeToolData) {
|
||||
let hint_data = match state {
|
||||
ShapeToolFsmState::Ready(_) => {
|
||||
let hint_groups = match tool_data.current_shape {
|
||||
ShapeType::Polygon | ShapeType::Star => vec![
|
||||
HintGroup(vec![
|
||||
HintInfo::mouse(MouseMotion::LmbDrag, "Draw Polygon"),
|
||||
HintInfo::keys([Key::Shift], "Constrain Regular").prepend_plus(),
|
||||
HintInfo::keys([Key::Alt], "From Center").prepend_plus(),
|
||||
]),
|
||||
HintGroup(vec![HintInfo::multi_keys([[Key::BracketLeft], [Key::BracketRight]], "Decrease/Increase Sides")]),
|
||||
],
|
||||
ShapeType::Ellipse => vec![HintGroup(vec![
|
||||
HintInfo::mouse(MouseMotion::LmbDrag, "Draw Ellipse"),
|
||||
HintInfo::keys([Key::Shift], "Constrain Circular").prepend_plus(),
|
||||
HintInfo::keys([Key::Alt], "From Center").prepend_plus(),
|
||||
])],
|
||||
ShapeType::Line => vec![HintGroup(vec![
|
||||
HintInfo::mouse(MouseMotion::LmbDrag, "Draw Line"),
|
||||
HintInfo::keys([Key::Shift], "15° Increments").prepend_plus(),
|
||||
HintInfo::keys([Key::Alt], "From Center").prepend_plus(),
|
||||
HintInfo::keys([Key::Control], "Lock Angle").prepend_plus(),
|
||||
])],
|
||||
ShapeType::Rectangle => vec![HintGroup(vec![
|
||||
HintInfo::mouse(MouseMotion::LmbDrag, "Draw Rectangle"),
|
||||
HintInfo::keys([Key::Shift], "Constrain Square").prepend_plus(),
|
||||
HintInfo::keys([Key::Alt], "From Center").prepend_plus(),
|
||||
])],
|
||||
ShapeType::Circle => vec![HintGroup(vec![
|
||||
HintInfo::mouse(MouseMotion::LmbDrag, "Draw Circle"),
|
||||
HintInfo::keys([Key::Alt], "From Center").prepend_plus(),
|
||||
])],
|
||||
ShapeType::Arc => vec![HintGroup(vec![
|
||||
HintInfo::mouse(MouseMotion::LmbDrag, "Draw Arc"),
|
||||
HintInfo::keys([Key::Shift], "Constrain Arc").prepend_plus(),
|
||||
HintInfo::keys([Key::Alt], "From Center").prepend_plus(),
|
||||
])],
|
||||
};
|
||||
HintData(hint_groups)
|
||||
}
|
||||
ShapeToolFsmState::Drawing(shape) => {
|
||||
let mut common_hint_group = vec![HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, ""), HintInfo::keys([Key::Escape], "Cancel").prepend_slash()])];
|
||||
let tool_hint_group = match shape {
|
||||
ShapeType::Polygon | ShapeType::Star | ShapeType::Arc => HintGroup(vec![HintInfo::keys([Key::Shift], "Constrain Regular"), HintInfo::keys([Key::Alt], "From Center")]),
|
||||
ShapeType::Rectangle => HintGroup(vec![HintInfo::keys([Key::Shift], "Constrain Square"), HintInfo::keys([Key::Alt], "From Center")]),
|
||||
ShapeType::Ellipse => HintGroup(vec![HintInfo::keys([Key::Shift], "Constrain Circular"), HintInfo::keys([Key::Alt], "From Center")]),
|
||||
ShapeType::Line => HintGroup(vec![
|
||||
HintInfo::keys([Key::Shift], "15° Increments"),
|
||||
HintInfo::keys([Key::Alt], "From Center"),
|
||||
HintInfo::keys([Key::Control], "Lock Angle"),
|
||||
]),
|
||||
ShapeType::Circle => HintGroup(vec![HintInfo::keys([Key::Alt], "From Center")]),
|
||||
};
|
||||
|
||||
if !tool_hint_group.0.is_empty() {
|
||||
common_hint_group.push(tool_hint_group);
|
||||
}
|
||||
|
||||
if matches!(shape, ShapeType::Polygon | ShapeType::Star) {
|
||||
common_hint_group.push(HintGroup(vec![HintInfo::multi_keys([[Key::BracketLeft], [Key::BracketRight]], "Decrease/Increase Sides")]));
|
||||
}
|
||||
|
||||
HintData(common_hint_group)
|
||||
}
|
||||
ShapeToolFsmState::DraggingLineEndpoints => HintData(vec![
|
||||
HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, ""), HintInfo::keys([Key::Escape], "Cancel").prepend_slash()]),
|
||||
HintGroup(vec![
|
||||
HintInfo::keys([Key::Shift], "15° Increments"),
|
||||
HintInfo::keys([Key::Alt], "From Center"),
|
||||
HintInfo::keys([Key::Control], "Lock Angle"),
|
||||
]),
|
||||
]),
|
||||
ShapeToolFsmState::ResizingBounds => HintData(vec![
|
||||
HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, ""), HintInfo::keys([Key::Escape], "Cancel").prepend_slash()]),
|
||||
HintGroup(vec![HintInfo::keys([Key::Alt], "From Pivot"), HintInfo::keys([Key::Shift], "Preserve Aspect Ratio")]),
|
||||
]),
|
||||
ShapeToolFsmState::RotatingBounds => HintData(vec![
|
||||
HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, ""), HintInfo::keys([Key::Escape], "Cancel").prepend_slash()]),
|
||||
HintGroup(vec![HintInfo::keys([Key::Shift], "15° Increments")]),
|
||||
]),
|
||||
ShapeToolFsmState::SkewingBounds { .. } => HintData(vec![
|
||||
HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, ""), HintInfo::keys([Key::Escape], "Cancel").prepend_slash()]),
|
||||
HintGroup(vec![HintInfo::keys([Key::Control], "Unlock Slide")]),
|
||||
]),
|
||||
ShapeToolFsmState::ModifyingGizmo => HintData(vec![HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, ""), HintInfo::keys([Key::Escape], "Cancel").prepend_slash()])]),
|
||||
};
|
||||
responses.add(FrontendMessage::UpdateInputHints { hint_data });
|
||||
}
|
||||
|
||||
@@ -548,15 +548,15 @@ mod test_spline_tool {
|
||||
use crate::test_utils::test_prelude::*;
|
||||
use glam::DAffine2;
|
||||
use graphene_std::vector::PointId;
|
||||
use graphene_std::vector::VectorData;
|
||||
use graphene_std::vector::Vector;
|
||||
|
||||
fn assert_point_positions(vector_data: &VectorData, layer_to_viewport: DAffine2, expected_points: &[DVec2], epsilon: f64) {
|
||||
let points_in_viewport: Vec<DVec2> = vector_data
|
||||
fn assert_point_positions(vector: &Vector, layer_to_viewport: DAffine2, expected_points: &[DVec2], epsilon: f64) {
|
||||
let points_in_viewport: Vec<DVec2> = vector
|
||||
.point_domain
|
||||
.ids()
|
||||
.iter()
|
||||
.filter_map(|&point_id| {
|
||||
let position = vector_data.point_domain.position_from_id(point_id)?;
|
||||
let position = vector.point_domain.position_from_id(point_id)?;
|
||||
Some(layer_to_viewport.transform_point2(position))
|
||||
})
|
||||
.collect();
|
||||
@@ -601,19 +601,19 @@ mod test_spline_tool {
|
||||
|
||||
let first_spline_node = find_spline(document, spline_layer).expect("Spline node not found in the layer");
|
||||
|
||||
let first_vector_data = document.network_interface.compute_modified_vector(spline_layer).expect("Vector data not found for the spline layer");
|
||||
let first_vector = document.network_interface.compute_modified_vector(spline_layer).expect("Vector not found for the spline layer");
|
||||
|
||||
// Verify initial spline has correct number of points and segments
|
||||
let initial_point_count = first_vector_data.point_domain.ids().len();
|
||||
let initial_segment_count = first_vector_data.segment_domain.ids().len();
|
||||
let initial_point_count = first_vector.point_domain.ids().len();
|
||||
let initial_segment_count = first_vector.segment_domain.ids().len();
|
||||
assert_eq!(initial_point_count, 3, "Expected 3 points in initial spline, found {}", initial_point_count);
|
||||
assert_eq!(initial_segment_count, 2, "Expected 2 segments in initial spline, found {}", initial_segment_count);
|
||||
|
||||
let layer_to_viewport = document.metadata().transform_to_viewport(spline_layer);
|
||||
|
||||
let endpoints: Vec<(PointId, DVec2)> = first_vector_data
|
||||
let endpoints: Vec<(PointId, DVec2)> = first_vector
|
||||
.extendable_points(false)
|
||||
.filter_map(|point_id| first_vector_data.point_domain.position_from_id(point_id).map(|pos| (point_id, layer_to_viewport.transform_point2(pos))))
|
||||
.filter_map(|point_id| first_vector.point_domain.position_from_id(point_id).map(|pos| (point_id, layer_to_viewport.transform_point2(pos))))
|
||||
.collect();
|
||||
|
||||
assert_eq!(endpoints.len(), 2, "Expected 2 endpoints in the initial spline");
|
||||
@@ -632,14 +632,14 @@ mod test_spline_tool {
|
||||
editor.press(Key::Enter, ModifierKeys::empty()).await;
|
||||
|
||||
let document = editor.active_document();
|
||||
let extended_vector_data = document
|
||||
let extended_vector = document
|
||||
.network_interface
|
||||
.compute_modified_vector(spline_layer)
|
||||
.expect("Vector data not found for the extended spline layer");
|
||||
.expect("Vector not found for the extended spline layer");
|
||||
|
||||
// Verify extended spline has correct number of points and segments
|
||||
let extended_point_count = extended_vector_data.point_domain.ids().len();
|
||||
let extended_segment_count = extended_vector_data.segment_domain.ids().len();
|
||||
let extended_point_count = extended_vector.point_domain.ids().len();
|
||||
let extended_segment_count = extended_vector.segment_domain.ids().len();
|
||||
|
||||
assert_eq!(extended_point_count, 5, "Expected 5 points in extended spline, found {}", extended_point_count);
|
||||
assert_eq!(extended_segment_count, 4, "Expected 4 segments in extended spline, found {}", extended_segment_count);
|
||||
@@ -653,7 +653,7 @@ mod test_spline_tool {
|
||||
|
||||
let all_expected_points = [initial_points[0], initial_points[1], initial_points[2], continuation_points[0], continuation_points[1]];
|
||||
|
||||
assert_point_positions(&extended_vector_data, layer_to_viewport, &all_expected_points, 1e-10);
|
||||
assert_point_positions(&extended_vector, layer_to_viewport, &all_expected_points, 1e-10);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -688,14 +688,14 @@ mod test_spline_tool {
|
||||
.selected_visible_and_unlocked_layers(network_interface)
|
||||
.next()
|
||||
.expect("Should have a selected layer");
|
||||
let vector_data = network_interface.compute_modified_vector(layer).expect("Should have vector data");
|
||||
let vector = network_interface.compute_modified_vector(layer).expect("Should have vector data");
|
||||
let layer_to_viewport = document.metadata().transform_to_viewport(layer);
|
||||
|
||||
// Expected points in viewport coordinates
|
||||
let expected_points = vec![DVec2::new(50., 50.), DVec2::new(100., 50.), DVec2::new(150., 100.)];
|
||||
|
||||
// Assert all points are correctly positioned
|
||||
assert_point_positions(&vector_data, layer_to_viewport, &expected_points, 1e-10);
|
||||
assert_point_positions(&vector, layer_to_viewport, &expected_points, 1e-10);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -728,14 +728,14 @@ mod test_spline_tool {
|
||||
.selected_visible_and_unlocked_layers(network_interface)
|
||||
.next()
|
||||
.expect("Should have a selected layer");
|
||||
let vector_data = network_interface.compute_modified_vector(layer).expect("Should have vector data");
|
||||
let vector = network_interface.compute_modified_vector(layer).expect("Should have vector data");
|
||||
let layer_to_viewport = document.metadata().transform_to_viewport(layer);
|
||||
|
||||
// Expected points in viewport coordinates
|
||||
let expected_points = vec![DVec2::new(50., 50.), DVec2::new(100., 50.), DVec2::new(150., 100.)];
|
||||
|
||||
// Assert all points are correctly positioned
|
||||
assert_point_positions(&vector_data, layer_to_viewport, &expected_points, 1e-10);
|
||||
assert_point_positions(&vector, layer_to_viewport, &expected_points, 1e-10);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -766,14 +766,14 @@ mod test_spline_tool {
|
||||
.selected_visible_and_unlocked_layers(network_interface)
|
||||
.next()
|
||||
.expect("Should have a selected layer");
|
||||
let vector_data = network_interface.compute_modified_vector(layer).expect("Should have vector data");
|
||||
let vector = network_interface.compute_modified_vector(layer).expect("Should have vector data");
|
||||
let layer_to_viewport = document.metadata().transform_to_viewport(layer);
|
||||
|
||||
// Expected points in viewport coordinates
|
||||
let expected_points = vec![DVec2::new(50., 50.), DVec2::new(100., 50.), DVec2::new(150., 100.)];
|
||||
|
||||
// Assert all points are correctly positioned
|
||||
assert_point_positions(&vector_data, layer_to_viewport, &expected_points, 1e-10);
|
||||
assert_point_positions(&vector, layer_to_viewport, &expected_points, 1e-10);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -805,14 +805,14 @@ mod test_spline_tool {
|
||||
.selected_visible_and_unlocked_layers(network_interface)
|
||||
.next()
|
||||
.expect("Should have a selected layer");
|
||||
let vector_data = network_interface.compute_modified_vector(layer).expect("Should have vector data");
|
||||
let vector = network_interface.compute_modified_vector(layer).expect("Should have vector data");
|
||||
let layer_to_viewport = document.metadata().transform_to_viewport(layer);
|
||||
|
||||
// Expected points in viewport coordinates
|
||||
let expected_points = vec![DVec2::new(50., 50.), DVec2::new(100., 50.), DVec2::new(150., 100.)];
|
||||
|
||||
// Assert all points are correctly positioned
|
||||
assert_point_positions(&vector_data, layer_to_viewport, &expected_points, 1e-10);
|
||||
assert_point_positions(&vector, layer_to_viewport, &expected_points, 1e-10);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -845,17 +845,17 @@ mod test_spline_tool {
|
||||
let spline_layer = layers.next().expect("Failed to find the spline layer");
|
||||
assert!(find_spline(document, spline_layer).is_some(), "Spline node not found in the layer");
|
||||
|
||||
let vector_data = document.network_interface.compute_modified_vector(spline_layer).expect("Vector data not found for the spline layer");
|
||||
let vector = document.network_interface.compute_modified_vector(spline_layer).expect("Vector not found for the spline layer");
|
||||
|
||||
// Verify we have the correct number of points and segments
|
||||
let point_count = vector_data.point_domain.ids().len();
|
||||
let segment_count = vector_data.segment_domain.ids().len();
|
||||
let point_count = vector.point_domain.ids().len();
|
||||
let segment_count = vector.segment_domain.ids().len();
|
||||
|
||||
assert_eq!(point_count, 3, "Expected 3 points in the spline, found {}", point_count);
|
||||
assert_eq!(segment_count, 2, "Expected 2 segments in the spline, found {}", segment_count);
|
||||
|
||||
let layer_to_viewport = document.metadata().transform_to_viewport(spline_layer);
|
||||
|
||||
assert_point_positions(&vector_data, layer_to_viewport, &spline_points, 1e-10);
|
||||
assert_point_positions(&vector, layer_to_viewport, &spline_points, 1e-10);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -385,25 +385,19 @@ impl TextToolData {
|
||||
parent: document.new_layer_parent(true),
|
||||
insert_index: 0,
|
||||
});
|
||||
responses.add(DeferMessage::AfterGraphRun {
|
||||
messages: vec![
|
||||
GraphOperationMessage::FillSet {
|
||||
layer: self.layer,
|
||||
fill: if editing_text.color.is_some() {
|
||||
Fill::Solid(editing_text.color.unwrap().to_gamma_srgb())
|
||||
} else {
|
||||
Fill::None
|
||||
},
|
||||
}
|
||||
.into(),
|
||||
GraphOperationMessage::TransformSet {
|
||||
layer: self.layer,
|
||||
transform: editing_text.transform,
|
||||
transform_in: TransformIn::Viewport,
|
||||
skip_rerender: true,
|
||||
}
|
||||
.into(),
|
||||
],
|
||||
responses.add(GraphOperationMessage::FillSet {
|
||||
layer: self.layer,
|
||||
fill: if editing_text.color.is_some() {
|
||||
Fill::Solid(editing_text.color.unwrap().to_gamma_srgb())
|
||||
} else {
|
||||
Fill::None
|
||||
},
|
||||
});
|
||||
responses.add(GraphOperationMessage::TransformSet {
|
||||
layer: self.layer,
|
||||
transform: editing_text.transform,
|
||||
transform_in: TransformIn::Viewport,
|
||||
skip_rerender: true,
|
||||
});
|
||||
self.editing_text = Some(editing_text);
|
||||
|
||||
|
||||
@@ -11,9 +11,9 @@ use crate::messages::tool::tool_messages::tool_prelude::Key;
|
||||
use crate::messages::tool::utility_types::{ToolData, ToolType};
|
||||
use glam::{DAffine2, DVec2};
|
||||
use graphene_std::renderer::Quad;
|
||||
use graphene_std::vector::ManipulatorPointId;
|
||||
use graphene_std::vector::click_target::ClickTargetType;
|
||||
use graphene_std::vector::{VectorData, VectorModificationType};
|
||||
use graphene_std::vector::misc::ManipulatorPointId;
|
||||
use graphene_std::vector::{Vector, VectorModificationType};
|
||||
use std::f64::consts::{PI, TAU};
|
||||
|
||||
const TRANSFORM_GRS_OVERLAY_PROVIDER: OverlayProvider = |context| TransformLayerMessage::Overlays(context).into();
|
||||
@@ -123,14 +123,14 @@ impl MessageHandler<TransformLayerMessage, TransformLayerMessageContext<'_>> for
|
||||
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)) {
|
||||
else if let Some(vector) = 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() {
|
||||
for (segment_id, _, start, end) in vector.segment_bezier_iter() {
|
||||
if selected_segments.contains(&segment_id) {
|
||||
affected_points.push(ManipulatorPointId::Anchor(start));
|
||||
affected_points.push(ManipulatorPointId::Anchor(end));
|
||||
@@ -139,11 +139,11 @@ impl MessageHandler<TransformLayerMessage, TransformLayerMessageContext<'_>> for
|
||||
|
||||
let affected_point_refs = affected_points.iter().collect();
|
||||
|
||||
let get_location = |point: &&ManipulatorPointId| point.get_position(&vector_data).map(|position| viewspace.transform_point2(position));
|
||||
let get_location = |point: &&ManipulatorPointId| point.get_position(&vector).map(|position| viewspace.transform_point2(position));
|
||||
if let (Some((new_pivot, grab_target)), bounds) = calculate_pivot(
|
||||
document,
|
||||
&affected_point_refs,
|
||||
&vector_data,
|
||||
&vector,
|
||||
viewspace,
|
||||
|point: &ManipulatorPointId| get_location(&point),
|
||||
&mut self.pivot_gizmo,
|
||||
@@ -362,12 +362,12 @@ impl MessageHandler<TransformLayerMessage, TransformLayerMessageContext<'_>> for
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(vector_data) = selected_layers.first().and_then(|&layer| document.network_interface.compute_modified_vector(layer)) {
|
||||
if let Some(vector) = selected_layers.first().and_then(|&layer| document.network_interface.compute_modified_vector(layer)) {
|
||||
if let [point] = selected_points.as_slice() {
|
||||
if matches!(point, ManipulatorPointId::Anchor(_)) {
|
||||
if let Some([handle1, handle2]) = point.get_handle_pair(&vector_data) {
|
||||
let handle1_length = handle1.length(&vector_data);
|
||||
let handle2_length = handle2.length(&vector_data);
|
||||
if let Some([handle1, handle2]) = point.get_handle_pair(&vector) {
|
||||
let handle1_length = handle1.length(&vector);
|
||||
let handle2_length = handle2.length(&vector);
|
||||
|
||||
if (handle1_length == 0. && handle2_length == 0. && !using_select_tool) || (handle1_length == f64::MAX && handle2_length == f64::MAX && !using_select_tool) {
|
||||
// G should work for this point but not R and S
|
||||
@@ -378,7 +378,7 @@ impl MessageHandler<TransformLayerMessage, TransformLayerMessageContext<'_>> for
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let handle_length = point.as_handle().map(|handle| handle.length(&vector_data));
|
||||
let handle_length = point.as_handle().map(|handle| handle.length(&vector));
|
||||
|
||||
if handle_length == Some(0.) {
|
||||
selected.original_transforms.clear();
|
||||
@@ -693,7 +693,7 @@ impl TransformLayerMessageHandler {
|
||||
fn calculate_pivot(
|
||||
document: &DocumentMessageHandler,
|
||||
selected_points: &Vec<&ManipulatorPointId>,
|
||||
vector_data: &VectorData,
|
||||
vector: &Vector,
|
||||
viewspace: DAffine2,
|
||||
get_location: impl Fn(&ManipulatorPointId) -> Option<DVec2>,
|
||||
gizmo: &mut PivotGizmo,
|
||||
@@ -736,8 +736,8 @@ fn calculate_pivot(
|
||||
ManipulatorPointId::PrimaryHandle(_) | ManipulatorPointId::EndHandle(_) => {
|
||||
// Get the anchor position and transform it to the pivot
|
||||
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),
|
||||
point.get_anchor_position(vector).map(|anchor_position| viewspace.transform_point2(anchor_position)),
|
||||
point.get_position(vector),
|
||||
) else {
|
||||
return (None, None);
|
||||
};
|
||||
@@ -774,15 +774,15 @@ fn project_edge_to_quad(edge: DVec2, quad: &Quad, local: bool, axis_constraint:
|
||||
|
||||
fn update_colinear_handles(selected_layers: &[LayerNodeIdentifier], document: &DocumentMessageHandler, responses: &mut VecDeque<Message>) {
|
||||
for &layer in selected_layers {
|
||||
let Some(vector_data) = document.network_interface.compute_modified_vector(layer) else { continue };
|
||||
let Some(vector) = document.network_interface.compute_modified_vector(layer) else { continue };
|
||||
|
||||
for [handle1, handle2] in &vector_data.colinear_manipulators {
|
||||
for [handle1, handle2] in &vector.colinear_manipulators {
|
||||
let manipulator1 = handle1.to_manipulator_point();
|
||||
let manipulator2 = handle2.to_manipulator_point();
|
||||
|
||||
let Some(anchor) = manipulator1.get_anchor_position(&vector_data) else { continue };
|
||||
let Some(pos1) = manipulator1.get_position(&vector_data).map(|pos| pos - anchor) else { continue };
|
||||
let Some(pos2) = manipulator2.get_position(&vector_data).map(|pos| pos - anchor) else { continue };
|
||||
let Some(anchor) = manipulator1.get_anchor_position(&vector) else { continue };
|
||||
let Some(pos1) = manipulator1.get_position(&vector).map(|pos| pos - anchor) else { continue };
|
||||
let Some(pos2) = manipulator2.get_position(&vector).map(|pos| pos - anchor) else { continue };
|
||||
|
||||
let angle = pos1.angle_to(pos2);
|
||||
|
||||
|
||||
@@ -9,11 +9,11 @@ use graph_craft::wasm_application_io::EditorPreferences;
|
||||
use graphene_std::application_io::TimingInformation;
|
||||
use graphene_std::application_io::{NodeGraphUpdateMessage, RenderConfig};
|
||||
use graphene_std::renderer::RenderSvgSegmentList;
|
||||
use graphene_std::renderer::{GraphicElementRendered, RenderParams, SvgRender};
|
||||
use graphene_std::renderer::{Render, RenderParams, SvgRender};
|
||||
use graphene_std::renderer::{RenderMetadata, format_transform_matrix};
|
||||
use graphene_std::text::FontCache;
|
||||
use graphene_std::transform::Footprint;
|
||||
use graphene_std::vector::VectorData;
|
||||
use graphene_std::vector::Vector;
|
||||
use graphene_std::vector::style::ViewMode;
|
||||
use interpreted_executor::dynamic_executor::ResolvedDocumentNodeTypesDelta;
|
||||
|
||||
@@ -34,7 +34,7 @@ pub struct ExecutionResponse {
|
||||
result: Result<TaggedValue, String>,
|
||||
responses: VecDeque<FrontendMessage>,
|
||||
transform: DAffine2,
|
||||
vector_modify: HashMap<NodeId, VectorData>,
|
||||
vector_modify: HashMap<NodeId, Vector>,
|
||||
/// The resulting value from the temporary inspected during execution
|
||||
inspect_result: Option<InspectResult>,
|
||||
}
|
||||
@@ -63,6 +63,7 @@ pub struct NodeGraphExecutor {
|
||||
#[derive(Debug, Clone)]
|
||||
struct ExecutionContext {
|
||||
export_config: Option<ExportConfig>,
|
||||
document_id: DocumentId,
|
||||
}
|
||||
|
||||
impl NodeGraphExecutor {
|
||||
@@ -133,7 +134,13 @@ impl NodeGraphExecutor {
|
||||
}
|
||||
|
||||
/// Adds an evaluate request for whatever current network is cached.
|
||||
pub(crate) fn submit_current_node_graph_evaluation(&mut self, document: &mut DocumentMessageHandler, viewport_resolution: UVec2, time: TimingInformation) -> Result<Message, String> {
|
||||
pub(crate) fn submit_current_node_graph_evaluation(
|
||||
&mut self,
|
||||
document: &mut DocumentMessageHandler,
|
||||
document_id: DocumentId,
|
||||
viewport_resolution: UVec2,
|
||||
time: TimingInformation,
|
||||
) -> Result<Message, String> {
|
||||
let render_config = RenderConfig {
|
||||
viewport: Footprint {
|
||||
transform: document.metadata().document_to_viewport,
|
||||
@@ -153,7 +160,7 @@ impl NodeGraphExecutor {
|
||||
// Execute the node graph
|
||||
let execution_id = self.queue_execution(render_config);
|
||||
|
||||
self.futures.insert(execution_id, ExecutionContext { export_config: None });
|
||||
self.futures.insert(execution_id, ExecutionContext { export_config: None, document_id });
|
||||
|
||||
Ok(DeferMessage::SetGraphSubmissionIndex(execution_id).into())
|
||||
}
|
||||
@@ -162,17 +169,18 @@ impl NodeGraphExecutor {
|
||||
pub fn submit_node_graph_evaluation(
|
||||
&mut self,
|
||||
document: &mut DocumentMessageHandler,
|
||||
document_id: DocumentId,
|
||||
viewport_resolution: UVec2,
|
||||
time: TimingInformation,
|
||||
inspect_node: Option<NodeId>,
|
||||
ignore_hash: bool,
|
||||
) -> Result<Message, String> {
|
||||
self.update_node_graph(document, inspect_node, ignore_hash)?;
|
||||
self.submit_current_node_graph_evaluation(document, viewport_resolution, time)
|
||||
self.submit_current_node_graph_evaluation(document, document_id, viewport_resolution, time)
|
||||
}
|
||||
|
||||
/// Evaluates a node graph for export
|
||||
pub fn submit_document_export(&mut self, document: &mut DocumentMessageHandler, mut export_config: ExportConfig) -> Result<(), String> {
|
||||
pub fn submit_document_export(&mut self, document: &mut DocumentMessageHandler, document_id: DocumentId, mut export_config: ExportConfig) -> Result<(), String> {
|
||||
let network = document.network_interface.document_network().clone();
|
||||
|
||||
// Calculate the bounding box of the region to be exported
|
||||
@@ -204,7 +212,10 @@ impl NodeGraphExecutor {
|
||||
.send(GraphRuntimeRequest::GraphUpdate(GraphUpdate { network, inspect_node: None }))
|
||||
.map_err(|e| e.to_string())?;
|
||||
let execution_id = self.queue_execution(render_config);
|
||||
let execution_context = ExecutionContext { export_config: Some(export_config) };
|
||||
let execution_context = ExecutionContext {
|
||||
export_config: Some(export_config),
|
||||
document_id,
|
||||
};
|
||||
self.futures.insert(execution_id, execution_context);
|
||||
|
||||
Ok(())
|
||||
@@ -234,11 +245,11 @@ impl NodeGraphExecutor {
|
||||
};
|
||||
|
||||
if file_type == FileType::Svg {
|
||||
responses.add(FrontendMessage::TriggerDownloadTextFile { document: svg, name });
|
||||
responses.add(FrontendMessage::TriggerSaveFile { name, content: svg.into_bytes() });
|
||||
} else {
|
||||
let mime = file_type.to_mime().to_string();
|
||||
let size = (size * scale_factor).into();
|
||||
responses.add(FrontendMessage::TriggerDownloadImage { svg, name, mime, size });
|
||||
responses.add(FrontendMessage::TriggerExportImage { svg, name, mime, size });
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -279,7 +290,7 @@ impl NodeGraphExecutor {
|
||||
} else {
|
||||
self.process_node_graph_output(node_graph_output, transform, responses)?
|
||||
}
|
||||
responses.add(DeferMessage::TriggerGraphRun(execution_id));
|
||||
responses.add_front(DeferMessage::TriggerGraphRun(execution_id, execution_context.document_id));
|
||||
|
||||
// Update the spreadsheet on the frontend using the value of the inspect result.
|
||||
if self.old_inspect_node.is_some() {
|
||||
@@ -321,7 +332,7 @@ impl NodeGraphExecutor {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn debug_render(render_object: impl GraphicElementRendered, transform: DAffine2, responses: &mut VecDeque<Message>) {
|
||||
fn debug_render(render_object: impl Render, transform: DAffine2, responses: &mut VecDeque<Message>) {
|
||||
// Setup rendering
|
||||
let mut render = SvgRender::new();
|
||||
let render_params = RenderParams {
|
||||
@@ -377,9 +388,9 @@ impl NodeGraphExecutor {
|
||||
TaggedValue::F64(render_object) => Self::debug_render(render_object, transform, responses),
|
||||
TaggedValue::DVec2(render_object) => Self::debug_render(render_object, transform, responses),
|
||||
TaggedValue::OptionalColor(render_object) => Self::debug_render(render_object, transform, responses),
|
||||
TaggedValue::VectorData(render_object) => Self::debug_render(render_object, transform, responses),
|
||||
TaggedValue::GraphicGroup(render_object) => Self::debug_render(render_object, transform, responses),
|
||||
TaggedValue::RasterData(render_object) => Self::debug_render(render_object, transform, responses),
|
||||
TaggedValue::Vector(render_object) => Self::debug_render(render_object, transform, responses),
|
||||
TaggedValue::Graphic(render_object) => Self::debug_render(render_object, transform, responses),
|
||||
TaggedValue::Raster(render_object) => Self::debug_render(render_object, transform, responses),
|
||||
TaggedValue::Palette(render_object) => Self::debug_render(render_object, transform, responses),
|
||||
_ => {
|
||||
return Err(format!("Invalid node graph output type: {node_graph_output:#?}"));
|
||||
@@ -388,7 +399,7 @@ impl NodeGraphExecutor {
|
||||
let graphene_std::renderer::RenderMetadata {
|
||||
upstream_footprints: footprints,
|
||||
local_transforms,
|
||||
first_instance_source_id,
|
||||
first_element_source_id,
|
||||
click_targets,
|
||||
clip_targets,
|
||||
} = render_output_metadata;
|
||||
@@ -397,7 +408,7 @@ impl NodeGraphExecutor {
|
||||
responses.add(DocumentMessage::UpdateUpstreamTransforms {
|
||||
upstream_footprints: footprints,
|
||||
local_transforms,
|
||||
first_instance_source_id,
|
||||
first_element_source_id,
|
||||
});
|
||||
responses.add(DocumentMessage::UpdateClickTargets { click_targets });
|
||||
responses.add(DocumentMessage::UpdateClipTargets { clip_targets });
|
||||
|
||||
@@ -7,16 +7,16 @@ 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::{ImageTexture, NodeGraphUpdateMessage, NodeGraphUpdateSender, RenderConfig};
|
||||
use graphene_std::instances::Instance;
|
||||
use graphene_std::memo::IORecord;
|
||||
use graphene_std::renderer::{GraphicElementRendered, RenderParams, SvgRender};
|
||||
use graphene_std::renderer::{Render, RenderParams, SvgRender};
|
||||
use graphene_std::renderer::{RenderSvgSegmentList, SvgSegment};
|
||||
use graphene_std::table::{Table, TableRow};
|
||||
use graphene_std::text::FontCache;
|
||||
use graphene_std::vector::Vector;
|
||||
use graphene_std::vector::style::ViewMode;
|
||||
use graphene_std::vector::{VectorData, VectorDataTable};
|
||||
use graphene_std::wasm_application_io::{RenderOutputType, WasmApplicationIo, WasmEditorApi};
|
||||
use graphene_std::{Artboard, Context, Graphic};
|
||||
use interpreted_executor::dynamic_executor::{DynamicExecutor, IntrospectError, ResolvedDocumentNodeTypesDelta};
|
||||
use interpreted_executor::util::wrap_network_in_scope;
|
||||
use once_cell::sync::Lazy;
|
||||
@@ -51,7 +51,7 @@ pub struct NodeRuntime {
|
||||
// TODO: Remove, it doesn't need to be persisted anymore
|
||||
/// The current renders of the thumbnails for layer nodes.
|
||||
thumbnail_renders: HashMap<NodeId, Vec<SvgSegment>>,
|
||||
vector_modify: HashMap<NodeId, VectorData>,
|
||||
vector_modify: HashMap<NodeId, Vector>,
|
||||
}
|
||||
|
||||
/// Messages passed from the editor thread to the node runtime thread.
|
||||
@@ -134,9 +134,9 @@ impl NodeRuntime {
|
||||
pub async fn run(&mut self) -> Option<ImageTexture> {
|
||||
if self.editor_api.application_io.is_none() {
|
||||
self.editor_api = WasmEditorApi {
|
||||
#[cfg(all(not(test), target_arch = "wasm32"))]
|
||||
#[cfg(all(not(test), target_family = "wasm"))]
|
||||
application_io: Some(WasmApplicationIo::new().await.into()),
|
||||
#[cfg(any(test, not(target_arch = "wasm32")))]
|
||||
#[cfg(any(test, not(target_family = "wasm")))]
|
||||
application_io: Some(WasmApplicationIo::new_offscreen().await.into()),
|
||||
font_cache: self.editor_api.font_cache.clone(),
|
||||
node_graph_message_sender: Box::new(self.sender.clone()),
|
||||
@@ -299,7 +299,7 @@ impl NodeRuntime {
|
||||
continue;
|
||||
};
|
||||
|
||||
// Extract the monitor node's stored `GraphicElement` data.
|
||||
// Extract the monitor node's stored `Graphic` data.
|
||||
let Ok(introspected_data) = self.executor.introspect(monitor_node_path) else {
|
||||
// TODO: Fix the root of the issue causing the spam of this warning (this at least temporarily disables it in release builds)
|
||||
#[cfg(debug_assertions)]
|
||||
@@ -308,29 +308,26 @@ impl NodeRuntime {
|
||||
continue;
|
||||
};
|
||||
|
||||
if let Some(io) = introspected_data.downcast_ref::<IORecord<Context, graphene_std::GraphicElement>>() {
|
||||
Self::process_graphic_element(&mut self.thumbnail_renders, parent_network_node_id, &io.output, responses, update_thumbnails)
|
||||
} else if let Some(io) = introspected_data.downcast_ref::<IORecord<Context, graphene_std::Artboard>>() {
|
||||
Self::process_graphic_element(&mut self.thumbnail_renders, parent_network_node_id, &io.output, responses, update_thumbnails)
|
||||
if let Some(io) = introspected_data.downcast_ref::<IORecord<Context, Table<Graphic>>>() {
|
||||
Self::process_graphic(&mut self.thumbnail_renders, parent_network_node_id, &io.output, responses, update_thumbnails)
|
||||
} else if let Some(io) = introspected_data.downcast_ref::<IORecord<Context, Table<Artboard>>>() {
|
||||
Self::process_graphic(&mut self.thumbnail_renders, parent_network_node_id, &io.output, responses, update_thumbnails)
|
||||
// Insert the vector modify if we are dealing with vector data
|
||||
} else if let Some(record) = introspected_data.downcast_ref::<IORecord<Context, VectorDataTable>>() {
|
||||
let default = Instance::default();
|
||||
self.vector_modify.insert(
|
||||
parent_network_node_id,
|
||||
record.output.instance_ref_iter().next().unwrap_or_else(|| default.to_instance_ref()).instance.clone(),
|
||||
);
|
||||
} else if let Some(record) = introspected_data.downcast_ref::<IORecord<Context, Table<Vector>>>() {
|
||||
let default = TableRow::default();
|
||||
self.vector_modify
|
||||
.insert(parent_network_node_id, record.output.iter().next().unwrap_or_else(|| default.as_ref()).element.clone());
|
||||
} else {
|
||||
log::warn!("Failed to downcast monitor node output {parent_network_node_id:?}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If this is `GraphicElement` data:
|
||||
// Regenerate click targets and thumbnails for the layers in the graph, modifying the state and updating the UI.
|
||||
fn process_graphic_element(
|
||||
// If this is `Graphic` data, regenerate click targets and thumbnails for the layers in the graph, modifying the state and updating the UI.
|
||||
fn process_graphic(
|
||||
thumbnail_renders: &mut HashMap<NodeId, Vec<SvgSegment>>,
|
||||
parent_network_node_id: NodeId,
|
||||
graphic_element: &impl GraphicElementRendered,
|
||||
graphic: &impl Render,
|
||||
responses: &mut VecDeque<FrontendMessage>,
|
||||
update_thumbnails: bool,
|
||||
) {
|
||||
@@ -341,7 +338,7 @@ impl NodeRuntime {
|
||||
}
|
||||
|
||||
// Skip thumbnails if the layer is too complex (for performance)
|
||||
if graphic_element.render_complexity() > 1000 {
|
||||
if graphic.render_complexity() > 1000 {
|
||||
let old = thumbnail_renders.insert(parent_network_node_id, Vec::new());
|
||||
if old.is_none_or(|v| !v.is_empty()) {
|
||||
responses.push_back(FrontendMessage::UpdateNodeThumbnail {
|
||||
@@ -352,9 +349,9 @@ impl NodeRuntime {
|
||||
return;
|
||||
}
|
||||
|
||||
let bounds = graphic_element.bounding_box(DAffine2::IDENTITY, true);
|
||||
let bounds = graphic.bounding_box(DAffine2::IDENTITY, true);
|
||||
|
||||
// Render the thumbnail from a `GraphicElement` into an SVG string
|
||||
// Render the thumbnail from a `Graphic` into an SVG string
|
||||
let render_params = RenderParams {
|
||||
view_mode: ViewMode::Normal,
|
||||
culling_bounds: bounds,
|
||||
@@ -365,7 +362,7 @@ impl NodeRuntime {
|
||||
alignment_parent_transform: None,
|
||||
};
|
||||
let mut render = SvgRender::new();
|
||||
graphic_element.render_svg(&mut render, &render_params);
|
||||
graphic.render_svg(&mut render, &render_params);
|
||||
|
||||
// And give the SVG a viewbox and outer <svg>...</svg> wrapper tag
|
||||
let [min, max] = bounds.unwrap_or_default();
|
||||
|
||||
@@ -49,7 +49,7 @@ impl EditorTestUtils {
|
||||
};
|
||||
|
||||
let viewport_resolution = glam::UVec2::ONE;
|
||||
if let Err(e) = exector.submit_current_node_graph_evaluation(document, viewport_resolution, Default::default()) {
|
||||
if let Err(e) = exector.submit_current_node_graph_evaluation(document, DocumentId(0), viewport_resolution, Default::default()) {
|
||||
return Err(format!("submit_current_node_graph_evaluation failed\n\n{e}"));
|
||||
}
|
||||
runtime.run().await;
|
||||
|
||||
Reference in New Issue
Block a user