fix animation

This commit is contained in:
Adam
2025-07-16 18:36:22 -07:00
parent 04ff7b75e5
commit c2815ea0e3
30 changed files with 258 additions and 386 deletions

View File

@@ -16,7 +16,6 @@ pub struct Dispatcher {
#[derive(Debug, Default)]
pub struct DispatcherMessageHandlers {
animation_message_handler: AnimationMessageHandler,
broadcast_message_handler: BroadcastMessageHandler,
debug_message_handler: DebugMessageHandler,
dialog_message_handler: DialogMessageHandler,
@@ -59,7 +58,7 @@ const SIDE_EFFECT_FREE_MESSAGES: &[MessageDiscriminant] = &[
MessageDiscriminant::Frontend(FrontendMessageDiscriminant::UpdateDocumentLayerStructure),
MessageDiscriminant::Frontend(FrontendMessageDiscriminant::TriggerFontLoad),
];
const DEBUG_MESSAGE_BLOCK_LIST: &[MessageDiscriminant] = &[MessageDiscriminant::Broadcast(BroadcastMessageDiscriminant::TriggerEvent(BroadcastEventDiscriminant::AnimationFrame))];
const DEBUG_MESSAGE_BLOCK_LIST: &[MessageDiscriminant] = &[MessageDiscriminant::InputPreprocessor(InputPreprocessorMessageDiscriminant::CurrentTime)];
// 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"];
@@ -163,27 +162,6 @@ impl Dispatcher {
let clear_message = PortfolioMessage::ClearIntrospectedData.into();
Self::schedule_execution(&mut self.message_queues, true, [clear_message]);
}
Message::NoOp => {}
Message::Init => {
// Load persistent data from the browser database
queue.add(FrontendMessage::TriggerLoadFirstAutoSaveDocument);
queue.add(FrontendMessage::TriggerLoadPreferences);
// Display the menu bar at the top of the window
queue.add(MenuBarMessage::SendLayout);
// Send the information for tooltips and categories for each node/input.
queue.add(FrontendMessage::SendUIMetadata {
node_descriptions: document_node_definitions::collect_node_descriptions(),
node_types: document_node_definitions::collect_node_types(),
});
// Finish loading persistent data from the browser database
queue.add(FrontendMessage::TriggerLoadRestAutoSaveDocuments);
}
Message::Animation(message) => {
self.message_handlers.animation_message_handler.process_message(message, &mut queue, ());
}
Message::Broadcast(message) => self.message_handlers.broadcast_message_handler.process_message(message, &mut queue, ()),
Message::Debug(message) => {
self.message_handlers.debug_message_handler.process_message(message, &mut queue, ());
@@ -238,8 +216,6 @@ impl Dispatcher {
let current_tool = &self.message_handlers.tool_message_handler.tool_state.tool_data.active_tool_type;
let message_logging_verbosity = self.message_handlers.debug_message_handler.message_logging_verbosity;
let reset_node_definitions_on_open = self.message_handlers.portfolio_message_handler.reset_node_definitions_on_open;
let timing_information = self.message_handlers.animation_message_handler.timing_information();
let animation = &self.message_handlers.animation_message_handler;
self.message_handlers.portfolio_message_handler.process_message(
message,
@@ -250,8 +226,6 @@ impl Dispatcher {
current_tool,
message_logging_verbosity,
reset_node_definitions_on_open,
timing_information,
animation,
},
);
}
@@ -283,37 +257,6 @@ impl Dispatcher {
Message::Batched { messages } => {
messages.iter().for_each(|message| self.handle_message(message.to_owned(), false));
}
Message::StartBuffer => {
self.buffered_queue = Some(std::mem::take(&mut self.message_queues));
}
Message::EndBuffer { render_metadata } => {
// Assign the message queue to the currently buffered queue
if let Some(buffered_queue) = self.buffered_queue.take() {
self.cleanup_queues(false);
assert!(self.message_queues.is_empty(), "message queues are always empty when ending a buffer");
self.message_queues = buffered_queue;
};
let graphene_std::renderer::RenderMetadata {
upstream_footprints: footprints,
local_transforms,
first_instance_source_id,
click_targets,
clip_targets,
} = render_metadata;
// Run these update state messages immediately
let messages = [
DocumentMessage::UpdateUpstreamTransforms {
upstream_footprints: footprints,
local_transforms,
first_instance_source_id,
},
DocumentMessage::UpdateClickTargets { click_targets },
DocumentMessage::UpdateClipTargets { clip_targets },
];
Self::schedule_execution(&mut self.message_queues, false, messages.map(Message::from));
}
}
// If there are child messages, append the queue to the list of queues
@@ -329,7 +272,6 @@ impl Dispatcher {
// TODO: Reduce the number of heap allocations
let mut list = Vec::new();
list.extend(self.message_handlers.dialog_message_handler.actions());
list.extend(self.message_handlers.animation_message_handler.actions());
list.extend(self.message_handlers.input_preprocessor_message_handler.actions());
list.extend(self.message_handlers.key_mapping_message_handler.actions());
list.extend(self.message_handlers.debug_message_handler.actions());

View File

@@ -1,17 +0,0 @@
use crate::messages::prelude::*;
use super::animation_message_handler::AnimationTimeMode;
#[impl_message(Message, Animation)]
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]
pub enum AnimationMessage {
ToggleLivePreview,
EnableLivePreview,
DisableLivePreview,
RestartAnimation,
SetFrameIndex { frame: f64 },
SetTime { time: f64 },
UpdateTime,
IncrementFrameCounter,
SetAnimationTimeMode { animation_time_mode: AnimationTimeMode },
}

View File

@@ -1,134 +0,0 @@
use std::time::Duration;
use crate::messages::prelude::*;
use super::TimingInformation;
#[derive(PartialEq, Clone, Default, Debug, serde::Serialize, serde::Deserialize)]
pub enum AnimationTimeMode {
#[default]
TimeBased,
FrameBased,
}
#[derive(Default, Debug, Clone, PartialEq)]
enum AnimationState {
#[default]
Stopped,
Playing {
start: f64,
},
Paused {
start: f64,
pause_time: f64,
},
}
#[derive(Default, Debug, Clone, PartialEq, ExtractField)]
pub struct AnimationMessageHandler {
/// Used to re-send the UI on the next frame after playback starts
live_preview_recently_zero: bool,
timestamp: f64,
frame_index: f64,
animation_state: AnimationState,
fps: f64,
animation_time_mode: AnimationTimeMode,
}
impl AnimationMessageHandler {
pub(crate) fn timing_information(&self) -> TimingInformation {
let animation_time = self.timestamp - self.animation_start();
let animation_time = match self.animation_time_mode {
AnimationTimeMode::TimeBased => Duration::from_millis(animation_time as u64),
AnimationTimeMode::FrameBased => Duration::from_secs((self.frame_index / self.fps) as u64),
};
TimingInformation { time: self.timestamp, animation_time }
}
pub(crate) fn animation_start(&self) -> f64 {
match self.animation_state {
AnimationState::Stopped => self.timestamp,
AnimationState::Playing { start } => start,
AnimationState::Paused { start, pause_time } => start + self.timestamp - pause_time,
}
}
pub fn is_playing(&self) -> bool {
matches!(self.animation_state, AnimationState::Playing { .. })
}
}
#[message_handler_data]
impl MessageHandler<AnimationMessage, ()> for AnimationMessageHandler {
fn process_message(&mut self, message: AnimationMessage, responses: &mut VecDeque<Message>, _: ()) {
match message {
AnimationMessage::ToggleLivePreview => match self.animation_state {
AnimationState::Stopped => responses.add(AnimationMessage::EnableLivePreview),
AnimationState::Playing { .. } => responses.add(AnimationMessage::DisableLivePreview),
AnimationState::Paused { .. } => responses.add(AnimationMessage::EnableLivePreview),
},
AnimationMessage::EnableLivePreview => {
self.animation_state = AnimationState::Playing { start: self.animation_start() };
// Update the restart and pause/play buttons
responses.add(PortfolioMessage::UpdateDocumentWidgets);
}
AnimationMessage::DisableLivePreview => {
match self.animation_state {
AnimationState::Stopped => (),
AnimationState::Playing { start } => self.animation_state = AnimationState::Paused { start, pause_time: self.timestamp },
AnimationState::Paused { .. } => (),
}
// Update the restart and pause/play buttons
responses.add(PortfolioMessage::UpdateDocumentWidgets);
}
AnimationMessage::SetFrameIndex { frame } => {
self.frame_index = frame;
responses.add(PortfolioMessage::EvaluateActiveDocumentWithThumbnails);
// Update the restart and pause/play buttons
responses.add(PortfolioMessage::UpdateDocumentWidgets);
}
AnimationMessage::SetTime { time } => {
self.timestamp = time;
responses.add(AnimationMessage::UpdateTime);
}
AnimationMessage::IncrementFrameCounter => {
if self.is_playing() {
self.frame_index += 1.;
responses.add(AnimationMessage::UpdateTime);
}
}
AnimationMessage::UpdateTime => {
if self.is_playing() {
responses.add(PortfolioMessage::EvaluateActiveDocumentWithThumbnails);
if self.live_preview_recently_zero {
// Update the restart and pause/play buttons
responses.add(PortfolioMessage::UpdateDocumentWidgets);
self.live_preview_recently_zero = false;
}
}
}
AnimationMessage::RestartAnimation => {
self.frame_index = 0.;
self.animation_state = match self.animation_state {
AnimationState::Playing { .. } => AnimationState::Playing { start: self.timestamp },
_ => AnimationState::Stopped,
};
self.live_preview_recently_zero = true;
responses.add(PortfolioMessage::EvaluateActiveDocumentWithThumbnails);
// Update the restart and pause/play buttons
responses.add(PortfolioMessage::UpdateDocumentWidgets);
}
AnimationMessage::SetAnimationTimeMode { animation_time_mode } => {
self.animation_time_mode = animation_time_mode;
}
}
}
advertise_actions!(AnimationMessageDiscriminant;
ToggleLivePreview,
SetFrameIndex,
RestartAnimation,
);
}

View File

@@ -1,9 +0,0 @@
mod animation_message;
mod animation_message_handler;
#[doc(inline)]
pub use animation_message::{AnimationMessage, AnimationMessageDiscriminant};
#[doc(inline)]
pub use animation_message_handler::AnimationMessageHandler;
pub use graphene_std::application_io::TimingInformation;

View File

@@ -437,8 +437,8 @@ pub fn input_mappings() -> Mapping {
entry!(KeyDown(Digit1); modifiers=[Alt], action_dispatch=DebugMessage::MessageNames),
entry!(KeyDown(Digit2); modifiers=[Alt], action_dispatch=DebugMessage::MessageContents),
// AnimationMessage
entry!(KeyDown(Space); modifiers=[Shift], action_dispatch=AnimationMessage::ToggleLivePreview),
entry!(KeyDown(Home); modifiers=[Shift], action_dispatch=AnimationMessage::RestartAnimation),
entry!(KeyDown(Space); modifiers=[Shift], action_dispatch=DocumentMessage::ToggleAnimation),
entry!(KeyDown(Home); modifiers=[Shift], action_dispatch=DocumentMessage::RestartAnimation),
];
let (mut key_up, mut key_down, mut key_up_no_repeat, mut key_down_no_repeat, mut double_click, mut wheel_scroll, mut pointer_move) = mappings;

View File

@@ -12,6 +12,6 @@ pub enum InputPreprocessorMessage {
PointerDown { editor_mouse_state: EditorMouseState, modifier_keys: ModifierKeys },
PointerMove { editor_mouse_state: EditorMouseState, modifier_keys: ModifierKeys },
PointerUp { editor_mouse_state: EditorMouseState, modifier_keys: ModifierKeys },
CurrentTime { timestamp: u64 },
CurrentTime { timestamp: f64 },
WheelScroll { editor_mouse_state: EditorMouseState, modifier_keys: ModifierKeys },
}

View File

@@ -97,7 +97,7 @@ impl MessageHandler<InputPreprocessorMessage, InputPreprocessorMessageContext> f
self.translate_mouse_event(mouse_state, false, responses);
}
InputPreprocessorMessage::CurrentTime { timestamp } => {
self.time = timestamp as f64;
self.time = timestamp;
}
InputPreprocessorMessage::WheelScroll { editor_mouse_state, modifier_keys } => {
self.update_states_of_modifier_keys(modifier_keys, keyboard_platform, responses);

View File

@@ -4,9 +4,6 @@ use graphite_proc_macros::*;
#[impl_message]
#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
pub enum Message {
NoOp,
Init,
Batched(Box<[Message]>),
// Adds any subsequent messages to the queue
StartEvaluationQueue,
// Stop adding messages to the queue.
@@ -15,8 +12,6 @@ pub enum Message {
#[serde(skip)]
ProcessEvaluationQueue(graphene_std::renderer::RenderMetadata, IntrospectionResponse),
#[child]
Animation(AnimationMessage),
#[child]
Broadcast(BroadcastMessage),
#[child]
Debug(DebugMessage),
@@ -46,10 +41,6 @@ pub enum Message {
Batched {
messages: Box<[Message]>,
},
StartBuffer,
EndBuffer {
render_metadata: RenderMetadata,
},
}
/// Provides an impl of `specta::Type` for `MessageDiscriminant`, the struct created by `impl_message`.

View File

@@ -1,6 +1,5 @@
//! The root-level messages forming the first layer of the message system architecture.
pub mod animation;
pub mod broadcast;
pub mod debug;
pub mod dialog;

View File

@@ -170,6 +170,8 @@ pub enum DocumentMessage {
RepeatedAbortTransaction {
undo_count: usize,
},
ToggleAnimation,
RestartAnimation,
ToggleLayerExpansion {
id: NodeId,
recursive: bool,

View File

@@ -35,17 +35,13 @@ use graphene_std::uuid::NodeId;
use graphene_std::vector::PointId;
use graphene_std::vector::click_target::{ClickTarget, ClickTargetType};
use graphene_std::vector::style::ViewMode;
use std::time::Duration;
#[derive(ExtractField)]
pub struct DocumentMessageData<'a> {
pub struct DocumentMessageContext<'a> {
pub ipp: &'a InputPreprocessorMessageHandler,
pub persistent_data: &'a PersistentData,
pub current_tool: &'a ToolType,
pub preferences: &'a PreferencesMessageHandler,
pub device_pixel_ratio: f64,
// pub introspected_inputs: &HashMap<CompiledProtonodeInput, Arc<dyn std::any::Any + Send + Sync>>,
// pub downcasted_inputs: &mut HashMap<CompiledProtonodeInput, TaggedValue>,
}
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize, ExtractField)]
@@ -101,6 +97,9 @@ pub struct DocumentMessageHandler {
// Fields omitted from the saved document format
// =============================================
//
/// Animation state for when the animation button was pressed/paused
#[serde(skip)]
pub animation_state: AnimationState,
/// Path to network currently viewed in the node graph overlay. This will eventually be stored in each panel, so that multiple panels can refer to different networks
#[serde(skip)]
pub breadcrumb_network_path: Vec<NodeId>,
@@ -164,16 +163,16 @@ impl Default for DocumentMessageHandler {
auto_saved_hash: None,
layer_range_selection_reference: None,
is_loaded: false,
animation_state: AnimationState::Stopped,
}
}
}
#[message_handler_data]
impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessageHandler {
fn process_message(&mut self, message: DocumentMessage, responses: &mut VecDeque<Message>, data: DocumentMessageData) {
let DocumentMessageData {
impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMessageHandler {
fn process_message(&mut self, message: DocumentMessage, responses: &mut VecDeque<Message>, data: DocumentMessageContext) {
let DocumentMessageContext {
ipp,
persistent_data,
current_tool,
preferences,
device_pixel_ratio,
@@ -217,7 +216,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
);
}
DocumentMessage::PropertiesPanel(message) => {
let properties_panel_message_handler_data = super::properties_panel::PropertiesPanelMessageHandlerData {
let context = super::properties_panel::PropertiesPanelMessageContext {
network_interface: &mut self.network_interface,
selection_network_path: &self.selection_network_path,
document_name: self.name.as_str(),
@@ -1284,6 +1283,27 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
responses.add(NodeGraphMessage::SendGraph);
}
DocumentMessage::ToggleAnimation => {
match self.animation_state {
AnimationState::Stopped => {
self.animation_state = AnimationState::Playing { start: ipp.time };
responses.add(PortfolioMessage::EvaluateActiveDocumentWithThumbnails);
}
AnimationState::Playing { start } => self.animation_state = AnimationState::Paused { start, pause_time: ipp.time },
AnimationState::Paused { start, .. } => {
self.animation_state = AnimationState::Playing { start };
responses.add(PortfolioMessage::EvaluateActiveDocumentWithThumbnails);
}
}
responses.add(PortfolioMessage::UpdateDocumentWidgets);
}
DocumentMessage::RestartAnimation => {
self.animation_state = match self.animation_state {
AnimationState::Playing { .. } => AnimationState::Playing { start: ipp.time },
_ => AnimationState::Stopped,
};
responses.add(PortfolioMessage::UpdateDocumentWidgets);
}
DocumentMessage::ToggleSelectedLocked => responses.add(NodeGraphMessage::ToggleSelectedLocked),
DocumentMessage::ToggleSelectedVisibility => {
responses.add(NodeGraphMessage::ToggleSelectedVisibility);
@@ -1302,17 +1322,6 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
self.snapping_state.snapping_enabled = !self.snapping_state.snapping_enabled;
responses.add(PortfolioMessage::UpdateDocumentWidgets);
}
// DocumentMessage::ToggleAnimation => match self.animation_state {
// AnimationState::Stopped => {self.animation_state = AnimationState::Playing { start: ipp.time }; responses.add(PortfolioMessage::EvaluateActiveDocument)},
// AnimationState::Playing { start } => self.animation_state = AnimationState::Paused { start , pause_time: ipp.time },
// AnimationState::Paused { start, .. } => {self.animation_state = AnimationState::Playing { start }; responses.add(PortfolioMessage::EvaluateActiveDocument)},
// },
// DocumentMessage::RestartAnimation => {
// self.animation_state = match self.animation_state {
// AnimationState::Playing { .. } => AnimationState::Playing { start: ipp.time },
// _ => AnimationState::Stopped,
// };
// }
DocumentMessage::UpdateUpstreamTransforms {
upstream_footprints,
local_transforms,
@@ -1320,6 +1329,9 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
} => {
self.network_interface.update_transforms(upstream_footprints, local_transforms);
self.network_interface.update_first_instance_source_id(first_instance_source_id);
if matches!(self.animation_state, AnimationState::Playing { .. }) {
responses.add(PortfolioMessage::EvaluateActiveDocumentWithThumbnails);
}
}
DocumentMessage::UpdateClickTargets { click_targets } => {
// TODO: Allow non layer nodes to have click targets
@@ -1582,6 +1594,14 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
}
impl DocumentMessageHandler {
pub fn animation_time(&self, ipp: &InputPreprocessorMessageHandler) -> f64 {
match self.animation_state {
AnimationState::Stopped => 0.,
AnimationState::Playing { start } => ipp.time - start,
AnimationState::Paused { start, pause_time } => pause_time - start,
}
}
/// Runs an intersection test with all layers and a viewport space quad
pub fn intersect_quad<'a>(&'a self, viewport_quad: graphene_std::renderer::Quad, ipp: &InputPreprocessorMessageHandler) -> impl Iterator<Item = LayerNodeIdentifier> + use<'a> {
let document_to_viewport = self.navigation_handler.calculate_offset_transform(ipp.viewport_bounds.center(), &self.document_ptz);
@@ -2113,7 +2133,9 @@ impl DocumentMessageHandler {
}
}
pub fn update_document_widgets(&self, responses: &mut VecDeque<Message>, animation_is_playing: bool, time: Duration) {
pub fn update_document_widgets(&self, responses: &mut VecDeque<Message>, ipp: &InputPreprocessorMessageHandler) {
let animation_is_playing = matches!(self.animation_state, AnimationState::Playing { .. });
// Document mode (dropdown menu at the left of the bar above the viewport, before the tool options)
let document_mode_layout = WidgetLayout::new(vec![LayoutGroup::Row {
@@ -2153,14 +2175,14 @@ impl DocumentMessageHandler {
let mut widgets = vec![
IconButton::new("PlaybackToStart", 24)
.tooltip("Restart Animation")
.tooltip_shortcut(action_keys!(AnimationMessageDiscriminant::RestartAnimation))
.on_update(|_| AnimationMessage::RestartAnimation.into())
.disabled(time == Duration::ZERO)
.tooltip_shortcut(action_keys!(DocumentMessageDiscriminant::RestartAnimation))
.on_update(|_| DocumentMessage::RestartAnimation.into())
.disabled(self.animation_time(ipp) == 0.)
.widget_holder(),
IconButton::new(if animation_is_playing { "PlaybackPause" } else { "PlaybackPlay" }, 24)
.tooltip(if animation_is_playing { "Pause Animation" } else { "Play Animation" })
.tooltip_shortcut(action_keys!(AnimationMessageDiscriminant::ToggleLivePreview))
.on_update(|_| AnimationMessage::ToggleLivePreview.into())
.tooltip_shortcut(action_keys!(DocumentMessageDiscriminant::ToggleAnimation))
.on_update(|_| DocumentMessage::ToggleAnimation.into())
.widget_holder(),
Separator::new(SeparatorType::Unrelated).widget_holder(),
CheckboxInput::new(self.overlays_visibility_settings.all)
@@ -3140,6 +3162,18 @@ impl Iterator for ClickXRayIter<'_> {
}
}
#[derive(Default, Debug, Clone, PartialEq)]
pub enum AnimationState {
#[default]
Stopped,
Playing {
start: f64,
},
Paused {
start: f64,
pause_time: f64,
},
}
// #[cfg(test)]
// mod document_message_handler_tests {
// use super::*;

View File

@@ -8,7 +8,6 @@ use crate::messages::portfolio::document::utility_types::network_interface::{
DocumentNodeMetadata, DocumentNodePersistentMetadata, InputMetadata, NodeNetworkInterface, NodeNetworkMetadata, NodeNetworkPersistentMetadata, NodeTemplate, NodeTypePersistentMetadata,
NumberInputSettings, Vec2InputSettings, WidgetOverride,
};
use crate::messages::portfolio::utility_types::PersistentData;
use crate::messages::prelude::Message;
use glam::DVec2;
use graph_craft::ProtoNodeIdentifier;
@@ -28,7 +27,6 @@ use graphene_std::*;
use std::collections::{HashMap, HashSet, VecDeque};
pub struct NodePropertiesContext<'a> {
pub persistent_data: &'a PersistentData,
pub responses: &'a mut VecDeque<Message>,
pub network_interface: &'a mut NodeNetworkInterface,
pub selection_network_path: &'a [NodeId],

View File

@@ -2305,7 +2305,7 @@ impl NodeGraphMessageHandler {
.find(|error| match &error.original_location {
graph_craft::proto::OriginalLocation::Value(_) => false,
graph_craft::proto::OriginalLocation::Node(prefixed_node_path) => {
let (prefix, node_path) = prefixed_node_path.split_first().unwrap();
let (_, node_path) = prefixed_node_path.split_first().unwrap();
node_path == &node_id_path
}
})
@@ -2314,7 +2314,7 @@ impl NodeGraphMessageHandler {
if self.node_graph_errors.iter().any(|error| match &error.original_location {
graph_craft::proto::OriginalLocation::Value(_) => false,
graph_craft::proto::OriginalLocation::Node(prefixed_node_path) => {
let (prefix, node_path) = prefixed_node_path.split_first().unwrap();
let (_, node_path) = prefixed_node_path.split_first().unwrap();
node_path.starts_with(&node_id_path)
}
}) {

View File

@@ -4,4 +4,4 @@ mod properties_panel_message_handler;
#[doc(inline)]
pub use properties_panel_message::{PropertiesPanelMessage, PropertiesPanelMessageDiscriminant};
#[doc(inline)]
pub use properties_panel_message_handler::{PropertiesPanelMessageHandler, PropertiesPanelMessageHandlerData};
pub use properties_panel_message_handler::{PropertiesPanelMessageContext, PropertiesPanelMessageHandler};

View File

@@ -3,11 +3,10 @@ use graphene_std::uuid::NodeId;
use crate::messages::layout::utility_types::widget_prelude::*;
use crate::messages::portfolio::document::node_graph::document_node_definitions::NodePropertiesContext;
use crate::messages::portfolio::document::utility_types::network_interface::NodeNetworkInterface;
use crate::messages::portfolio::utility_types::PersistentData;
use crate::messages::prelude::*;
use crate::messages::portfolio::document::utility_types::network_interface::NodeNetworkInterface;
pub struct PropertiesPanelMessageHandlerData<'a> {
#[derive(ExtractField)]
pub struct PropertiesPanelMessageContext<'a> {
pub network_interface: &'a mut NodeNetworkInterface,
pub selection_network_path: &'a [NodeId],
pub document_name: &'a str,
@@ -23,7 +22,7 @@ impl MessageHandler<PropertiesPanelMessage, PropertiesPanelMessageContext<'_>> f
network_interface,
selection_network_path,
document_name,
} = data;
} = context;
match message {
PropertiesPanelMessage::Clear => {
@@ -34,7 +33,6 @@ impl MessageHandler<PropertiesPanelMessage, PropertiesPanelMessageContext<'_>> f
}
PropertiesPanelMessage::Refresh => {
let mut node_properties_context = NodePropertiesContext {
persistent_data,
responses,
network_interface,
selection_network_path,

View File

@@ -2535,7 +2535,7 @@ impl NodeNetworkInterface {
pub fn newly_loaded_input_wire(&mut self, input: &InputConnector, graph_wire_style: &GraphWireStyle, network_path: &[NodeId]) -> Option<WirePathUpdate> {
match self.cached_wire(input, network_path) {
Some(loaded) => None,
Some(_) => None,
None => {
self.load_wire(input, graph_wire_style, network_path);
self.cached_wire(input, network_path).cloned()

View File

@@ -22,6 +22,7 @@ pub enum PortfolioMessage {
#[child]
Spreadsheet(SpreadsheetMessage),
Init,
// Sends a request to compile the network. Should occur when any value, preference, or font changes
CompileActiveDocument,
// Sends a request to evaluate the network. Should occur when any context value changes.
@@ -54,10 +55,6 @@ pub enum PortfolioMessage {
// Introspected data is cleared after queued messages are complete
ClearIntrospectedData,
ProcessThumbnails,
DocumentPassMessage {
document_id: DocumentId,
message: DocumentMessage,
},
AutoSaveActiveDocument,
AutoSaveAllDocuments,
AutoSaveDocument {

View File

@@ -10,6 +10,7 @@ use crate::messages::frontend::utility_types::{ExportBounds, FrontendDocumentDet
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::utility_types::clipboards::{Clipboard, CopyBufferEntry, INTERNAL_CLIPBOARD_COUNT};
use crate::messages::portfolio::document::utility_types::nodes::SelectedNodes;
use crate::messages::portfolio::document::utility_types::wires::{GraphWireStyle, WireSNIUpdate};
@@ -23,7 +24,6 @@ use glam::{DAffine2, DVec2};
use graph_craft::document::value::EditorMetadata;
use graph_craft::document::{InputConnector, NodeInput, OutputConnector};
use graphene_std::EditorContext;
use graphene_std::application_io::TimingInformation;
use graphene_std::memo::MonitorIntrospectResult;
use graphene_std::renderer::{Quad, RenderMetadata};
use graphene_std::text::Font;
@@ -38,8 +38,6 @@ pub struct PortfolioMessageContext<'a> {
pub current_tool: &'a ToolType,
pub message_logging_verbosity: MessageLoggingVerbosity,
pub reset_node_definitions_on_open: bool,
pub timing_information: TimingInformation,
pub animation: &'a AnimationMessageHandler,
}
#[derive(Debug, Default, ExtractField)]
@@ -74,8 +72,6 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
current_tool,
message_logging_verbosity,
reset_node_definitions_on_open,
timing_information,
animation,
} = context;
match message {
@@ -127,9 +123,8 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
PortfolioMessage::Document(message) => {
if let Some(document_id) = self.active_document_id {
if let Some(document) = self.documents.get_mut(&document_id) {
let document_inputs = DocumentMessageData {
let document_inputs = DocumentMessageContext {
ipp,
persistent_data: &self.persistent_data,
current_tool,
preferences,
device_pixel_ratio: self.device_pixel_ratio.unwrap_or(1.),
@@ -138,7 +133,6 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
}
}
}
// Messages
PortfolioMessage::Init => {
// Load persistent data from the browser database
@@ -157,18 +151,6 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
// Finish loading persistent data from the browser database
responses.add(FrontendMessage::TriggerLoadRestAutoSaveDocuments);
}
PortfolioMessage::DocumentPassMessage { document_id, message } => {
if let Some(document) = self.documents.get_mut(&document_id) {
let document_inputs = DocumentMessageData {
ipp,
persistent_data: &self.persistent_data,
current_tool,
preferences,
device_pixel_ratio: self.device_pixel_ratio.unwrap_or(1.),
};
document.process_message(message, responses, document_inputs)
}
}
PortfolioMessage::AutoSaveActiveDocument => {
if let Some(document_id) = self.active_document_id {
if let Some(document) = self.active_document_mut() {
@@ -831,25 +813,17 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
return;
};
// let animation_time = match animation.timing_information().animation_time {
// AnimationState::Stopped => 0.,
// AnimationState::Playing { start } => ipp.time - start,
// AnimationState::Paused { start, pause_time } => pause_time - start,
// };
let mut context = EditorContext::default();
context.footprint = Some(Footprint {
transform: document.metadata().document_to_viewport,
resolution: ipp.viewport_bounds.size().as_uvec2(),
quality: RenderQuality::Full,
});
context.animation_time = Some(timing_information.animation_time.as_secs_f64());
context.animation_time = Some(document.animation_time(ipp));
context.real_time = Some(ipp.time);
context.downstream_transform = Some(DAffine2::IDENTITY);
let nodes_to_try_render = self.visible_nodes_to_try_render(ipp, &preferences.graph_wire_style);
self.executor.submit_node_graph_evaluation(context, None, None, nodes_to_try_render);
self.executor.submit_node_graph_evaluation(context, None, None, nodes_to_introspect);
}
PortfolioMessage::ProcessEvaluationResponse {
evaluation_metadata,
@@ -876,10 +850,6 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
responses.add(DocumentMessage::RenderScrollbars);
responses.add(DocumentMessage::RenderRulers);
responses.add(OverlaysMessage::Draw);
// match document.animation_state {
// AnimationState::Playing { .. } => responses.add(PortfolioMessage::EvaluateActiveDocument),
// _ => {}
// };
}
// PortfolioMessage::IntrospectActiveDocument { nodes_to_introspect } => {
// self.executor.submit_node_graph_introspection(nodes_to_introspect);
@@ -1081,7 +1051,7 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
}
PortfolioMessage::UpdateDocumentWidgets => {
if let Some(document) = self.active_document() {
document.update_document_widgets(responses, animation.is_playing(), timing_information.animation_time);
document.update_document_widgets(responses, ipp);
}
}
PortfolioMessage::UpdateOpenDocumentsList => {
@@ -1283,8 +1253,6 @@ impl PortfolioMessageHandler {
.network_interface
.viewport_loaded_thumbnail_position(&input_connector, graph_wire_style, &document.breadcrumb_network_path)
{
log::debug!("viewport position: {:?}, input: {:?}", viewport_position, input_connector);
let in_view = viewport_position.x > 0.0 && viewport_position.y > 0.0 && viewport_position.x < ipp.viewport_bounds()[1].x && viewport_position.y < ipp.viewport_bounds()[1].y;
if in_view {
let Some(protonode) = document.network_interface.protonode_from_input(&input_connector, &document.breadcrumb_network_path) else {

View File

@@ -2,7 +2,6 @@
pub use crate::utility_traits::{ActionList, AsMessage, HierarchicalTree, MessageHandler, ToDiscriminant, TransitiveChild};
pub use crate::utility_types::{DebugMessageTree, MessageData};
// Message, MessageData, MessageDiscriminant, MessageHandler
pub use crate::messages::animation::{AnimationMessage, AnimationMessageDiscriminant, AnimationMessageHandler};
pub use crate::messages::broadcast::{BroadcastMessage, BroadcastMessageDiscriminant, BroadcastMessageHandler};
pub use crate::messages::debug::{DebugMessage, DebugMessageDiscriminant, DebugMessageHandler};
pub use crate::messages::dialog::export_dialog::{ExportDialogMessage, ExportDialogMessageContext, ExportDialogMessageDiscriminant, ExportDialogMessageHandler};

View File

@@ -611,7 +611,7 @@ impl PathToolData {
self.last_drill_through_click_position.map_or(true, |last_pos| last_pos.distance(position) > DRILL_THROUGH_THRESHOLD)
}
fn set_ghost_outline(&mut self, shape_editor: &ShapeState, document: &DocumentMessageHandler) {
pub fn set_ghost_outline(&mut self, shape_editor: &ShapeState, document: &DocumentMessageHandler) {
self.ghost_outline.clear();
for &layer in shape_editor.selected_shape_state.keys() {
// We probably need to collect here

View File

@@ -11,9 +11,8 @@ 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::{ManipulatorPointId, VectorData, VectorModificationType};
use std::f64::consts::{PI, TAU};
const TRANSFORM_GRS_OVERLAY_PROVIDER: OverlayProvider = |context| TransformLayerMessage::Overlays(context).into();
@@ -670,6 +669,135 @@ impl MessageHandler<TransformLayerMessage, TransformLayerMessageContext<'_>> for
}
}
impl TransformLayerMessageHandler {
pub fn is_transforming(&self) -> bool {
self.transform_operation != TransformOperation::None
}
pub fn hints(&self, responses: &mut VecDeque<Message>) {
self.transform_operation.hints(responses, self.local);
}
fn set_ghost_outline(ghost_outline: &mut Vec<(Vec<ClickTargetType>, DAffine2)>, shape_editor: &ShapeState, document: &DocumentMessageHandler) {
ghost_outline.clear();
for &layer in shape_editor.selected_shape_state.keys() {
// We probably need to collect here
let outline = document.metadata().layer_with_free_points_outline(layer).cloned().collect();
let transform = document.metadata().transform_to_viewport(layer);
ghost_outline.push((outline, transform));
}
}
}
fn calculate_pivot(
document: &DocumentMessageHandler,
selected_points: &Vec<&ManipulatorPointId>,
vector_data: &VectorData,
viewspace: DAffine2,
get_location: impl Fn(&ManipulatorPointId) -> Option<DVec2>,
gizmo: &mut PivotGizmo,
) -> (Option<(DVec2, DVec2)>, Option<[DVec2; 2]>) {
let average_position = || {
let mut point_count = 0_usize;
selected_points.iter().filter_map(|p| get_location(p)).inspect(|_| point_count += 1).sum::<DVec2>() / point_count as f64
};
let bounds = selected_points.iter().filter_map(|p| get_location(p)).fold(None, |acc: Option<[DVec2; 2]>, point| {
if let Some([mut min, mut max]) = acc {
min.x = min.x.min(point.x);
min.y = min.y.min(point.y);
max.x = max.x.max(point.x);
max.y = max.y.max(point.y);
Some([min, max])
} else {
Some([point, point])
}
});
gizmo.pivot.recalculate_pivot_for_layer(document, bounds);
let position = || {
(if !gizmo.state.disabled {
match gizmo.state.gizmo_type {
PivotGizmoType::Average => None,
PivotGizmoType::Active => gizmo.point.and_then(|p| get_location(&p)),
PivotGizmoType::Pivot => gizmo.pivot.pivot,
}
} else {
None
})
.unwrap_or_else(average_position)
};
let [point] = selected_points.as_slice() else {
// Handle the case where there are multiple points
let position = position();
return (Some((position, position)), bounds);
};
match point {
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),
) else {
return (None, None);
};
let target = viewspace.transform_point2(position);
(Some((pivot_position, target)), None)
}
_ => {
// Calculate the average position of all selected points
let position = position();
(Some((position, position)), bounds)
}
}
}
fn project_edge_to_quad(edge: DVec2, quad: &Quad, local: bool, axis_constraint: Axis) -> DVec2 {
match axis_constraint {
Axis::X => {
if local {
edge.project_onto(quad.top_right() - quad.top_left())
} else {
edge.with_y(0.)
}
}
Axis::Y => {
if local {
edge.project_onto(quad.bottom_left() - quad.top_left())
} else {
edge.with_x(0.)
}
}
_ => edge,
}
}
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 };
for [handle1, handle2] in &vector_data.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 angle = pos1.angle_to(pos2);
// Check if handles are not colinear (not approximately equal to +/- PI)
if (angle - PI).abs() > 1e-6 && (angle + PI).abs() > 1e-6 {
let modification_type = VectorModificationType::SetG1Continuous {
handles: [*handle1, *handle2],
enabled: false,
};
responses.add(GraphOperationMessage::Vector { layer, modification_type });
}
}
}
}
// #[cfg(test)]
// mod test_transform_layer {
// use crate::messages::portfolio::document::graph_operation::transform_utils;

View File

@@ -79,15 +79,6 @@ struct EvaluationContext {
export_config: Option<ExportConfig>,
}
impl Default for NodeGraphExecutor {
fn default() -> Self {
Self {
futures: Default::default(),
runtime_io: NodeRuntimeIO::new(),
}
}
}
impl NodeGraphExecutor {
/// A local runtime is useful on threads since having global state causes flakes
// #[cfg(test)]
@@ -270,18 +261,6 @@ impl NodeGraphExecutor {
}
}
// pub enum AnimationState {
// #[default]
// Stopped,
// Playing {
// start: f64,
// },
// Paused {
// start: f64,
// pause_time: f64,
// },
// }
// Re-export for usage by tests in other modules
// #[cfg(test)]
// pub use test::Instrumented;

View File

@@ -130,18 +130,12 @@ impl EditorHandle {
let f = std::rc::Rc::new(RefCell::new(None));
let g = f.clone();
*g.borrow_mut() = Some(Closure::new(move |_timestamp| {
*g.borrow_mut() = Some(Closure::new(move |timestamp| {
wasm_bindgen_futures::spawn_local(poll_node_graph_evaluation());
if !EDITOR_HAS_CRASHED.load(Ordering::SeqCst) {
editor_and_handle(|editor, handle| {
for message in editor.handle_message(InputPreprocessorMessage::CurrentTime {
timestamp: js_sys::Date::now() as u64,
}) {
handle.send_frontend_message_to_js(message);
}
for message in editor.handle_message(AnimationMessage::IncrementFrameCounter) {
for message in editor.handle_message(InputPreprocessorMessage::CurrentTime { timestamp }) {
handle.send_frontend_message_to_js(message);
}

View File

@@ -41,7 +41,7 @@ pub trait ExtractAnimationTime {
}
pub trait ExtractIndex {
fn try_index(&self) -> Option<Vec<usize>>;
fn try_index(&self) -> Option<usize>;
}
// Consider returning a slice or something like that
@@ -231,7 +231,7 @@ impl<T: ExtractAnimationTime + Sync> ExtractAnimationTime for Option<T> {
}
}
impl<T: ExtractIndex> ExtractIndex for Option<T> {
fn try_index(&self) -> Option<Vec<usize>> {
fn try_index(&self) -> Option<usize> {
self.as_ref().and_then(|x| x.try_index())
}
}
@@ -263,7 +263,7 @@ impl<T: ExtractAnimationTime + Sync> ExtractAnimationTime for Arc<T> {
}
}
impl<T: ExtractIndex> ExtractIndex for Arc<T> {
fn try_index(&self) -> Option<Vec<usize>> {
fn try_index(&self) -> Option<usize> {
(**self).try_index()
}
}
@@ -316,7 +316,7 @@ impl ExtractAnimationTime for OwnedContextImpl {
}
}
impl ExtractIndex for OwnedContextImpl {
fn try_index(&self) -> Option<Vec<usize>> {
fn try_index(&self) -> Option<usize> {
self.index.clone()
}
}
@@ -630,8 +630,8 @@ fn get_animation_time(ctx: impl Ctx + ExtractAnimationTime) -> Option<f64> {
}
#[node_macro::node(category("Context Getter"))]
fn get_index(ctx: impl Ctx + ExtractIndex) -> Option<u32> {
ctx.try_index().map(|index| index as u32)
fn get_index(ctx: impl Ctx + ExtractIndex) -> Option<usize> {
ctx.try_index()
}
// #[node_macro::node(category("Loop"))]
@@ -671,21 +671,21 @@ fn get_index(ctx: impl Ctx + ExtractIndex) -> Option<u32> {
// }
// }
#[node_macro::node(category("Loop"))]
async fn set_index<T: 'n + 'static>(
ctx: impl Ctx + ExtractAll + CloneVarArgs + Sync,
#[expose]
#[implementations(
Context -> u32,
Context -> (),
)]
input: impl Node<Context<'static>, Output = T>,
number: u32,
) -> T {
let mut new_context = OwnedContextImpl::from(ctx);
new_context.index = Some(number.try_into().unwrap());
input.eval(new_context.into_context()).await
}
// #[node_macro::node(category("Loop"))]
// async fn set_index<T: 'n + 'static>(
// ctx: impl Ctx + ExtractAll + CloneVarArgs + Sync,
// #[expose]
// #[implementations(
// Context -> u32,
// Context -> (),
// )]
// input: impl Node<Context<'static>, Output = T>,
// number: u32,
// ) -> T {
// let mut new_context = OwnedContextImpl::from(ctx);
// new_context.index = Some(number.try_into().unwrap());
// input.eval(new_context.into_context()).await
// }
// #[node_macro::node(category("Loop"))]
// fn create_arc_mutex(_ctx: impl Ctx) -> Arc<Mutex<Option<OwnedContextImpl>>> {

View File

@@ -86,10 +86,10 @@ async fn instance_position(ctx: impl Ctx + ExtractVarArgs) -> DVec2 {
// TODO: Make this return a u32 instead of an f64, but we ned to improve math-related compatibility with integer types first.
#[node_macro::node(category("Instancing"), path(graphene_core::vector))]
async fn instance_index(ctx: impl Ctx + ExtractIndex, _primary: (), loop_level: u32) -> f64 {
ctx.try_index()
.and_then(|indexes| indexes.get(indexes.len().wrapping_sub(1).wrapping_sub(loop_level as usize)).copied())
.unwrap_or_default() as f64
async fn instance_index(ctx: impl Ctx + ExtractIndex, _primary: (), _loop_level: u32) -> f64 {
ctx.try_index().unwrap_or_default() as f64
// .and_then(|indexes| indexes.get(indexes.len().wrapping_sub(1).wrapping_sub(loop_level as usize)).copied())
// .unwrap_or_default() as f64
}
// #[cfg(test)]

View File

@@ -20,7 +20,8 @@ use glam::{DAffine2, DVec2};
use kurbo::{Affine, BezPath, DEFAULT_ACCURACY, ParamCurve, PathEl, PathSeg, Shape};
use rand::{Rng, SeedableRng};
use std::collections::hash_map::DefaultHasher;
use std::f64::consts::TAU;
use std::f64::consts::{PI, TAU};
use std::hash::{Hash, Hasher};
/// Implemented for types that can be converted to an iterator of vector data.
/// Used for the fill and stroke node so they can be used on VectorData or GraphicGroup
@@ -1732,7 +1733,7 @@ async fn morph(_: impl Ctx, source: VectorDataTable, #[expose] target: VectorDat
fn bevel_algorithm(mut vector_data: VectorData, vector_data_transform: DAffine2, distance: f64) -> VectorData {
// Splits a bézier curve based on a distance measurement
fn split_distance(bezier: PathSeg, distance: f64, length: f64) -> PathSeg {
let parametric = eval_pathseg_euclidean(bezier, (distance / length).clamp(0., 1.), DEFAULT_ACCURACY);
let parametric = bezpath_algorithms::eval_pathseg_euclidean(bezier, (distance / length).clamp(0., 1.), DEFAULT_ACCURACY);
bezier.subsegment(parametric..1.)
}
@@ -1773,7 +1774,7 @@ fn bevel_algorithm(mut vector_data: VectorData, vector_data_transform: DAffine2,
}
fn calculate_distance_to_spilt(bezier1: PathSeg, bezier2: PathSeg, bevel_length: f64) -> f64 {
if is_linear(&bezier1) && is_linear(&bezier2) {
if bezpath_algorithms::is_linear(&bezier1) && bezpath_algorithms::is_linear(&bezier2) {
let v1 = (bezier1.end() - bezier1.start()).normalize();
let v2 = (bezier1.end() - bezier2.end()).normalize();
@@ -1798,8 +1799,8 @@ fn bevel_algorithm(mut vector_data: VectorData, vector_data_transform: DAffine2,
for i in 0..=INITIAL_SAMPLES {
let distance_sample = max_split * (i as f64 / INITIAL_SAMPLES as f64);
let x_point_t = eval_pathseg_euclidean(bezier1, 1. - clamp_and_round(distance_sample / length1), DEFAULT_ACCURACY);
let y_point_t = eval_pathseg_euclidean(bezier2, clamp_and_round(distance_sample / length2), DEFAULT_ACCURACY);
let x_point_t = bezpath_algorithms::eval_pathseg_euclidean(bezier1, 1. - clamp_and_round(distance_sample / length1), DEFAULT_ACCURACY);
let y_point_t = bezpath_algorithms::eval_pathseg_euclidean(bezier2, clamp_and_round(distance_sample / length2), DEFAULT_ACCURACY);
let x_point = bezier1.eval(x_point_t);
let y_point = bezier2.eval(y_point_t);
@@ -1822,8 +1823,8 @@ fn bevel_algorithm(mut vector_data: VectorData, vector_data_transform: DAffine2,
for j in 1..=REFINE_STEPS {
let refined_sample = prev_sample + (distance_sample - prev_sample) * (j as f64 / REFINE_STEPS as f64);
let x_point_t = eval_pathseg_euclidean(bezier1, 1. - (refined_sample / length1).clamp(0., 1.), DEFAULT_ACCURACY);
let y_point_t = eval_pathseg_euclidean(bezier2, (refined_sample / length2).clamp(0., 1.), DEFAULT_ACCURACY);
let x_point_t = bezpath_algorithms::eval_pathseg_euclidean(bezier1, 1. - (refined_sample / length1).clamp(0., 1.), DEFAULT_ACCURACY);
let y_point_t = bezpath_algorithms::eval_pathseg_euclidean(bezier2, (refined_sample / length2).clamp(0., 1.), DEFAULT_ACCURACY);
let x_point = bezier1.eval(x_point_t);
let y_point = bezier2.eval(y_point_t);
@@ -1911,16 +1912,16 @@ fn bevel_algorithm(mut vector_data: VectorData, vector_data_transform: DAffine2,
let spilt_distance = calculate_distance_to_spilt(bezier, next_bezier, distance);
if is_linear(&bezier) {
if bezpath_algorithms::is_linear(&bezier) {
let start = point_to_dvec2(bezier.start());
let end = point_to_dvec2(bezier.end());
bezier = handles_to_segment(start, BezierHandles::Linear, end);
bezier = handles_to_segment(start, bezier_rs::BezierHandles::Linear, end);
}
if is_linear(&next_bezier) {
if bezpath_algorithms::is_linear(&next_bezier) {
let start = point_to_dvec2(next_bezier.start());
let end = point_to_dvec2(next_bezier.end());
next_bezier = handles_to_segment(start, BezierHandles::Linear, end);
next_bezier = handles_to_segment(start, bezier_rs::BezierHandles::Linear, end);
}
let inverse_transform = (vector_data_transform.matrix2.determinant() != 0.).then(|| vector_data_transform.inverse()).unwrap_or_default();

View File

@@ -13,7 +13,6 @@ use graphene_core::uuid::NodeId;
use graphene_core::vector::style::Fill;
use graphene_core::{Color, MemoHash, Node, Type};
use graphene_svg_renderer::{GraphicElementRendered, RenderMetadata};
use std::cell::Cell;
use std::fmt::Display;
use std::hash::Hash;
use std::marker::PhantomData;
@@ -564,6 +563,7 @@ thumbnail_render! {
graphene_core::GraphicElement,
Option<Color>,
Vec<Color>,
f64,
}
pub enum ThumbnailRenderResult {

View File

@@ -3,6 +3,7 @@ pub use graph_craft::proto::{Any, NodeContainer, TypeErasedBox, TypeErasedNode};
use graph_craft::proto::{DynFuture, FutureAny, SharedNodeContainer};
use graphene_core::Context;
use graphene_core::ContextDependencies;
use graphene_core::EditorContext;
use graphene_core::NodeIO;
use graphene_core::OwnedContextImpl;
use graphene_core::WasmNotSend;

View File

@@ -1,4 +1,4 @@
use crate::vector::{VectorData, VectorDataTable};
use crate::vector::{VectorDataTable};
use graphene_core::Ctx;
pub use graphene_core::text::*;

View File

@@ -213,6 +213,7 @@ pub trait GraphicElementRendered: BoundingBox + RenderComplexity {
fn render_thumbnail(&self) -> String {
let Some(bounds) = self.bounding_box(DAffine2::IDENTITY, true) else {
log::debug!("Could not get bounds");
return String::new();
};
@@ -228,7 +229,7 @@ pub trait GraphicElementRendered: BoundingBox + RenderComplexity {
let mut render = SvgRender::new();
self.render_svg(&mut render, &render_params);
// let center = (bounds[0] + bounds[1]) / 2.;
// let size = bounds[1] - bounds[0];
@@ -1193,13 +1194,13 @@ impl Primitive for f64 {}
impl Primitive for DVec2 {}
fn text_attributes(attributes: &mut SvgRenderAttrs) {
attributes.push("fill", "white");
attributes.push("y", "30");
attributes.push("fill", "black");
attributes.push("font-size", "30");
}
impl<P: Primitive> GraphicElementRendered for P {
fn render_svg(&self, render: &mut SvgRender, _render_params: &RenderParams) {
log::debug!("Rendering svg for primative: {}", self);
render.parent_tag("text", text_attributes, |render| render.leaf_node(format!("{self}")));
}