Merge branch 'master' into merge_point

This commit is contained in:
Adesh Gupta
2025-08-08 11:54:55 +05:30
committed by GitHub
200 changed files with 8963 additions and 5995 deletions
+1
View File
@@ -46,6 +46,7 @@ once_cell = { workspace = true }
web-sys = { workspace = true }
bytemuck = { workspace = true }
vello = { workspace = true }
tracing = { workspace = true }
# Required dependencies
spin = "0.9.8"
+2 -1
View File
@@ -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;
+6 -1
View File
@@ -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);
}
+1 -1
View File
@@ -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() {
+1 -1
View File
@@ -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};
@@ -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(),
];
@@ -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,
},
@@ -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);
@@ -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
}
@@ -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;
@@ -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 };
@@ -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);
}
}
}
@@ -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
+110 -120
View File
@@ -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);
+28 -17
View File
@@ -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 });
+23 -26
View File
@@ -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();
+1 -1
View File
@@ -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;