Fix misc errors and cleanup after rebases

This commit is contained in:
Adam
2025-09-09 16:53:07 -07:00
parent c994cdeced
commit 4c92c48cc3
25 changed files with 206 additions and 436 deletions
+1
View File
@@ -50,6 +50,7 @@ impl Editor {
open: active_document.graph_view_overlay_open, open: active_document.graph_view_overlay_open,
in_selected_network: &active_document.selection_network_path == breadcrumb_network_path, in_selected_network: &active_document.selection_network_path == breadcrumb_network_path,
previewed_node, previewed_node,
thumbnails: active_document.node_graph_handler.thumbnails.clone()
}; };
let opacity = active_document.graph_fade_artwork_percentage; let opacity = active_document.graph_fade_artwork_percentage;
let node_graph_overlay_node = generate_node_graph_overlay(node_graph_render_data, opacity); let node_graph_overlay_node = generate_node_graph_overlay(node_graph_render_data, opacity);
+1 -1
View File
@@ -4,7 +4,7 @@ pub const EXPORTS_TO_TOP_EDGE_PIXEL_GAP: u32 = 72;
pub const EXPORTS_TO_RIGHT_EDGE_PIXEL_GAP: u32 = 120; pub const EXPORTS_TO_RIGHT_EDGE_PIXEL_GAP: u32 = 120;
pub const IMPORTS_TO_TOP_EDGE_PIXEL_GAP: u32 = 72; pub const IMPORTS_TO_TOP_EDGE_PIXEL_GAP: u32 = 72;
pub const IMPORTS_TO_LEFT_EDGE_PIXEL_GAP: u32 = 120; pub const IMPORTS_TO_LEFT_EDGE_PIXEL_GAP: u32 = 120;
pub const TOOLTIP_DELAY: u32 = 800; pub const INPUT_TOOLTIP_DELAY: u64 = 800;
// VIEWPORT // VIEWPORT
pub const VIEWPORT_ZOOM_WHEEL_RATE: f64 = (1. / 600.) * 3.; pub const VIEWPORT_ZOOM_WHEEL_RATE: f64 = (1. / 600.) * 3.;
+1
View File
@@ -59,6 +59,7 @@ const SIDE_EFFECT_FREE_MESSAGES: &[MessageDiscriminant] = &[
const DEBUG_MESSAGE_BLOCK_LIST: &[MessageDiscriminant] = &[ const DEBUG_MESSAGE_BLOCK_LIST: &[MessageDiscriminant] = &[
MessageDiscriminant::Broadcast(BroadcastMessageDiscriminant::TriggerEvent(EventMessageDiscriminant::AnimationFrame)), MessageDiscriminant::Broadcast(BroadcastMessageDiscriminant::TriggerEvent(EventMessageDiscriminant::AnimationFrame)),
MessageDiscriminant::Animation(AnimationMessageDiscriminant::IncrementFrameCounter), MessageDiscriminant::Animation(AnimationMessageDiscriminant::IncrementFrameCounter),
MessageDiscriminant::Defer(DeferMessageDiscriminant::CheckDeferredMessages),
]; ];
// TODO: Find a way to combine these with the list above. We use strings for now since these are the standard variant names used by multiple messages. But having these also type-checked would be best. // TODO: Find a way to combine these with the list above. We use strings for now since these are the standard variant names used by multiple messages. But having these also type-checked would be best.
const DEBUG_MESSAGE_ENDING_BLOCK_LIST: &[&str] = &["PointerMove", "PointerOutsideViewport", "Overlays", "Draw", "CurrentTime", "Time"]; const DEBUG_MESSAGE_ENDING_BLOCK_LIST: &[&str] = &["PointerMove", "PointerOutsideViewport", "Overlays", "Draw", "CurrentTime", "Time"];
@@ -1,15 +1,11 @@
use std::{ use std::collections::BTreeMap;
collections::BTreeMap,
ops::Bound,
time::{Duration, Instant},
};
use crate::messages::prelude::*; use crate::messages::prelude::*;
#[derive(ExtractField)] #[derive(ExtractField)]
pub struct DeferMessageContext<'a> { pub struct DeferMessageContext<'a> {
pub portfolio: &'a PortfolioMessageHandler, pub portfolio: &'a PortfolioMessageHandler,
pub time: Instant, pub time: u64,
} }
#[derive(Debug, Default, ExtractField)] #[derive(Debug, Default, ExtractField)]
@@ -17,7 +13,7 @@ pub struct DeferMessageHandler {
after_graph_run: HashMap<DocumentId, Vec<(u64, Message)>>, after_graph_run: HashMap<DocumentId, Vec<(u64, Message)>>,
after_viewport_resize: Vec<Message>, after_viewport_resize: Vec<Message>,
current_graph_submission_id: u64, current_graph_submission_id: u64,
after_time_elapsed: BTreeMap<Instant, Message>, after_time_elapsed: BTreeMap<u64, Message>,
} }
#[message_handler_data] #[message_handler_data]
@@ -58,11 +54,11 @@ impl MessageHandler<DeferMessage, DeferMessageContext<'_>> for DeferMessageHandl
} }
} }
DeferMessage::RequestDeferredMessage { timeout, message } => { DeferMessage::RequestDeferredMessage { timeout, message } => {
self.after_time_elapsed.insert(context.time + timeout, *message); self.after_time_elapsed.insert(context.time + timeout.as_millis() as u64, *message);
} }
DeferMessage::CheckDeferredMessages => { DeferMessage::CheckDeferredMessages => {
let after_current_time = self.after_time_elapsed.split_off((Bound::Unbounded, Bound::Excluded(context.time))); let after_current_time = self.after_time_elapsed.split_off(&context.time);
for (_, message) in std::mem::replace(self.after_time_elapsed, after_current_time) { for (_, message) in std::mem::replace(&mut self.after_time_elapsed, after_current_time) {
responses.add(message); responses.add(message);
} }
} }
@@ -175,10 +175,6 @@ pub enum FrontendMessage {
#[serde(rename = "exportIndex")] #[serde(rename = "exportIndex")]
index: Option<usize>, index: Option<usize>,
}, },
UpdateLayerWidths {
#[serde(rename = "layerWidths")]
layer_widths: HashMap<NodeId, u32>,
},
UpdateDialogButtons { UpdateDialogButtons {
#[serde(rename = "layoutTarget")] #[serde(rename = "layoutTarget")]
layout_target: LayoutTarget, layout_target: LayoutTarget,
@@ -269,7 +265,6 @@ pub enum FrontendMessage {
UpdateMouseCursor { UpdateMouseCursor {
cursor: MouseCursorIcon, cursor: MouseCursorIcon,
}, },
RequestNativeNodeGraphRender,
UpdateNativeNodeGraphSVG { UpdateNativeNodeGraphSVG {
#[serde(rename = "svgString")] #[serde(rename = "svgString")]
svg_string: String, svg_string: String,
@@ -4,7 +4,7 @@ use crate::messages::input_mapper::utility_types::misc::FrameTimeInfo;
use crate::messages::portfolio::utility_types::KeyboardPlatformLayout; use crate::messages::portfolio::utility_types::KeyboardPlatformLayout;
use crate::messages::prelude::*; use crate::messages::prelude::*;
use glam::DVec2; use glam::DVec2;
use std::time::{Duration, Instant}; use std::time::Duration;
#[derive(ExtractField)] #[derive(ExtractField)]
pub struct InputPreprocessorMessageContext { pub struct InputPreprocessorMessageContext {
@@ -14,7 +14,7 @@ pub struct InputPreprocessorMessageContext {
#[derive(Debug, Default, ExtractField)] #[derive(Debug, Default, ExtractField)]
pub struct InputPreprocessorMessageHandler { pub struct InputPreprocessorMessageHandler {
pub frame_time: FrameTimeInfo, pub frame_time: FrameTimeInfo,
pub time: Instant, pub time: u64,
pub keyboard: KeyStates, pub keyboard: KeyStates,
pub mouse: MouseState, pub mouse: MouseState,
pub viewport_bounds: ViewportBounds, pub viewport_bounds: ViewportBounds,
@@ -43,6 +43,7 @@ impl MessageHandler<InputPreprocessorMessage, InputPreprocessorMessageContext> f
.into(), .into(),
], ],
}); });
responses.add(NodeGraphMessage::UpdateNodeGraphTopRight);
} }
InputPreprocessorMessage::DoubleClick { editor_mouse_state, modifier_keys } => { InputPreprocessorMessage::DoubleClick { editor_mouse_state, modifier_keys } => {
self.update_states_of_modifier_keys(modifier_keys, keyboard_platform, responses); self.update_states_of_modifier_keys(modifier_keys, keyboard_platform, responses);
@@ -114,7 +115,7 @@ impl MessageHandler<InputPreprocessorMessage, InputPreprocessorMessageContext> f
} }
InputPreprocessorMessage::CurrentTime { timestamp } => { InputPreprocessorMessage::CurrentTime { timestamp } => {
responses.add(AnimationMessage::SetTime { time: timestamp as f64 }); responses.add(AnimationMessage::SetTime { time: timestamp as f64 });
self.time = Instant::from(timestamp); self.time = timestamp;
self.frame_time.advance_timestamp(Duration::from_millis(timestamp)); self.frame_time.advance_timestamp(Duration::from_millis(timestamp));
} }
InputPreprocessorMessage::WheelScroll { editor_mouse_state, modifier_keys } => { InputPreprocessorMessage::WheelScroll { editor_mouse_state, modifier_keys } => {
@@ -476,6 +476,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
self.selection_network_path.clone_from(&self.breadcrumb_network_path); self.selection_network_path.clone_from(&self.breadcrumb_network_path);
responses.add(NodeGraphMessage::SendGraph); responses.add(NodeGraphMessage::SendGraph);
responses.add(DocumentMessage::ZoomCanvasToFitAll); responses.add(DocumentMessage::ZoomCanvasToFitAll);
responses.add(NodeGraphMessage::UpdateNodeGraphTopRight);
} }
DocumentMessage::Escape => { DocumentMessage::Escape => {
if self.node_graph_handler.drag_start.is_some() { if self.node_graph_handler.drag_start.is_some() {
@@ -504,6 +505,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
} }
responses.add(NodeGraphMessage::SendGraph); responses.add(NodeGraphMessage::SendGraph);
responses.add(DocumentMessage::PTZUpdate); responses.add(DocumentMessage::PTZUpdate);
responses.add(NodeGraphMessage::UpdateNodeGraphTopRight);
} }
DocumentMessage::FlipSelectedLayers { flip_axis } => { DocumentMessage::FlipSelectedLayers { flip_axis } => {
let scale = match flip_axis { let scale = match flip_axis {
@@ -3,7 +3,7 @@ use crate::messages::input_mapper::utility_types::input_keyboard::Key;
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier; use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
use crate::messages::portfolio::document::utility_types::network_interface::{ImportOrExport, InputConnector, NodeTemplate, OutputConnector}; use crate::messages::portfolio::document::utility_types::network_interface::{ImportOrExport, InputConnector, NodeTemplate, OutputConnector};
use crate::messages::prelude::*; use crate::messages::prelude::*;
use glam::IVec2; use glam::{DVec2, IVec2};
use graph_craft::document::value::TaggedValue; use graph_craft::document::value::TaggedValue;
use graph_craft::document::{NodeId, NodeInput}; use graph_craft::document::{NodeId, NodeInput};
use graph_craft::proto::GraphErrors; use graph_craft::proto::GraphErrors;
@@ -112,6 +112,7 @@ pub enum NodeGraphMessage {
PointerOutsideViewport { PointerOutsideViewport {
shift: Key, shift: Key,
}, },
UpdateNodeGraphTopRight,
ShakeNode, ShakeNode,
RemoveImport { RemoveImport {
import_index: usize, import_index: usize,
@@ -215,7 +216,9 @@ pub enum NodeGraphMessage {
SetLockedOrVisibilitySideEffects { SetLockedOrVisibilitySideEffects {
node_ids: Vec<NodeId>, node_ids: Vec<NodeId>,
}, },
TryDisplayTooltip, TryDisplayTooltip {
initial_position: DVec2,
},
UpdateBoxSelection, UpdateBoxSelection,
UpdateImportsExports, UpdateImportsExports,
UpdateLayerPanel, UpdateLayerPanel,
@@ -1,6 +1,6 @@
use super::utility_types::{BoxSelection, ContextMenuInformation, DragStart}; use super::utility_types::{BoxSelection, ContextMenuInformation, DragStart};
use super::{document_node_definitions, node_properties}; use super::{document_node_definitions, node_properties};
use crate::consts::{GRID_SIZE, TOOLTIP_DELAY}; use crate::consts::*;
use crate::messages::input_mapper::utility_types::macros::action_keys; use crate::messages::input_mapper::utility_types::macros::action_keys;
use crate::messages::layout::utility_types::widget_prelude::*; use crate::messages::layout::utility_types::widget_prelude::*;
use crate::messages::portfolio::document::document_message_handler::navigation_controls; use crate::messages::portfolio::document::document_message_handler::navigation_controls;
@@ -30,6 +30,7 @@ use graphene_std::*;
use kurbo::{DEFAULT_ACCURACY, Shape}; use kurbo::{DEFAULT_ACCURACY, Shape};
use renderer::Quad; use renderer::Quad;
use std::cmp::Ordering; use std::cmp::Ordering;
use std::time::Duration;
#[derive(Debug, ExtractField)] #[derive(Debug, ExtractField)]
pub struct NodeGraphMessageContext<'a> { pub struct NodeGraphMessageContext<'a> {
@@ -1149,19 +1150,21 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
.unwrap_or_else(|| modify_import_export.reorder_imports_exports.input_ports().count() + 1), .unwrap_or_else(|| modify_import_export.reorder_imports_exports.input_ports().count() + 1),
); );
responses.add(FrontendMessage::UpdateExportReorderIndex { index: self.end_index }); responses.add(FrontendMessage::UpdateExportReorderIndex { index: self.end_index });
} else if !self.hovering_input && !self.hovering_output { } else if !self.hovering_input && !self.hovering_output && !self.hovering_node {
if network_interface.input_connector_from_click(ipp.mouse.position, breadcrumb_network_path).is_some() { if network_interface.input_connector_from_click(ipp.mouse.position, breadcrumb_network_path).is_some() {
self.hovering_input = true;
responses.add(DeferMessage::RequestDeferredMessage { responses.add(DeferMessage::RequestDeferredMessage {
message: Box::new(NodeGraphMessage::TryDisplayTooltip.into()), message: Box::new(NodeGraphMessage::TryDisplayTooltip { initial_position: ipp.mouse.position }.into()),
timeout: Duration::from_millis(TOOLTIP_DELAY), timeout: Duration::from_millis(INPUT_TOOLTIP_DELAY),
}); });
} } else if network_interface.output_connector_from_click(ipp.mouse.position, breadcrumb_network_path).is_some() {
if network_interface.output_connector_from_click(ipp.mouse.position, breadcrumb_network_path).is_some() {
self.hovering_output = true;
responses.add(DeferMessage::RequestDeferredMessage { responses.add(DeferMessage::RequestDeferredMessage {
message: Box::new(NodeGraphMessage::TryDisplayTooltip.into()), message: Box::new(NodeGraphMessage::TryDisplayTooltip { initial_position: ipp.mouse.position }.into()),
timeout: Duration::from_millis(TOOLTIP_DELAY), timeout: Duration::from_millis(INPUT_TOOLTIP_DELAY),
})
} else if network_interface.node_from_click(ipp.mouse.position, breadcrumb_network_path).is_some() {
responses.add(DeferMessage::RequestDeferredMessage {
message: Box::new(NodeGraphMessage::TryDisplayTooltip { initial_position: ipp.mouse.position }.into()),
timeout: Duration::from_millis(INPUT_TOOLTIP_DELAY),
}) })
} }
} else if self.hovering_input { } else if self.hovering_input {
@@ -1174,16 +1177,8 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
self.hovering_output = false; self.hovering_output = false;
responses.add(FrontendMessage::UpdateTooltip { position: None, text: String::new() }); responses.add(FrontendMessage::UpdateTooltip { position: None, text: String::new() });
} }
} else if !self.hovering_node {
if network_interface.node_from_click(ipp.mouse.position, breadcrumb_network_path) {
self.hovering_node = true;
responses.add(DeferMessage::RequestDeferredMessage {
message: Box::new(NodeGraphMessage::TryDisplayTooltip.into()),
timeout: Duration::from_millis(TOOLTIP_DELAY),
})
}
} else if self.hovering_node { } else if self.hovering_node {
if !network_interface.node_from_click(ipp.mouse.position, breadcrumb_network_path) { if !network_interface.node_from_click(ipp.mouse.position, breadcrumb_network_path).is_some() {
self.hovering_node = false; self.hovering_node = false;
responses.add(FrontendMessage::UpdateTooltip { position: None, text: String::new() }); responses.add(FrontendMessage::UpdateTooltip { position: None, text: String::new() });
} }
@@ -1449,6 +1444,10 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
self.auto_panning.stop(&messages, responses); self.auto_panning.stop(&messages, responses);
} }
} }
NodeGraphMessage::UpdateNodeGraphTopRight => {
network_interface.set_node_graph_width(ipp.viewport_bounds.size().x, breadcrumb_network_path);
responses.add(NodeGraphMessage::UpdateImportsExports);
}
NodeGraphMessage::ShakeNode => { NodeGraphMessage::ShakeNode => {
let Some(drag_start) = &self.drag_start else { let Some(drag_start) = &self.drag_start else {
log::error!("Drag start should be initialized when shaking a node"); log::error!("Drag start should be initialized when shaking a node");
@@ -1849,8 +1848,10 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
responses.add(NodeGraphMessage::SetVisibility { node_id, visible }); responses.add(NodeGraphMessage::SetVisibility { node_id, visible });
responses.add(NodeGraphMessage::SetLockedOrVisibilitySideEffects { node_ids: vec![node_id] }); responses.add(NodeGraphMessage::SetLockedOrVisibilitySideEffects { node_ids: vec![node_id] });
} }
NodeGraphMessage::TryDisplayTooltip => { NodeGraphMessage::TryDisplayTooltip { initial_position } => {
if let Some(input) = network_interface.input_connector_from_click(ipp.mouse.position, breadcrumb_network_path) { if initial_position != ipp.mouse.position {
return;
} else if let Some(input) = network_interface.input_connector_from_click(ipp.mouse.position, breadcrumb_network_path) {
let text = network_interface.input_tooltip_text(&input, breadcrumb_network_path); let text = network_interface.input_tooltip_text(&input, breadcrumb_network_path);
if let Some(position) = network_interface.input_position(&input, breadcrumb_network_path) { if let Some(position) = network_interface.input_position(&input, breadcrumb_network_path) {
let Some(network_metadata) = network_interface.network_metadata(breadcrumb_network_path) else { let Some(network_metadata) = network_interface.network_metadata(breadcrumb_network_path) else {
@@ -1862,6 +1863,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
y: position.y as i32, y: position.y as i32,
}; };
responses.add(FrontendMessage::UpdateTooltip { position: Some(xy), text }); responses.add(FrontendMessage::UpdateTooltip { position: Some(xy), text });
self.hovering_input = true;
} }
} else if let Some(output) = network_interface.output_connector_from_click(ipp.mouse.position, breadcrumb_network_path) { } else if let Some(output) = network_interface.output_connector_from_click(ipp.mouse.position, breadcrumb_network_path) {
let text = network_interface.output_tooltip_text(&output, breadcrumb_network_path); let text = network_interface.output_tooltip_text(&output, breadcrumb_network_path);
@@ -1876,25 +1878,29 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
y: position.y as i32, y: position.y as i32,
}; };
responses.add(FrontendMessage::UpdateTooltip { position: Some(xy), text }); responses.add(FrontendMessage::UpdateTooltip { position: Some(xy), text });
self.hovering_output = true;
} }
} else if let Some(node_id) = network_interface.node_from_click(ipp.mouse.position, breadcrumb_network_path) { } else if let Some(node_id) = network_interface.node_from_click(ipp.mouse.position, breadcrumb_network_path) {
let text = network_interface.node_tooltip_text(&node_id, breadcrumb_network_path); let text = network_interface.node_tooltip_text(&node_id, breadcrumb_network_path);
if let Some(position) = network_interface.position(&node_id, breadcrumb_network_path) { let Some(position) = network_interface.position(&node_id, breadcrumb_network_path) else {
let Some(network_metadata) = network_interface.network_metadata(breadcrumb_network_path) else { log::error!("Could not get position from node: {node_id}");
return; return;
}; };
let position = network_metadata let Some(network_metadata) = network_interface.network_metadata(breadcrumb_network_path) else {
.persistent_metadata return;
.navigation_metadata };
.node_graph_to_viewport let position = network_metadata
.transform_point2(DVec2::new(position.x as f64 * 24., position.y as f64 * 24.)); .persistent_metadata
.navigation_metadata
.node_graph_to_viewport
.transform_point2(DVec2::new(position.x as f64 * 24., position.y as f64 * 24.));
let xy = FrontendXY { let xy = FrontendXY {
x: position.x as i32, x: position.x as i32,
y: position.y as i32, y: position.y as i32,
}; };
responses.add(FrontendMessage::UpdateTooltip { position: Some(xy), text }); responses.add(FrontendMessage::UpdateTooltip { position: Some(xy), text });
} self.hovering_node = true;
} }
} }
NodeGraphMessage::SetPinned { node_id, pinned } => { NodeGraphMessage::SetPinned { node_id, pinned } => {
@@ -2655,6 +2661,9 @@ impl Default for NodeGraphMessageHandler {
reordering_import: None, reordering_import: None,
end_index: None, end_index: None,
thumbnails: HashMap::new(), thumbnails: HashMap::new(),
hovering_input: false,
hovering_output: false,
hovering_node: false,
} }
} }
} }
@@ -1,185 +1,6 @@
use graph_craft::document::NodeId;
use std::borrow::Cow; use std::borrow::Cow;
use crate::messages::portfolio::document::utility_types::network_interface::resolved_types::TypeSource; use graphene_std::uuid::NodeId;
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Hash, serde::Serialize, serde::Deserialize, specta::Type)]
pub enum FrontendGraphDataType {
#[default]
General,
Number,
Artboard,
Graphic,
Raster,
Vector,
Color,
Gradient,
Typography,
}
impl FrontendGraphDataType {
pub fn from_type(input: &Type) -> Self {
match TaggedValue::from_type_or_none(input) {
TaggedValue::U32(_)
| TaggedValue::U64(_)
| TaggedValue::F32(_)
| TaggedValue::F64(_)
| TaggedValue::DVec2(_)
| TaggedValue::F64Array4(_)
| TaggedValue::VecF64(_)
| TaggedValue::VecDVec2(_)
| TaggedValue::DAffine2(_) => Self::Number,
TaggedValue::Artboard(_) => Self::Artboard,
TaggedValue::Graphic(_) => Self::Graphic,
TaggedValue::Raster(_) => Self::Raster,
TaggedValue::Vector(_) => Self::Vector,
TaggedValue::Color(_) => Self::Color,
TaggedValue::Gradient(_) | TaggedValue::GradientStops(_) | TaggedValue::GradientTable(_) => Self::Gradient,
TaggedValue::String(_) => Self::Typography,
_ => Self::General,
}
}
pub fn displayed_type(type_source: &TypeSource) -> Self {
match type_source.compiled_nested_type() {
Some(nested_type) => Self::from_type(&nested_type),
None => Self::General,
}
}
}
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
pub struct FrontendXY {
pub x: i32,
pub y: i32,
}
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
pub struct FrontendGraphInput {
#[serde(rename = "dataType")]
pub data_type: FrontendGraphDataType,
#[serde(rename = "resolvedType")]
pub resolved_type: String,
pub name: String,
pub description: String,
/// Either "nothing", "import index {index}", or "{node name} output {output_index}".
#[serde(rename = "connectedToString")]
pub connected_to: String,
/// Used to render the upstream node once this node is rendered
#[serde(rename = "connectedToNode")]
pub connected_to_node: Option<NodeId>,
}
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
pub struct FrontendGraphOutput {
#[serde(rename = "dataType")]
pub data_type: FrontendGraphDataType,
pub name: String,
#[serde(rename = "resolvedType")]
pub resolved_type: String,
pub description: String,
/// If connected to an export, it is "export index {index}".
/// If connected to a node, it is "{node name} input {input_index}".
#[serde(rename = "connectedTo")]
pub connected_to: Vec<String>,
}
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
pub struct FrontendExport {
pub port: FrontendGraphInput,
pub wire: Option<String>,
}
#[derive(Clone, Debug, Default, Eq, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
pub struct FrontendExports {
/// If the primary export is not visible, then it is None.
pub exports: Vec<Option<FrontendExport>>,
#[serde(rename = "previewWire")]
pub preview_wire: Option<String>,
}
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
pub struct FrontendImport {
pub port: FrontendGraphOutput,
pub wires: Vec<String>,
}
// Metadata that is common to nodes and layers
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
pub struct FrontendNodeMetadata {
#[serde(rename = "nodeId")]
pub node_id: NodeId,
// TODO: Remove and replace with popup manager system
#[serde(rename = "canBeLayer")]
pub can_be_layer: bool,
#[serde(rename = "displayName")]
pub display_name: String,
pub selected: bool,
// Used to get the description, which is stored in a global hashmap
pub reference: Option<String>,
// Reduces opacity of node/hidden eye icon
pub visible: bool,
// The svg string for each input
// pub wires: Vec<Option<String>>,
pub errors: Option<String>,
}
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
pub struct FrontendNode {
// pub position: FrontendNodePosition,
pub position: FrontendXY,
pub inputs: Vec<Option<FrontendGraphInput>>,
pub outputs: Vec<Option<FrontendGraphOutput>>,
}
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
pub struct FrontendLayer {
#[serde(rename = "bottomInput")]
pub bottom_input: FrontendGraphInput,
#[serde(rename = "sideInput")]
pub side_input: Option<FrontendGraphInput>,
pub output: FrontendGraphOutput,
// pub position: FrontendLayerPosition,
pub position: FrontendXY,
pub locked: bool,
#[serde(rename = "chainWidth")]
pub chain_width: u32,
#[serde(rename = "layerHasLeftBorderGap")]
pub layer_has_left_border_gap: bool,
#[serde(rename = "primaryInputConnectedToLayer")]
pub primary_input_connected_to_layer: bool,
#[serde(rename = "primaryOutputConnectedToLayer")]
pub primary_output_connected_to_layer: bool,
}
// // Should be an enum but those are hard to serialize/deserialize to TS
// #[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
// pub struct FrontendNodePosition {
// pub absolute: Option<FrontendXY>,
// pub chain: Option<bool>,
// }
// // Should be an enum but those are hard to serialize/deserialize to TS
// #[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
// pub struct FrontendLayerPosition {
// pub absolute: Option<FrontendXY>,
// pub stack: Option<u32>,
// }
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
pub struct FrontendNodeOrLayer {
pub node: Option<FrontendNode>,
pub layer: Option<FrontendLayer>,
}
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
pub struct FrontendNodeToRender {
pub metadata: FrontendNodeMetadata,
#[serde(rename = "nodeOrLayer")]
pub node_or_layer: FrontendNodeOrLayer,
//TODO: Remove
pub wires: Vec<(String, bool, FrontendGraphDataType)>,
}
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)] #[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
pub struct FrontendNodeType { pub struct FrontendNodeType {
@@ -2675,6 +2675,16 @@ impl NodeNetworkInterface {
self.unload_modify_import_export(network_path); self.unload_modify_import_export(network_path);
} }
pub fn set_node_graph_width(&mut self, node_graph_width: f64, network_path: &[NodeId]) {
let Some(network_metadata) = self.network_metadata_mut(network_path) else {
log::error!("Could not get nested network in set_transform");
return;
};
network_metadata.persistent_metadata.navigation_metadata.node_graph_width = node_graph_width;
self.unload_import_export_ports(network_path);
self.unload_modify_import_export(network_path);
}
pub fn vector_modify(&mut self, node_id: &NodeId, modification_type: VectorModificationType) { pub fn vector_modify(&mut self, node_id: &NodeId, modification_type: VectorModificationType) {
let Some(node) = self.network_mut(&[]).unwrap().nodes.get_mut(node_id) else { let Some(node) = self.network_mut(&[]).unwrap().nodes.get_mut(node_id) else {
log::error!("Could not get node in vector_modification"); log::error!("Could not get node in vector_modification");
@@ -5853,7 +5863,7 @@ pub enum LayerClickTargetTypes {
// Preview, // Preview,
} }
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] #[derive(Debug, Clone, Default, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct NavigationMetadata { pub struct NavigationMetadata {
/// The current pan, and zoom state of the viewport's view of the node graph. /// The current pan, and zoom state of the viewport's view of the node graph.
/// Ensure `DocumentMessage::UpdateDocumentTransform` is called when the pan, zoom, or transform changes. /// Ensure `DocumentMessage::UpdateDocumentTransform` is called when the pan, zoom, or transform changes.
@@ -5861,21 +5871,10 @@ pub struct NavigationMetadata {
// TODO: Remove and replace with calculate_offset_transform from the node_graph_ptz. This will be difficult since it requires both the navigation message handler and the IPP // TODO: Remove and replace with calculate_offset_transform from the node_graph_ptz. This will be difficult since it requires both the navigation message handler and the IPP
/// Transform from node graph space to viewport space. /// Transform from node graph space to viewport space.
pub node_graph_to_viewport: DAffine2, pub node_graph_to_viewport: DAffine2,
/// Top right of the node graph in viewport space // TODO: Eventually replace with footprint
/// The width of the node graph in viewport space
#[serde(default)] #[serde(default)]
pub node_graph_top_right: DVec2, pub node_graph_width: f64,
}
impl Default for NavigationMetadata {
fn default() -> NavigationMetadata {
// Default PTZ and transform
NavigationMetadata {
node_graph_ptz: PTZ::default(),
node_graph_to_viewport: DAffine2::IDENTITY,
// TODO: Eventually replace with footprint
node_graph_top_right: DVec2::ZERO,
}
}
} }
// PartialEq required by message handlers // PartialEq required by message handlers
@@ -1,19 +1,19 @@
use glam::{DVec2, IVec2}; use glam::{DVec2, IVec2};
use graph_craft::proto::GraphErrors; use graph_craft::proto::GraphErrors;
use graphene_std::uuid::NodeId; use graphene_std::{
node_graph_overlay::types::{
FrontendExport, FrontendExports, FrontendGraphInput, FrontendGraphOutput, FrontendImport, FrontendLayer, FrontendNode, FrontendNodeMetadata, FrontendNodeOrLayer, FrontendNodeToRender,
FrontendXY,
},
uuid::NodeId,
};
use kurbo::BezPath; use kurbo::BezPath;
use crate::{ use crate::{
consts::{EXPORTS_TO_RIGHT_EDGE_PIXEL_GAP, EXPORTS_TO_TOP_EDGE_PIXEL_GAP, GRID_SIZE, IMPORTS_TO_LEFT_EDGE_PIXEL_GAP, IMPORTS_TO_TOP_EDGE_PIXEL_GAP}, consts::{EXPORTS_TO_RIGHT_EDGE_PIXEL_GAP, EXPORTS_TO_TOP_EDGE_PIXEL_GAP, GRID_SIZE, IMPORTS_TO_LEFT_EDGE_PIXEL_GAP, IMPORTS_TO_TOP_EDGE_PIXEL_GAP},
messages::portfolio::document::{ messages::portfolio::document::utility_types::{
node_graph::utility_types::{ network_interface::{FlowType, InputConnector, NodeNetworkInterface, OutputConnector, Previewing},
FrontendExport, FrontendExports, FrontendGraphDataType, FrontendGraphInput, FrontendGraphOutput, FrontendImport, FrontendLayer, FrontendNode, FrontendNodeMetadata, FrontendNodeOrLayer, wires::{GraphWireStyle, build_vector_wire},
FrontendNodeToRender, FrontendXY,
},
utility_types::{
network_interface::{FlowType, InputConnector, NodeNetworkInterface, OutputConnector, Previewing},
wires::{GraphWireStyle, build_vector_wire},
},
}, },
}; };
@@ -123,7 +123,7 @@ impl NodeNetworkInterface {
( (
wire, wire,
self.wire_is_thick(&InputConnector::node(node_id, input_index), network_path), self.wire_is_thick(&InputConnector::node(node_id, input_index), network_path),
FrontendGraphDataType::displayed_type(&self.input_type(&InputConnector::node(node_id, input_index), network_path)), self.input_type(&InputConnector::node(node_id, input_index), network_path).displayed_type(),
) )
}) })
}) })
@@ -210,8 +210,12 @@ impl NodeNetworkInterface {
} }
} }
}; };
let connected = self
.outward_wires(network_path)
.and_then(|outward_wires| outward_wires.get(output_connector))
.is_some_and(|downstream| downstream.len() > 0);
let data_type = output_type.displayed_type(); let data_type = output_type.displayed_type();
Some(FrontendGraphOutput { data_type, name }) Some(FrontendGraphOutput { data_type, name, connected })
} }
pub fn chain_width(&self, node_id: &NodeId, network_path: &[NodeId]) -> u32 { pub fn chain_width(&self, node_id: &NodeId, network_path: &[NodeId]) -> u32 {
@@ -386,19 +390,18 @@ impl NodeNetworkInterface {
let import_top_left = DVec2::new(top_left_inner_bound.x.min(bounding_box_top_left.x), top_left_inner_bound.y.min(bounding_box_top_left.y)); let import_top_left = DVec2::new(top_left_inner_bound.x.min(bounding_box_top_left.x), top_left_inner_bound.y.min(bounding_box_top_left.y));
let rounded_import_top_left = DVec2::new((import_top_left.x / 24.).round() * 24., (import_top_left.y / 24.).round() * 24.); let rounded_import_top_left = DVec2::new((import_top_left.x / 24.).round() * 24., (import_top_left.y / 24.).round() * 24.);
let viewport_top_right = network_metadata.persistent_metadata.navigation_metadata.node_graph_top_right; let viewport_width = network_metadata.persistent_metadata.navigation_metadata.node_graph_width;
let target_viewport_top_right = DVec2::new(
viewport_top_right.x - EXPORTS_TO_RIGHT_EDGE_PIXEL_GAP as f64, let target_viewport_top_right = DVec2::new(viewport_width - EXPORTS_TO_RIGHT_EDGE_PIXEL_GAP as f64, EXPORTS_TO_TOP_EDGE_PIXEL_GAP as f64);
viewport_top_right.y + EXPORTS_TO_TOP_EDGE_PIXEL_GAP as f64,
);
// An offset from the right edge in viewport pixels // An offset from the right edge in viewport pixels
let node_graph_pixel_offset_top_right = node_graph_to_viewport.inverse().transform_point2(target_viewport_top_right); let node_graph_pixel_offset_top_right = node_graph_to_viewport.inverse().transform_point2(target_viewport_top_right);
// A 5x5 grid offset from the right corner // A 5x5 grid offset from the right corner
let node_graph_grid_space_offset_top_right = node_graph_to_viewport.inverse().transform_point2(viewport_top_right) + DVec2::new(-5. * GRID_SIZE as f64, 4. * GRID_SIZE as f64); let node_graph_grid_space_offset_top_right = node_graph_to_viewport.inverse().transform_point2(DVec2::new(viewport_width, 0.)) + DVec2::new(-5. * GRID_SIZE as f64, 4. * GRID_SIZE as f64);
// The inner bound of the export is the highest/furthest right of the two offsets // The inner bound of the export is the highest/furthest right of the two offsets.
// When zoomed out this keeps it a constant grid space away from the edge, but when zoomed in it prevents the exports from getting too far in
let top_right_inner_bound = DVec2::new( let top_right_inner_bound = DVec2::new(
node_graph_pixel_offset_top_right.x.max(node_graph_grid_space_offset_top_right.x), node_graph_pixel_offset_top_right.x.max(node_graph_grid_space_offset_top_right.x),
node_graph_pixel_offset_top_right.y.min(node_graph_grid_space_offset_top_right.y), node_graph_pixel_offset_top_right.y.min(node_graph_grid_space_offset_top_right.y),
@@ -561,6 +564,6 @@ impl NodeNetworkInterface {
return String::new(); return String::new();
}; };
format!("{display_name}\nReference: {reference}\n\n{description}") format!("{display_name}\n\nReference: {reference:?}\n\n{description}")
} }
} }
@@ -1,5 +1,5 @@
use glam::{DVec2, IVec2}; use glam::{DVec2, IVec2};
use graphene_std::vector::misc::dvec2_to_point; use graphene_std::{node_graph_overlay::types::FrontendGraphDataType, vector::misc::dvec2_to_point};
use kurbo::{BezPath, DEFAULT_ACCURACY, Line, Point, Shape}; use kurbo::{BezPath, DEFAULT_ACCURACY, Line, Point, Shape};
#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)] #[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
@@ -562,7 +562,7 @@ struct PathToolData {
saved_selection_before_handle_drag: HashMap<LayerNodeIdentifier, (HashSet<ManipulatorPointId>, HashSet<SegmentId>)>, saved_selection_before_handle_drag: HashMap<LayerNodeIdentifier, (HashSet<ManipulatorPointId>, HashSet<SegmentId>)>,
handle_drag_toggle: bool, handle_drag_toggle: bool,
saved_points_before_anchor_convert_smooth_sharp: HashMap<LayerNodeIdentifier, Vec<ManipulatorPointId>>, saved_points_before_anchor_convert_smooth_sharp: HashMap<LayerNodeIdentifier, Vec<ManipulatorPointId>>,
last_click_time: Instant, last_click_time: u64,
dragging_state: DraggingState, dragging_state: DraggingState,
angle: f64, angle: f64,
pivot_gizmo: PivotGizmo, pivot_gizmo: PivotGizmo,
+2 -8
View File
@@ -12,6 +12,7 @@ use graphene_std::text::FontCache;
use graphene_std::transform::Footprint; use graphene_std::transform::Footprint;
use graphene_std::vector::Vector; use graphene_std::vector::Vector;
use graphene_std::wasm_application_io::RenderOutputType; use graphene_std::wasm_application_io::RenderOutputType;
use graphene_std::Graphic;
use interpreted_executor::dynamic_executor::ResolvedDocumentNodeTypesDelta; use interpreted_executor::dynamic_executor::ResolvedDocumentNodeTypesDelta;
mod runtime_io; mod runtime_io;
@@ -121,14 +122,7 @@ impl NodeGraphExecutor {
/// Update the cached network if necessary. /// Update the cached network if necessary.
fn update_node_graph(&mut self, document: &mut DocumentMessageHandler, node_to_inspect: Option<NodeId>, ignore_hash: bool) -> Result<(), String> { fn update_node_graph(&mut self, document: &mut DocumentMessageHandler, node_to_inspect: Option<NodeId>, ignore_hash: bool) -> Result<(), String> {
let mut network = document.network_interface.document_network().clone(); let network = document.network_interface.document_network().clone();
if let Some(mut node_graph_overlay_node) = document.node_graph_handler.node_graph_overlay.clone() {
let node_graph_overlay_id = NodeId::new();
let new_export = NodeInput::node(node_graph_overlay_id, 0);
let old_export = std::mem::replace(&mut network.exports[0], new_export);
node_graph_overlay_node.inputs[0] = old_export;
network.nodes.insert(node_graph_overlay_id, node_graph_overlay_node);
}
let network_hash = network.current_hash(); let network_hash = network.current_hash();
// Refresh the graph when it changes or the inspect node changes // Refresh the graph when it changes or the inspect node changes
if network_hash != self.node_graph_hash || self.previous_node_to_inspect != node_to_inspect || ignore_hash { if network_hash != self.node_graph_hash || self.previous_node_to_inspect != node_to_inspect || ignore_hash {
+1 -1
View File
@@ -299,7 +299,7 @@
style:--offset-left={($nodeGraph.updateImportsExports.importPosition.x - 8) / 24} style:--offset-left={($nodeGraph.updateImportsExports.importPosition.x - 8) / 24}
style:--offset-top={($nodeGraph.updateImportsExports.importPosition.y - 8) / 24 + index} style:--offset-top={($nodeGraph.updateImportsExports.importPosition.y - 8) / 24 + index}
> >
{#if frontendOutput.connectedTo.length > 0} {#if frontendOutput.connected}
<path d="M0,6.306A1.474,1.474,0,0,0,2.356,7.724L7.028,5.248c1.3-.687,1.3-1.809,0-2.5L2.356.276A1.474,1.474,0,0,0,0,1.694Z" fill="var(--data-color)" /> <path d="M0,6.306A1.474,1.474,0,0,0,2.356,7.724L7.028,5.248c1.3-.687,1.3-1.809,0-2.5L2.356.276A1.474,1.474,0,0,0,0,1.694Z" fill="var(--data-color)" />
{:else} {:else}
<path d="M0,6.306A1.474,1.474,0,0,0,2.356,7.724L7.028,5.248c1.3-.687,1.3-1.809,0-2.5L2.356.276A1.474,1.474,0,0,0,0,1.694Z" fill="var(--data-color-dim)" /> <path d="M0,6.306A1.474,1.474,0,0,0,2.356,7.724L7.028,5.248c1.3-.687,1.3-1.809,0-2.5L2.356.276A1.474,1.474,0,0,0,0,1.694Z" fill="var(--data-color-dim)" />
+1 -2
View File
@@ -157,8 +157,7 @@ export function createInputManager(editor: Editor, dialog: DialogState, portfoli
// TODO: This would allow it to properly decide to act on removing hover focus from something that was hovered in the canvas before moving over the GUI. // TODO: This would allow it to properly decide to act on removing hover focus from something that was hovered in the canvas before moving over the GUI.
// TODO: Further explanation: https://github.com/GraphiteEditor/Graphite/pull/623#discussion_r866436197 // TODO: Further explanation: https://github.com/GraphiteEditor/Graphite/pull/623#discussion_r866436197
const inFloatingMenu = e.target instanceof Element && e.target.closest("[data-floating-menu-content]"); const inFloatingMenu = e.target instanceof Element && e.target.closest("[data-floating-menu-content]");
const inGraphOverlay = get(document).graphViewOverlayOpen; if (!viewportPointerInteractionOngoing && inFloatingMenu) return;
if (!viewportPointerInteractionOngoing && (inFloatingMenu || inGraphOverlay)) return;
const modifiers = makeKeyboardModifiersBitfield(e); const modifiers = makeKeyboardModifiersBitfield(e);
if (detectShake(e)) editor.handle.onMouseShake(e.clientX, e.clientY, e.buttons, modifiers); if (detectShake(e)) editor.handle.onMouseShake(e.clientX, e.clientY, e.buttons, modifiers);
+24 -45
View File
@@ -127,17 +127,16 @@ export class UpdateNodeThumbnail extends JsMessage {
readonly value!: string; readonly value!: string;
} }
export class UpdateOpenDocumentsList extends JsMessage {
@Type(() => OpenDocument)
readonly openDocuments!: OpenDocument[];
}
export class UpdateTooltip extends JsMessage { export class UpdateTooltip extends JsMessage {
readonly position!: XY | undefined; readonly position!: XY | undefined;
readonly text!: string; readonly text!: string;
} }
export class UpdateOpenDocumentsList extends JsMessage {
@Type(() => FrontendDocumentDetails)
readonly openDocuments!: FrontendDocumentDetails[];
}
export class WirePathInProgress { export class WirePathInProgress {
readonly wire!: string; readonly wire!: string;
readonly thick!: boolean; readonly thick!: boolean;
@@ -148,35 +147,28 @@ export class UpdateWirePathInProgress extends JsMessage {
readonly wirePathInProgress!: WirePathInProgress | undefined; readonly wirePathInProgress!: WirePathInProgress | undefined;
} }
// Allows the auto save system to use a string for the id rather than a BigInt. export class OpenDocument {
// IndexedDb does not allow for BigInts as primary keys. readonly id!: bigint;
// TypeScript does not allow subclasses to change the type of class variables in subclasses. @Type(() => DocumentDetails)
// It is an abstract class to point out that it should not be instantiated directly. readonly details!: DocumentDetails;
export abstract class DocumentDetails {
get displayName(): string {
return this.details.displayName;
}
}
export class DocumentDetails {
readonly name!: string; readonly name!: string;
readonly isAutoSaved!: boolean; readonly isAutoSaved!: boolean;
readonly isSaved!: boolean; readonly isSaved!: boolean;
// This field must be provided by the subclass implementation
// readonly id!: bigint | string;
get displayName(): string { get displayName(): string {
return `${this.name}${this.isSaved ? "" : "*"}`; return `${this.name}${this.isSaved ? "" : "*"}`;
} }
} }
export class FrontendDocumentDetails extends DocumentDetails {
readonly id!: bigint;
}
=======
export class FrontendDocumentDetails extends DocumentDetails {
readonly id!: bigint;
}
>>>>>>> 17a1a3d5 (Complete separating node rendering from imports/exports)
export type FrontendGraphDataType = "General" | "Number" | "Artboard" | "Graphic" | "Raster" | "Vector" | "Color"; export type FrontendGraphDataType = "General" | "Number" | "Artboard" | "Graphic" | "Raster" | "Vector" | "Color";
export class FrontendGraphInput { export class FrontendGraphInput {
@@ -198,11 +190,7 @@ export class FrontendGraphOutput {
readonly name!: string; readonly name!: string;
readonly description!: string; readonly connected!: boolean;
readonly resolvedType!: string;
readonly connectedTo!: string[];
} }
export class FrontendExport { export class FrontendExport {
@@ -322,21 +310,20 @@ export class WireUpdate {
readonly wirePathUpdate!: WirePath | undefined; readonly wirePathUpdate!: WirePath | undefined;
} }
export class IndexedDbDocumentDetails extends DocumentDetails { export class TriggerPersistenceWriteDocument extends JsMessage {
// Use a string since IndexedDB can not use BigInts for keys
@Transform(({ value }: { value: bigint }) => value.toString()) @Transform(({ value }: { value: bigint }) => value.toString())
id!: string; documentId!: string;
}
export class TriggerIndexedDbWriteDocument extends JsMessage {
document!: string; document!: string;
@Type(() => IndexedDbDocumentDetails) @Type(() => DocumentDetails)
details!: IndexedDbDocumentDetails; details!: DocumentDetails;
version!: string; version!: string;
} }
export class TriggerIndexedDbRemoveDocument extends JsMessage { export class TriggerPersistenceRemoveDocument extends JsMessage {
// Use a string since IndexedDB can not use BigInts for keys // Use a string since IndexedDB can not use BigInts for keys
@Transform(({ value }: { value: bigint }) => value.toString()) @Transform(({ value }: { value: bigint }) => value.toString())
documentId!: string; documentId!: string;
@@ -1469,7 +1456,6 @@ export class WidgetDiffUpdate extends JsMessage {
layoutTarget!: unknown; layoutTarget!: unknown;
// TODO: Replace `any` with correct typing // TODO: Replace `any` with correct typing
@Transform(({ value }: { value: any }) => createWidgetDiff(value)) @Transform(({ value }: { value: any }) => createWidgetDiff(value))
diff!: WidgetDiff[]; diff!: WidgetDiff[];
} }
@@ -1504,7 +1490,6 @@ export function patchWidgetLayout(layout: /* &mut */ WidgetLayout, updates: Widg
return targetLayout; return targetLayout;
} }
// This is a path traversal so we can assume from the backend that it exists // This is a path traversal so we can assume from the backend that it exists
if (targetLayout && "action" in targetLayout) return targetLayout.children![index]; if (targetLayout && "action" in targetLayout) return targetLayout.children![index];
return targetLayout?.[index]; return targetLayout?.[index];
@@ -1525,7 +1510,6 @@ export function patchWidgetLayout(layout: /* &mut */ WidgetLayout, updates: Widg
diffObject.length = 0; diffObject.length = 0;
} }
// Remove all of the keys from the old object // Remove all of the keys from the old object
Object.keys(diffObject).forEach((key) => delete (diffObject as any)[key]); Object.keys(diffObject).forEach((key) => delete (diffObject as any)[key]);
// Assign keys to the new object // Assign keys to the new object
@@ -1558,7 +1542,6 @@ export function isWidgetSection(layoutRow: LayoutGroup): layoutRow is WidgetSect
} }
// Unpacking rust types to more usable type in the frontend // Unpacking rust types to more usable type in the frontend
function createWidgetDiff(diffs: any[]): WidgetDiff[] { function createWidgetDiff(diffs: any[]): WidgetDiff[] {
return diffs.map((diff) => { return diffs.map((diff) => {
const { widgetPath, newValue } = diff; const { widgetPath, newValue } = diff;
@@ -1577,7 +1560,6 @@ function createWidgetDiff(diffs: any[]): WidgetDiff[] {
} }
// Unpacking a layout group // Unpacking a layout group
function createLayoutGroup(layoutGroup: any): LayoutGroup { function createLayoutGroup(layoutGroup: any): LayoutGroup {
if (layoutGroup.column) { if (layoutGroup.column) {
const columnWidgets = hoistWidgetHolders(layoutGroup.column.columnWidgets); const columnWidgets = hoistWidgetHolders(layoutGroup.column.columnWidgets);
@@ -1635,7 +1617,6 @@ export class UpdateMenuBarLayout extends JsMessage {
layoutTarget!: unknown; layoutTarget!: unknown;
// TODO: Replace `any` with correct typing // TODO: Replace `any` with correct typing
@Transform(({ value }: { value: any }) => createMenuLayout(value)) @Transform(({ value }: { value: any }) => createMenuLayout(value))
layout!: MenuBarEntry[]; layout!: MenuBarEntry[];
} }
@@ -1658,7 +1639,6 @@ function createMenuLayout(menuBarEntry: any[]): MenuBarEntry[] {
children: createMenuLayoutRecursive(entry.children), children: createMenuLayoutRecursive(entry.children),
})); }));
} }
function createMenuLayoutRecursive(children: any[][]): MenuBarEntry[][] { function createMenuLayoutRecursive(children: any[][]): MenuBarEntry[][] {
return children.map((groups) => return children.map((groups) =>
groups.map((entry) => ({ groups.map((entry) => ({
@@ -1671,7 +1651,6 @@ function createMenuLayoutRecursive(children: any[][]): MenuBarEntry[][] {
} }
// `any` is used since the type of the object should be known from the Rust side // `any` is used since the type of the object should be known from the Rust side
type JSMessageFactory = (data: any, wasm: WebAssembly.Memory, handle: EditorHandle) => JsMessage; type JSMessageFactory = (data: any, wasm: WebAssembly.Memory, handle: EditorHandle) => JsMessage;
type MessageMaker = typeof JsMessage | JSMessageFactory; type MessageMaker = typeof JsMessage | JSMessageFactory;
@@ -1691,8 +1670,8 @@ export const messageMakers: Record<string, MessageMaker> = {
TriggerFetchAndOpenDocument, TriggerFetchAndOpenDocument,
TriggerFontLoad, TriggerFontLoad,
TriggerImport, TriggerImport,
TriggerIndexedDbRemoveDocument, TriggerPersistenceRemoveDocument,
TriggerIndexedDbWriteDocument, TriggerPersistenceWriteDocument,
TriggerLoadFirstAutoSaveDocument, TriggerLoadFirstAutoSaveDocument,
TriggerLoadPreferences, TriggerLoadPreferences,
TriggerLoadRestAutoSaveDocuments, TriggerLoadRestAutoSaveDocuments,
+11 -8
View File
@@ -5,21 +5,21 @@ import {
type FrontendSelectionBox, type FrontendSelectionBox,
type FrontendClickTargets, type FrontendClickTargets,
type ContextMenuInformation, type ContextMenuInformation,
type FrontendNodeToRender,
type FrontendNodeType, type FrontendNodeType,
type WirePathInProgress, type WirePathInProgress,
type XY,
SendUIMetadata, SendUIMetadata,
UpdateClickTargets, UpdateClickTargets,
UpdateContextMenuInformation, UpdateContextMenuInformation,
UpdateImportReorderIndex, UpdateImportReorderIndex,
UpdateExportReorderIndex, UpdateExportReorderIndex,
UpdateImportsExports, UpdateImportsExports,
UpdateLayerWidths,
UpdateNativeNodeGraphSVG, UpdateNativeNodeGraphSVG,
UpdateNodeThumbnail, UpdateNodeThumbnail,
UpdateWirePathInProgress, UpdateWirePathInProgress,
UpdateNodeGraphSelectionBox, UpdateNodeGraphSelectionBox,
UpdateNodeGraphTransform, UpdateNodeGraphTransform,
UpdateTooltip,
} from "@graphite/messages"; } from "@graphite/messages";
export function createNodeGraphState(editor: Editor) { export function createNodeGraphState(editor: Editor) {
@@ -37,11 +37,8 @@ export function createNodeGraphState(editor: Editor) {
nodeTypes: [] as FrontendNodeType[], nodeTypes: [] as FrontendNodeType[],
nodeDescriptions: new Map<string, string>(), nodeDescriptions: new Map<string, string>(),
// Data that will be moved into the node graph to be rendered natively tooltipPosition: undefined as XY | undefined,
nodesToRender: new Map<bigint, FrontendNodeToRender>(), tooltipText: "test",
opacity: 0.8,
inSelectedNetwork: true,
previewedNode: undefined as bigint | undefined,
// Data that will be passed in the context // Data that will be passed in the context
thumbnails: new Map<bigint, string>(), thumbnails: new Map<bigint, string>(),
@@ -117,7 +114,13 @@ export function createNodeGraphState(editor: Editor) {
return state; return state;
}); });
}); });
editor.subscriptions.subscribeJsMessage(UpdateTooltip, (updateTooltip) => {
update((state) => {
state.tooltipPosition = updateTooltip.position;
state.tooltipText = updateTooltip.text;
return state;
});
});
return { return {
subscribe, subscribe,
}; };
+45 -86
View File
@@ -5,11 +5,8 @@
// on the dispatcher messaging system and more complex Rust data types. // on the dispatcher messaging system and more complex Rust data types.
// //
use crate::helpers::translate_key; use crate::helpers::translate_key;
#[cfg(not(feature = "native"))] use crate::{EDITOR_HANDLE, EDITOR_HAS_CRASHED, Error, MESSAGE_BUFFER};
use crate::wasm_node_graph_ui_executor::WasmNodeGraphUIExecutor;
use crate::{EDITOR_HANDLE, EDITOR_HAS_CRASHED, Error, MESSAGE_BUFFER, WASM_NODE_GRAPH_EXECUTOR};
use editor::consts::FILE_EXTENSION; use editor::consts::FILE_EXTENSION;
use editor::dispatcher::EditorOutput;
use editor::messages::input_mapper::utility_types::input_keyboard::ModifierKeys; use editor::messages::input_mapper::utility_types::input_keyboard::ModifierKeys;
use editor::messages::input_mapper::utility_types::input_mouse::{EditorMouseState, ScrollDelta, ViewportBounds}; use editor::messages::input_mapper::utility_types::input_mouse::{EditorMouseState, ScrollDelta, ViewportBounds};
use editor::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier; use editor::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
@@ -175,13 +172,9 @@ impl EditorHandle {
pub fn new(frontend_message_handler_callback: js_sys::Function) -> Self { pub fn new(frontend_message_handler_callback: js_sys::Function) -> Self {
let editor = Editor::new(); let editor = Editor::new();
let editor_handle = EditorHandle { frontend_message_handler_callback }; let editor_handle = EditorHandle { frontend_message_handler_callback };
let node_graph_executor = WasmNodeGraphUIExecutor::new();
if EDITOR.with(|handle| handle.lock().ok().map(|mut guard| *guard = Some(editor))).is_none() { if EDITOR.with(|handle| handle.lock().ok().map(|mut guard| *guard = Some(editor))).is_none() {
log::error!("Attempted to initialize the editor more than once"); log::error!("Attempted to initialize the editor more than once");
} }
if WASM_NODE_GRAPH_EXECUTOR.with(|handle| handle.lock().ok().map(|mut guard| *guard = Some(node_graph_executor))).is_none() {
log::error!("Attempted to initialize the editor more than once");
}
if EDITOR_HANDLE.with(|handle| handle.lock().ok().map(|mut guard| *guard = Some(editor_handle.clone()))).is_none() { if EDITOR_HANDLE.with(|handle| handle.lock().ok().map(|mut guard| *guard = Some(editor_handle.clone()))).is_none() {
log::error!("Attempted to initialize the editor handle more than once"); log::error!("Attempted to initialize the editor handle more than once");
} }
@@ -202,12 +195,28 @@ impl EditorHandle {
#[cfg(not(feature = "native"))] #[cfg(not(feature = "native"))]
fn dispatch<T: Into<Message>>(&self, message: T) { fn dispatch<T: Into<Message>>(&self, message: T) {
// Process no further messages after a crash to avoid spamming the console // Process no further messages after a crash to avoid spamming the console
use crate::MESSAGE_BUFFER;
if EDITOR_HAS_CRASHED.load(Ordering::SeqCst) { if EDITOR_HAS_CRASHED.load(Ordering::SeqCst) {
return; return;
} }
let _ = editor(|editor| {
self.process_messages(std::iter::once(message.into()), editor); // Get the editor, dispatch the message, and store the `FrontendMessage` queue response
let frontend_messages = EDITOR.with(|editor| {
let mut guard = editor.try_lock();
let Ok(Some(editor)) = guard.as_deref_mut() else {
// Enqueue messages which can't be procssed currently
MESSAGE_BUFFER.with_borrow_mut(|buffer| buffer.push(message.into()));
return vec![];
};
editor.handle_message(message)
}); });
// Send each `FrontendMessage` to the JavaScript frontend
for message in frontend_messages.into_iter() {
self.send_frontend_message_to_js(message);
}
} }
#[cfg(feature = "native")] #[cfg(feature = "native")]
@@ -220,37 +229,6 @@ impl EditorHandle {
crate::native_communcation::send_message_to_cef(serialized_message) crate::native_communcation::send_message_to_cef(serialized_message)
} }
// Messages can come from the runtime, browser, or a timed callback. This processes them in the editor and does all the side effects
// Like updating the frontend and node graph ui network. Some side effects are deduplicated and produce other side effects.
fn process_messages(&self, messages: impl IntoIterator<Item = Message>, editor_param: &mut Editor) {
// Get the editor, dispatch the message, and store the `FrontendMessage` queue response
for output in messages.into_iter().flat_map(|message| editor_param.handle_message(message)).collect::<Vec<_>>() {
match output {
EditorOutput::RequestNativeNodeGraphRender { compilation_request } => {
let res = executor(|executor| executor.compilation_request(compilation_request));
if let Err(_) = res {
log::error!("Could not borrow executor in process_messages_in_editor");
}
}
EditorOutput::RequestDeferredMessage { message, timeout } => {
let callback = Closure::once_into_js(move || {
editor_and_handle(|editor, handle| {
handle.process_messages(std::iter::once(*message), editor);
});
});
window()
.unwrap()
.set_timeout_with_callback_and_timeout_and_arguments_0(callback.as_ref().unchecked_ref(), timeout.as_millis() as i32)
.unwrap();
}
EditorOutput::FrontendMessage { frontend_message } => {
self.send_frontend_message_to_js(frontend_message);
}
}
}
}
// ======================================================================== // ========================================================================
// Add additional JS -> Rust wrapper functions below as needed for calling // Add additional JS -> Rust wrapper functions below as needed for calling
// the backend from the web frontend. // the backend from the web frontend.
@@ -280,20 +258,6 @@ impl EditorHandle {
#[cfg(not(feature = "native"))] #[cfg(not(feature = "native"))]
wasm_bindgen_futures::spawn_local(poll_node_graph_evaluation()); wasm_bindgen_futures::spawn_local(poll_node_graph_evaluation());
// Poll the UI node graph
#[cfg(not(feature = "native"))]
let result = editor(|editor| {
let node_graph_response = executor(|executor| executor.poll_node_graph_ui_evaluation(editor));
match node_graph_response {
Ok(node_graph_ui_messages) => handle(|handle| handle.process_messages(node_graph_ui_messages, editor)),
Err(_) => log::error!("Could not get executor in frame loop"),
}
});
if let Err(_) = result {
log::error!("Could not get editor in frame loop");
}
if !EDITOR_HAS_CRASHED.load(Ordering::SeqCst) { if !EDITOR_HAS_CRASHED.load(Ordering::SeqCst) {
handle(|handle| { handle(|handle| {
// Process all messages that have been queued up // Process all messages that have been queued up
@@ -496,6 +460,7 @@ impl EditorHandle {
document_is_saved, document_is_saved,
document_serialized_content, document_serialized_content,
to_front, to_front,
select_after_open: false,
}; };
self.dispatch(message); self.dispatch(message);
} }
@@ -1000,49 +965,39 @@ fn set_timeout(f: &Closure<dyn FnMut()>, delay: Duration) {
/// Provides access to the `Editor` by calling the given closure with it as an argument. /// Provides access to the `Editor` by calling the given closure with it as an argument.
#[cfg(not(feature = "native"))] #[cfg(not(feature = "native"))]
fn editor<T>(callback: impl FnOnce(&mut editor::application::Editor) -> T) -> Result<T, ()> { fn editor<T: Default>(callback: impl FnOnce(&mut editor::application::Editor) -> T) -> T {
EDITOR.with(|editor| { EDITOR.with(|editor| {
let mut guard = editor.try_lock(); let mut guard = editor.try_lock();
let Ok(Some(editor)) = guard.as_deref_mut() else { let Ok(Some(editor)) = guard.as_deref_mut() else {
return Err(()); log::error!("Failed to borrow editor");
}; return T::default();
Ok(callback(editor))
})
}
#[cfg(not(feature = "native"))]
fn executor<T>(callback: impl FnOnce(&mut WasmNodeGraphUIExecutor) -> T) -> Result<T, ()> {
WASM_NODE_GRAPH_EXECUTOR.with(|executor| {
let mut guard = executor.try_lock();
let Ok(Some(executor)) = guard.as_deref_mut() else {
return Err(());
}; };
Ok(callback(executor)) callback(editor)
})
}
/// Provides access to the `EditorHandle` by calling the given closure with them as arguments.
pub(crate) fn handle(callback: impl FnOnce(&mut EditorHandle)) {
EDITOR_HANDLE.with(|editor_handle| {
let mut guard = editor_handle.try_lock();
let Ok(Some(editor_handle)) = guard.as_deref_mut() else {
return log::error!("Failed to borrow handle");
};
// Call the closure with the editor and its handle
callback(editor_handle)
}) })
} }
/// Provides access to the `Editor` and its `EditorHandle` by calling the given closure with them as arguments. /// Provides access to the `Editor` and its `EditorHandle` by calling the given closure with them as arguments.
#[cfg(not(feature = "native"))] #[cfg(not(feature = "native"))]
pub(crate) fn editor_and_handle(callback: impl FnOnce(&mut Editor, &mut EditorHandle)) { pub(crate) fn editor_and_handle(callback: impl FnOnce(&mut Editor, &mut EditorHandle)) {
let _ = handle(|editor_handle| { handle(|editor_handle| {
let _ = editor(|editor| { editor(|editor| {
// Call the closure with the editor and its handle // Call the closure with the editor and its handle
callback(editor, editor_handle); callback(editor, editor_handle);
}); })
});
}
/// Provides access to the `EditorHandle` by calling the given closure with them as arguments.
pub(crate) fn handle(callback: impl FnOnce(&mut EditorHandle)) {
EDITOR_HANDLE.with(|editor_handle| {
let mut guard = editor_handle.try_lock();
let Ok(Some(editor_handle)) = guard.as_deref_mut() else {
log::error!("Failed to borrow editor handle");
return;
};
// Call the closure with the editor and its handle
callback(editor_handle);
}); });
} }
@@ -1071,11 +1026,15 @@ async fn poll_node_graph_evaluation() {
crate::NODE_GRAPH_ERROR_DISPLAYED.store(false, Ordering::SeqCst); crate::NODE_GRAPH_ERROR_DISPLAYED.store(false, Ordering::SeqCst);
} }
handle.process_messages(messages, editor); // Send each `FrontendMessage` to the JavaScript frontend
for response in messages.into_iter().flat_map(|message| editor.handle_message(message)) {
handle.send_frontend_message_to_js(response);
}
// If the editor cannot be borrowed then it has encountered a panic - we should just ignore new dispatches // If the editor cannot be borrowed then it has encountered a panic - we should just ignore new dispatches
}); });
} }
fn auto_save_all_documents() { fn auto_save_all_documents() {
// Process no further messages after a crash to avoid spamming the console // Process no further messages after a crash to avoid spamming the console
if EDITOR_HAS_CRASHED.load(Ordering::SeqCst) { if EDITOR_HAS_CRASHED.load(Ordering::SeqCst) {
+2 -2
View File
@@ -61,7 +61,7 @@ pub fn panic_hook(info: &panic::PanicHookInfo) {
/text>"# /text>"#
// It's a mystery why the `/text>` tag above needs to be missing its `<`, but when it exists it prints the `<` character in the text. However this works with it removed. // It's a mystery why the `/text>` tag above needs to be missing its `<`, but when it exists it prints the `<` character in the text. However this works with it removed.
.to_string(); .to_string();
handle.send_frontend_message_to_js_rust_proxy(FrontendMessage::UpdateDocumentArtwork { svg: error }); handle.send_frontend_message_to_js(FrontendMessage::UpdateDocumentArtwork { svg: error });
}); });
} }
@@ -75,7 +75,7 @@ pub fn panic_hook(info: &panic::PanicHookInfo) {
EDITOR_HANDLE.with(|editor_handle| { EDITOR_HANDLE.with(|editor_handle| {
let mut guard = editor_handle.lock(); let mut guard = editor_handle.lock();
if let Ok(Some(handle)) = guard.as_deref_mut() { if let Ok(Some(handle)) = guard.as_deref_mut() {
handle.send_frontend_message_to_js_rust_proxy(FrontendMessage::DisplayDialogPanic { panic_info: info.to_string() }); handle.send_frontend_message_to_js(FrontendMessage::DisplayDialogPanic { panic_info: info.to_string() });
} }
}); });
} }
+1 -1
View File
@@ -11,7 +11,7 @@ pub fn receive_native_message(buffer: ArrayBuffer) {
Ok(messages) => { Ok(messages) => {
let callback = move |handle: &mut EditorHandle| { let callback = move |handle: &mut EditorHandle| {
for message in messages { for message in messages {
handle.send_frontend_message_to_js_rust_proxy(message); handle.send_frontend_message_to_js(message);
} }
}; };
editor_api::handle(callback); editor_api::handle(callback);
@@ -162,13 +162,7 @@ pub fn draw_nodes(nodes: &Vec<FrontendNodeToRender>) -> Table<Graphic> {
} }
} }
// for text_row in node_text.iter_mut() {
// text_row.element.style.fill = Fill::Solid(Color::WHITE);
// }
let node_text_row = TableRow::new_from_element(Graphic::Vector(node_text)); let node_text_row = TableRow::new_from_element(Graphic::Vector(node_text));
// node_text_row.transform.left_apply_transform(&DAffine2::from_translation(DVec2::new(x + 8., y + 8.)));
// log::debug!("node_text_row {:?}", node_text_row.transform);
node_table.push(node_text_row); node_table.push(node_text_row);
// Add black clipping path to view text in node // Add black clipping path to view text in node
@@ -193,12 +187,12 @@ pub fn draw_nodes(nodes: &Vec<FrontendNodeToRender>) -> Table<Graphic> {
ports_table.push(row); ports_table.push(row);
} }
if let Some(primary_output) = &frontend_node.primary_output { if let Some(primary_output) = &frontend_node.primary_output {
let mut row = port_row(&primary_output.data_type, true); let mut row = port_row(&primary_output.data_type, primary_output.connected);
row.transform = DAffine2::from_translation(DVec2::new(5. * GRID_SIZE, 12.)); row.transform = DAffine2::from_translation(DVec2::new(5. * GRID_SIZE, 12.));
ports_table.push(row); ports_table.push(row);
} }
for (index, secondary_output) in frontend_node.secondary_outputs.iter().enumerate() { for (index, secondary_output) in frontend_node.secondary_outputs.iter().enumerate() {
let mut row = port_row(&secondary_output.data_type, true); let mut row = port_row(&secondary_output.data_type, secondary_output.connected);
row.transform = DAffine2::from_translation(DVec2::new(5. * GRID_SIZE, 12. + GRID_SIZE * (index + 1) as f64)); row.transform = DAffine2::from_translation(DVec2::new(5. * GRID_SIZE, 12. + GRID_SIZE * (index + 1) as f64));
ports_table.push(row); ports_table.push(row);
} }
@@ -345,7 +339,12 @@ pub fn draw_layers(nodes: &mut NodeGraphOverlayData) -> (Table<Graphic>, Table<G
} }
let top_port = BezPath::from_svg("M0,6.953l2.521,-1.694a2.649,2.649,0,0,1,2.959,0l2.52,1.694v5.047h-8z").unwrap(); let top_port = BezPath::from_svg("M0,6.953l2.521,-1.694a2.649,2.649,0,0,1,2.959,0l2.52,1.694v5.047h-8z").unwrap();
let mut vector = Vector::from_bezpath(top_port); let mut vector = Vector::from_bezpath(top_port);
vector.style.fill = Fill::Solid(frontend_layer.output.data_type.data_color()); vector.style.fill = if frontend_layer.output.connected {
Fill::Solid(frontend_layer.output.data_type.data_color())
} else {
Fill::Solid(frontend_layer.output.data_type.data_color_dim())
};
let mut top_port = TableRow::new_from_element(vector); let mut top_port = TableRow::new_from_element(vector);
top_port.transform = DAffine2::from_translation(DVec2::new(frontend_layer.position.x as f64 * 24. + GRID_SIZE * 2. - 4., layer_position.y - 12.)); top_port.transform = DAffine2::from_translation(DVec2::new(frontend_layer.position.x as f64 * 24. + GRID_SIZE * 2. - 4., layer_position.y - 12.));
ports_table.push(top_port); ports_table.push(top_port);
@@ -181,6 +181,7 @@ pub struct FrontendGraphOutput {
#[serde(rename = "dataType")] #[serde(rename = "dataType")]
pub data_type: FrontendGraphDataType, pub data_type: FrontendGraphDataType,
pub name: String, pub name: String,
pub connected: bool,
} }
#[derive(Clone, Debug, Default, PartialEq, Hash, dyn_any::DynAny, serde::Serialize, serde::Deserialize, specta::Type)] #[derive(Clone, Debug, Default, PartialEq, Hash, dyn_any::DynAny, serde::Serialize, serde::Deserialize, specta::Type)]
+5
View File
@@ -302,6 +302,7 @@ impl Render for Graphic {
Graphic::RasterGPU(table) => table.to_graphic(), Graphic::RasterGPU(table) => table.to_graphic(),
Graphic::Color(table) => table.to_graphic(), Graphic::Color(table) => table.to_graphic(),
Graphic::Gradient(table) => table.to_graphic(), Graphic::Gradient(table) => table.to_graphic(),
Graphic::Typography(table) => table.to_graphic(),
} }
} }
@@ -1696,6 +1697,10 @@ impl Render for Table<Typography> {
} }
} }
} }
fn to_graphic(self) -> Graphic {
Graphic::Typography(self)
}
} }
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]