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
+1 -59
View File
@@ -16,7 +16,6 @@ pub struct Dispatcher {
#[derive(Debug, Default)] #[derive(Debug, Default)]
pub struct DispatcherMessageHandlers { pub struct DispatcherMessageHandlers {
animation_message_handler: AnimationMessageHandler,
broadcast_message_handler: BroadcastMessageHandler, broadcast_message_handler: BroadcastMessageHandler,
debug_message_handler: DebugMessageHandler, debug_message_handler: DebugMessageHandler,
dialog_message_handler: DialogMessageHandler, dialog_message_handler: DialogMessageHandler,
@@ -59,7 +58,7 @@ const SIDE_EFFECT_FREE_MESSAGES: &[MessageDiscriminant] = &[
MessageDiscriminant::Frontend(FrontendMessageDiscriminant::UpdateDocumentLayerStructure), MessageDiscriminant::Frontend(FrontendMessageDiscriminant::UpdateDocumentLayerStructure),
MessageDiscriminant::Frontend(FrontendMessageDiscriminant::TriggerFontLoad), 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. // 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"];
@@ -163,27 +162,6 @@ impl Dispatcher {
let clear_message = PortfolioMessage::ClearIntrospectedData.into(); let clear_message = PortfolioMessage::ClearIntrospectedData.into();
Self::schedule_execution(&mut self.message_queues, true, [clear_message]); 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::Broadcast(message) => self.message_handlers.broadcast_message_handler.process_message(message, &mut queue, ()),
Message::Debug(message) => { Message::Debug(message) => {
self.message_handlers.debug_message_handler.process_message(message, &mut queue, ()); 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 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 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 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( self.message_handlers.portfolio_message_handler.process_message(
message, message,
@@ -250,8 +226,6 @@ impl Dispatcher {
current_tool, current_tool,
message_logging_verbosity, message_logging_verbosity,
reset_node_definitions_on_open, reset_node_definitions_on_open,
timing_information,
animation,
}, },
); );
} }
@@ -283,37 +257,6 @@ impl Dispatcher {
Message::Batched { messages } => { Message::Batched { messages } => {
messages.iter().for_each(|message| self.handle_message(message.to_owned(), false)); 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 // 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 // TODO: Reduce the number of heap allocations
let mut list = Vec::new(); let mut list = Vec::new();
list.extend(self.message_handlers.dialog_message_handler.actions()); 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.input_preprocessor_message_handler.actions());
list.extend(self.message_handlers.key_mapping_message_handler.actions()); list.extend(self.message_handlers.key_mapping_message_handler.actions());
list.extend(self.message_handlers.debug_message_handler.actions()); list.extend(self.message_handlers.debug_message_handler.actions());
@@ -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 },
}
@@ -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,
);
}
-9
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;
@@ -437,8 +437,8 @@ pub fn input_mappings() -> Mapping {
entry!(KeyDown(Digit1); modifiers=[Alt], action_dispatch=DebugMessage::MessageNames), entry!(KeyDown(Digit1); modifiers=[Alt], action_dispatch=DebugMessage::MessageNames),
entry!(KeyDown(Digit2); modifiers=[Alt], action_dispatch=DebugMessage::MessageContents), entry!(KeyDown(Digit2); modifiers=[Alt], action_dispatch=DebugMessage::MessageContents),
// AnimationMessage // AnimationMessage
entry!(KeyDown(Space); modifiers=[Shift], action_dispatch=AnimationMessage::ToggleLivePreview), entry!(KeyDown(Space); modifiers=[Shift], action_dispatch=DocumentMessage::ToggleAnimation),
entry!(KeyDown(Home); modifiers=[Shift], action_dispatch=AnimationMessage::RestartAnimation), 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; 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;
@@ -12,6 +12,6 @@ pub enum InputPreprocessorMessage {
PointerDown { editor_mouse_state: EditorMouseState, modifier_keys: ModifierKeys }, PointerDown { editor_mouse_state: EditorMouseState, modifier_keys: ModifierKeys },
PointerMove { editor_mouse_state: EditorMouseState, modifier_keys: ModifierKeys }, PointerMove { editor_mouse_state: EditorMouseState, modifier_keys: ModifierKeys },
PointerUp { 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 }, WheelScroll { editor_mouse_state: EditorMouseState, modifier_keys: ModifierKeys },
} }
@@ -97,7 +97,7 @@ impl MessageHandler<InputPreprocessorMessage, InputPreprocessorMessageContext> f
self.translate_mouse_event(mouse_state, false, responses); self.translate_mouse_event(mouse_state, false, responses);
} }
InputPreprocessorMessage::CurrentTime { timestamp } => { InputPreprocessorMessage::CurrentTime { timestamp } => {
self.time = timestamp as f64; self.time = timestamp;
} }
InputPreprocessorMessage::WheelScroll { editor_mouse_state, modifier_keys } => { InputPreprocessorMessage::WheelScroll { 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);
-9
View File
@@ -4,9 +4,6 @@ use graphite_proc_macros::*;
#[impl_message] #[impl_message]
#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)] #[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
pub enum Message { pub enum Message {
NoOp,
Init,
Batched(Box<[Message]>),
// Adds any subsequent messages to the queue // Adds any subsequent messages to the queue
StartEvaluationQueue, StartEvaluationQueue,
// Stop adding messages to the queue. // Stop adding messages to the queue.
@@ -15,8 +12,6 @@ pub enum Message {
#[serde(skip)] #[serde(skip)]
ProcessEvaluationQueue(graphene_std::renderer::RenderMetadata, IntrospectionResponse), ProcessEvaluationQueue(graphene_std::renderer::RenderMetadata, IntrospectionResponse),
#[child] #[child]
Animation(AnimationMessage),
#[child]
Broadcast(BroadcastMessage), Broadcast(BroadcastMessage),
#[child] #[child]
Debug(DebugMessage), Debug(DebugMessage),
@@ -46,10 +41,6 @@ pub enum Message {
Batched { Batched {
messages: Box<[Message]>, messages: Box<[Message]>,
}, },
StartBuffer,
EndBuffer {
render_metadata: RenderMetadata,
},
} }
/// Provides an impl of `specta::Type` for `MessageDiscriminant`, the struct created by `impl_message`. /// Provides an impl of `specta::Type` for `MessageDiscriminant`, the struct created by `impl_message`.
-1
View File
@@ -1,6 +1,5 @@
//! The root-level messages forming the first layer of the message system architecture. //! The root-level messages forming the first layer of the message system architecture.
pub mod animation;
pub mod broadcast; pub mod broadcast;
pub mod debug; pub mod debug;
pub mod dialog; pub mod dialog;
@@ -170,6 +170,8 @@ pub enum DocumentMessage {
RepeatedAbortTransaction { RepeatedAbortTransaction {
undo_count: usize, undo_count: usize,
}, },
ToggleAnimation,
RestartAnimation,
ToggleLayerExpansion { ToggleLayerExpansion {
id: NodeId, id: NodeId,
recursive: bool, recursive: bool,
@@ -35,17 +35,13 @@ use graphene_std::uuid::NodeId;
use graphene_std::vector::PointId; use graphene_std::vector::PointId;
use graphene_std::vector::click_target::{ClickTarget, ClickTargetType}; use graphene_std::vector::click_target::{ClickTarget, ClickTargetType};
use graphene_std::vector::style::ViewMode; use graphene_std::vector::style::ViewMode;
use std::time::Duration;
#[derive(ExtractField)] #[derive(ExtractField)]
pub struct DocumentMessageData<'a> { pub struct DocumentMessageContext<'a> {
pub ipp: &'a InputPreprocessorMessageHandler, pub ipp: &'a InputPreprocessorMessageHandler,
pub persistent_data: &'a PersistentData,
pub current_tool: &'a ToolType, pub current_tool: &'a ToolType,
pub preferences: &'a PreferencesMessageHandler, pub preferences: &'a PreferencesMessageHandler,
pub device_pixel_ratio: f64, 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)] #[derive(Clone, Debug, serde::Serialize, serde::Deserialize, ExtractField)]
@@ -101,6 +97,9 @@ pub struct DocumentMessageHandler {
// Fields omitted from the saved document format // 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 /// 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)] #[serde(skip)]
pub breadcrumb_network_path: Vec<NodeId>, pub breadcrumb_network_path: Vec<NodeId>,
@@ -164,16 +163,16 @@ impl Default for DocumentMessageHandler {
auto_saved_hash: None, auto_saved_hash: None,
layer_range_selection_reference: None, layer_range_selection_reference: None,
is_loaded: false, is_loaded: false,
animation_state: AnimationState::Stopped,
} }
} }
} }
#[message_handler_data] #[message_handler_data]
impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessageHandler { impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMessageHandler {
fn process_message(&mut self, message: DocumentMessage, responses: &mut VecDeque<Message>, data: DocumentMessageData) { fn process_message(&mut self, message: DocumentMessage, responses: &mut VecDeque<Message>, data: DocumentMessageContext) {
let DocumentMessageData { let DocumentMessageContext {
ipp, ipp,
persistent_data,
current_tool, current_tool,
preferences, preferences,
device_pixel_ratio, device_pixel_ratio,
@@ -217,7 +216,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
); );
} }
DocumentMessage::PropertiesPanel(message) => { 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, network_interface: &mut self.network_interface,
selection_network_path: &self.selection_network_path, selection_network_path: &self.selection_network_path,
document_name: self.name.as_str(), document_name: self.name.as_str(),
@@ -1284,6 +1283,27 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
responses.add(NodeGraphMessage::SendGraph); 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::ToggleSelectedLocked => responses.add(NodeGraphMessage::ToggleSelectedLocked),
DocumentMessage::ToggleSelectedVisibility => { DocumentMessage::ToggleSelectedVisibility => {
responses.add(NodeGraphMessage::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; self.snapping_state.snapping_enabled = !self.snapping_state.snapping_enabled;
responses.add(PortfolioMessage::UpdateDocumentWidgets); 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 { DocumentMessage::UpdateUpstreamTransforms {
upstream_footprints, upstream_footprints,
local_transforms, 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_transforms(upstream_footprints, local_transforms);
self.network_interface.update_first_instance_source_id(first_instance_source_id); 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 } => { DocumentMessage::UpdateClickTargets { click_targets } => {
// TODO: Allow non layer nodes to have click targets // TODO: Allow non layer nodes to have click targets
@@ -1582,6 +1594,14 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
} }
impl DocumentMessageHandler { 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 /// 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> { 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); 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) // 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 { let document_mode_layout = WidgetLayout::new(vec![LayoutGroup::Row {
@@ -2153,14 +2175,14 @@ impl DocumentMessageHandler {
let mut widgets = vec![ let mut widgets = vec![
IconButton::new("PlaybackToStart", 24) IconButton::new("PlaybackToStart", 24)
.tooltip("Restart Animation") .tooltip("Restart Animation")
.tooltip_shortcut(action_keys!(AnimationMessageDiscriminant::RestartAnimation)) .tooltip_shortcut(action_keys!(DocumentMessageDiscriminant::RestartAnimation))
.on_update(|_| AnimationMessage::RestartAnimation.into()) .on_update(|_| DocumentMessage::RestartAnimation.into())
.disabled(time == Duration::ZERO) .disabled(self.animation_time(ipp) == 0.)
.widget_holder(), .widget_holder(),
IconButton::new(if animation_is_playing { "PlaybackPause" } else { "PlaybackPlay" }, 24) IconButton::new(if animation_is_playing { "PlaybackPause" } else { "PlaybackPlay" }, 24)
.tooltip(if animation_is_playing { "Pause Animation" } else { "Play Animation" }) .tooltip(if animation_is_playing { "Pause Animation" } else { "Play Animation" })
.tooltip_shortcut(action_keys!(AnimationMessageDiscriminant::ToggleLivePreview)) .tooltip_shortcut(action_keys!(DocumentMessageDiscriminant::ToggleAnimation))
.on_update(|_| AnimationMessage::ToggleLivePreview.into()) .on_update(|_| DocumentMessage::ToggleAnimation.into())
.widget_holder(), .widget_holder(),
Separator::new(SeparatorType::Unrelated).widget_holder(), Separator::new(SeparatorType::Unrelated).widget_holder(),
CheckboxInput::new(self.overlays_visibility_settings.all) 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)] // #[cfg(test)]
// mod document_message_handler_tests { // mod document_message_handler_tests {
// use super::*; // use super::*;
@@ -8,7 +8,6 @@ use crate::messages::portfolio::document::utility_types::network_interface::{
DocumentNodeMetadata, DocumentNodePersistentMetadata, InputMetadata, NodeNetworkInterface, NodeNetworkMetadata, NodeNetworkPersistentMetadata, NodeTemplate, NodeTypePersistentMetadata, DocumentNodeMetadata, DocumentNodePersistentMetadata, InputMetadata, NodeNetworkInterface, NodeNetworkMetadata, NodeNetworkPersistentMetadata, NodeTemplate, NodeTypePersistentMetadata,
NumberInputSettings, Vec2InputSettings, WidgetOverride, NumberInputSettings, Vec2InputSettings, WidgetOverride,
}; };
use crate::messages::portfolio::utility_types::PersistentData;
use crate::messages::prelude::Message; use crate::messages::prelude::Message;
use glam::DVec2; use glam::DVec2;
use graph_craft::ProtoNodeIdentifier; use graph_craft::ProtoNodeIdentifier;
@@ -28,7 +27,6 @@ use graphene_std::*;
use std::collections::{HashMap, HashSet, VecDeque}; use std::collections::{HashMap, HashSet, VecDeque};
pub struct NodePropertiesContext<'a> { pub struct NodePropertiesContext<'a> {
pub persistent_data: &'a PersistentData,
pub responses: &'a mut VecDeque<Message>, pub responses: &'a mut VecDeque<Message>,
pub network_interface: &'a mut NodeNetworkInterface, pub network_interface: &'a mut NodeNetworkInterface,
pub selection_network_path: &'a [NodeId], pub selection_network_path: &'a [NodeId],
@@ -2305,7 +2305,7 @@ impl NodeGraphMessageHandler {
.find(|error| match &error.original_location { .find(|error| match &error.original_location {
graph_craft::proto::OriginalLocation::Value(_) => false, graph_craft::proto::OriginalLocation::Value(_) => false,
graph_craft::proto::OriginalLocation::Node(prefixed_node_path) => { 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 node_path == &node_id_path
} }
}) })
@@ -2314,7 +2314,7 @@ impl NodeGraphMessageHandler {
if self.node_graph_errors.iter().any(|error| match &error.original_location { if self.node_graph_errors.iter().any(|error| match &error.original_location {
graph_craft::proto::OriginalLocation::Value(_) => false, graph_craft::proto::OriginalLocation::Value(_) => false,
graph_craft::proto::OriginalLocation::Node(prefixed_node_path) => { 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) node_path.starts_with(&node_id_path)
} }
}) { }) {
@@ -4,4 +4,4 @@ mod properties_panel_message_handler;
#[doc(inline)] #[doc(inline)]
pub use properties_panel_message::{PropertiesPanelMessage, PropertiesPanelMessageDiscriminant}; pub use properties_panel_message::{PropertiesPanelMessage, PropertiesPanelMessageDiscriminant};
#[doc(inline)] #[doc(inline)]
pub use properties_panel_message_handler::{PropertiesPanelMessageHandler, PropertiesPanelMessageHandlerData}; pub use properties_panel_message_handler::{PropertiesPanelMessageContext, PropertiesPanelMessageHandler};
@@ -3,11 +3,10 @@ use graphene_std::uuid::NodeId;
use crate::messages::layout::utility_types::widget_prelude::*; use crate::messages::layout::utility_types::widget_prelude::*;
use crate::messages::portfolio::document::node_graph::document_node_definitions::NodePropertiesContext; 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::document::utility_types::network_interface::NodeNetworkInterface;
use crate::messages::portfolio::utility_types::PersistentData;
use crate::messages::prelude::*; use crate::messages::prelude::*;
use crate::messages::portfolio::document::utility_types::network_interface::NodeNetworkInterface; #[derive(ExtractField)]
pub struct PropertiesPanelMessageHandlerData<'a> { pub struct PropertiesPanelMessageContext<'a> {
pub network_interface: &'a mut NodeNetworkInterface, pub network_interface: &'a mut NodeNetworkInterface,
pub selection_network_path: &'a [NodeId], pub selection_network_path: &'a [NodeId],
pub document_name: &'a str, pub document_name: &'a str,
@@ -23,7 +22,7 @@ impl MessageHandler<PropertiesPanelMessage, PropertiesPanelMessageContext<'_>> f
network_interface, network_interface,
selection_network_path, selection_network_path,
document_name, document_name,
} = data; } = context;
match message { match message {
PropertiesPanelMessage::Clear => { PropertiesPanelMessage::Clear => {
@@ -34,7 +33,6 @@ impl MessageHandler<PropertiesPanelMessage, PropertiesPanelMessageContext<'_>> f
} }
PropertiesPanelMessage::Refresh => { PropertiesPanelMessage::Refresh => {
let mut node_properties_context = NodePropertiesContext { let mut node_properties_context = NodePropertiesContext {
persistent_data,
responses, responses,
network_interface, network_interface,
selection_network_path, selection_network_path,
@@ -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> { 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) { match self.cached_wire(input, network_path) {
Some(loaded) => None, Some(_) => None,
None => { None => {
self.load_wire(input, graph_wire_style, network_path); self.load_wire(input, graph_wire_style, network_path);
self.cached_wire(input, network_path).cloned() self.cached_wire(input, network_path).cloned()
@@ -22,6 +22,7 @@ pub enum PortfolioMessage {
#[child] #[child]
Spreadsheet(SpreadsheetMessage), Spreadsheet(SpreadsheetMessage),
Init,
// Sends a request to compile the network. Should occur when any value, preference, or font changes // Sends a request to compile the network. Should occur when any value, preference, or font changes
CompileActiveDocument, CompileActiveDocument,
// Sends a request to evaluate the network. Should occur when any context value changes. // 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 // Introspected data is cleared after queued messages are complete
ClearIntrospectedData, ClearIntrospectedData,
ProcessThumbnails, ProcessThumbnails,
DocumentPassMessage {
document_id: DocumentId,
message: DocumentMessage,
},
AutoSaveActiveDocument, AutoSaveActiveDocument,
AutoSaveAllDocuments, AutoSaveAllDocuments,
AutoSaveDocument { AutoSaveDocument {
@@ -10,6 +10,7 @@ use crate::messages::frontend::utility_types::{ExportBounds, FrontendDocumentDet
use crate::messages::layout::utility_types::widget_prelude::*; use crate::messages::layout::utility_types::widget_prelude::*;
use crate::messages::portfolio::document::DocumentMessageContext; use crate::messages::portfolio::document::DocumentMessageContext;
use crate::messages::portfolio::document::graph_operation::utility_types::TransformIn; 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::clipboards::{Clipboard, CopyBufferEntry, INTERNAL_CLIPBOARD_COUNT};
use crate::messages::portfolio::document::utility_types::nodes::SelectedNodes; use crate::messages::portfolio::document::utility_types::nodes::SelectedNodes;
use crate::messages::portfolio::document::utility_types::wires::{GraphWireStyle, WireSNIUpdate}; 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::value::EditorMetadata;
use graph_craft::document::{InputConnector, NodeInput, OutputConnector}; use graph_craft::document::{InputConnector, NodeInput, OutputConnector};
use graphene_std::EditorContext; use graphene_std::EditorContext;
use graphene_std::application_io::TimingInformation;
use graphene_std::memo::MonitorIntrospectResult; use graphene_std::memo::MonitorIntrospectResult;
use graphene_std::renderer::{Quad, RenderMetadata}; use graphene_std::renderer::{Quad, RenderMetadata};
use graphene_std::text::Font; use graphene_std::text::Font;
@@ -38,8 +38,6 @@ pub struct PortfolioMessageContext<'a> {
pub current_tool: &'a ToolType, pub current_tool: &'a ToolType,
pub message_logging_verbosity: MessageLoggingVerbosity, pub message_logging_verbosity: MessageLoggingVerbosity,
pub reset_node_definitions_on_open: bool, pub reset_node_definitions_on_open: bool,
pub timing_information: TimingInformation,
pub animation: &'a AnimationMessageHandler,
} }
#[derive(Debug, Default, ExtractField)] #[derive(Debug, Default, ExtractField)]
@@ -74,8 +72,6 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
current_tool, current_tool,
message_logging_verbosity, message_logging_verbosity,
reset_node_definitions_on_open, reset_node_definitions_on_open,
timing_information,
animation,
} = context; } = context;
match message { match message {
@@ -127,9 +123,8 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
PortfolioMessage::Document(message) => { PortfolioMessage::Document(message) => {
if let Some(document_id) = self.active_document_id { if let Some(document_id) = self.active_document_id {
if let Some(document) = self.documents.get_mut(&document_id) { if let Some(document) = self.documents.get_mut(&document_id) {
let document_inputs = DocumentMessageData { let document_inputs = DocumentMessageContext {
ipp, ipp,
persistent_data: &self.persistent_data,
current_tool, current_tool,
preferences, preferences,
device_pixel_ratio: self.device_pixel_ratio.unwrap_or(1.), device_pixel_ratio: self.device_pixel_ratio.unwrap_or(1.),
@@ -138,7 +133,6 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
} }
} }
} }
// Messages // Messages
PortfolioMessage::Init => { PortfolioMessage::Init => {
// Load persistent data from the browser database // 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 // Finish loading persistent data from the browser database
responses.add(FrontendMessage::TriggerLoadRestAutoSaveDocuments); 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 => { PortfolioMessage::AutoSaveActiveDocument => {
if let Some(document_id) = self.active_document_id { if let Some(document_id) = self.active_document_id {
if let Some(document) = self.active_document_mut() { if let Some(document) = self.active_document_mut() {
@@ -831,25 +813,17 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
return; 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(); let mut context = EditorContext::default();
context.footprint = Some(Footprint { context.footprint = Some(Footprint {
transform: document.metadata().document_to_viewport, transform: document.metadata().document_to_viewport,
resolution: ipp.viewport_bounds.size().as_uvec2(), resolution: ipp.viewport_bounds.size().as_uvec2(),
quality: RenderQuality::Full, 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.real_time = Some(ipp.time);
context.downstream_transform = Some(DAffine2::IDENTITY); 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_introspect);
self.executor.submit_node_graph_evaluation(context, None, None, nodes_to_try_render);
} }
PortfolioMessage::ProcessEvaluationResponse { PortfolioMessage::ProcessEvaluationResponse {
evaluation_metadata, evaluation_metadata,
@@ -876,10 +850,6 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
responses.add(DocumentMessage::RenderScrollbars); responses.add(DocumentMessage::RenderScrollbars);
responses.add(DocumentMessage::RenderRulers); responses.add(DocumentMessage::RenderRulers);
responses.add(OverlaysMessage::Draw); responses.add(OverlaysMessage::Draw);
// match document.animation_state {
// AnimationState::Playing { .. } => responses.add(PortfolioMessage::EvaluateActiveDocument),
// _ => {}
// };
} }
// PortfolioMessage::IntrospectActiveDocument { nodes_to_introspect } => { // PortfolioMessage::IntrospectActiveDocument { nodes_to_introspect } => {
// self.executor.submit_node_graph_introspection(nodes_to_introspect); // self.executor.submit_node_graph_introspection(nodes_to_introspect);
@@ -1081,7 +1051,7 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
} }
PortfolioMessage::UpdateDocumentWidgets => { PortfolioMessage::UpdateDocumentWidgets => {
if let Some(document) = self.active_document() { 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 => { PortfolioMessage::UpdateOpenDocumentsList => {
@@ -1283,8 +1253,6 @@ impl PortfolioMessageHandler {
.network_interface .network_interface
.viewport_loaded_thumbnail_position(&input_connector, graph_wire_style, &document.breadcrumb_network_path) .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; 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 { if in_view {
let Some(protonode) = document.network_interface.protonode_from_input(&input_connector, &document.breadcrumb_network_path) else { let Some(protonode) = document.network_interface.protonode_from_input(&input_connector, &document.breadcrumb_network_path) else {
-1
View File
@@ -2,7 +2,6 @@
pub use crate::utility_traits::{ActionList, AsMessage, HierarchicalTree, MessageHandler, ToDiscriminant, TransitiveChild}; pub use crate::utility_traits::{ActionList, AsMessage, HierarchicalTree, MessageHandler, ToDiscriminant, TransitiveChild};
pub use crate::utility_types::{DebugMessageTree, MessageData}; pub use crate::utility_types::{DebugMessageTree, MessageData};
// Message, MessageData, MessageDiscriminant, MessageHandler // Message, MessageData, MessageDiscriminant, MessageHandler
pub use crate::messages::animation::{AnimationMessage, AnimationMessageDiscriminant, AnimationMessageHandler};
pub use crate::messages::broadcast::{BroadcastMessage, BroadcastMessageDiscriminant, BroadcastMessageHandler}; pub use crate::messages::broadcast::{BroadcastMessage, BroadcastMessageDiscriminant, BroadcastMessageHandler};
pub use crate::messages::debug::{DebugMessage, DebugMessageDiscriminant, DebugMessageHandler}; pub use crate::messages::debug::{DebugMessage, DebugMessageDiscriminant, DebugMessageHandler};
pub use crate::messages::dialog::export_dialog::{ExportDialogMessage, ExportDialogMessageContext, ExportDialogMessageDiscriminant, ExportDialogMessageHandler}; pub use crate::messages::dialog::export_dialog::{ExportDialogMessage, ExportDialogMessageContext, ExportDialogMessageDiscriminant, ExportDialogMessageHandler};
@@ -611,7 +611,7 @@ impl PathToolData {
self.last_drill_through_click_position.map_or(true, |last_pos| last_pos.distance(position) > DRILL_THROUGH_THRESHOLD) 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(); self.ghost_outline.clear();
for &layer in shape_editor.selected_shape_state.keys() { for &layer in shape_editor.selected_shape_state.keys() {
// We probably need to collect here // We probably need to collect here
@@ -11,9 +11,8 @@ use crate::messages::tool::tool_messages::tool_prelude::Key;
use crate::messages::tool::utility_types::{ToolData, ToolType}; use crate::messages::tool::utility_types::{ToolData, ToolType};
use glam::{DAffine2, DVec2}; use glam::{DAffine2, DVec2};
use graphene_std::renderer::Quad; use graphene_std::renderer::Quad;
use graphene_std::vector::ManipulatorPointId;
use graphene_std::vector::click_target::ClickTargetType; 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}; use std::f64::consts::{PI, TAU};
const TRANSFORM_GRS_OVERLAY_PROVIDER: OverlayProvider = |context| TransformLayerMessage::Overlays(context).into(); 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)] // #[cfg(test)]
// mod test_transform_layer { // mod test_transform_layer {
// use crate::messages::portfolio::document::graph_operation::transform_utils; // use crate::messages::portfolio::document::graph_operation::transform_utils;
-21
View File
@@ -79,15 +79,6 @@ struct EvaluationContext {
export_config: Option<ExportConfig>, export_config: Option<ExportConfig>,
} }
impl Default for NodeGraphExecutor {
fn default() -> Self {
Self {
futures: Default::default(),
runtime_io: NodeRuntimeIO::new(),
}
}
}
impl NodeGraphExecutor { impl NodeGraphExecutor {
/// A local runtime is useful on threads since having global state causes flakes /// A local runtime is useful on threads since having global state causes flakes
// #[cfg(test)] // #[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 // Re-export for usage by tests in other modules
// #[cfg(test)] // #[cfg(test)]
// pub use test::Instrumented; // pub use test::Instrumented;
+2 -8
View File
@@ -130,18 +130,12 @@ impl EditorHandle {
let f = std::rc::Rc::new(RefCell::new(None)); let f = std::rc::Rc::new(RefCell::new(None));
let g = f.clone(); 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()); wasm_bindgen_futures::spawn_local(poll_node_graph_evaluation());
if !EDITOR_HAS_CRASHED.load(Ordering::SeqCst) { if !EDITOR_HAS_CRASHED.load(Ordering::SeqCst) {
editor_and_handle(|editor, handle| { editor_and_handle(|editor, handle| {
for message in editor.handle_message(InputPreprocessorMessage::CurrentTime { for message in editor.handle_message(InputPreprocessorMessage::CurrentTime { timestamp }) {
timestamp: js_sys::Date::now() as u64,
}) {
handle.send_frontend_message_to_js(message);
}
for message in editor.handle_message(AnimationMessage::IncrementFrameCounter) {
handle.send_frontend_message_to_js(message); handle.send_frontend_message_to_js(message);
} }
+21 -21
View File
@@ -41,7 +41,7 @@ pub trait ExtractAnimationTime {
} }
pub trait ExtractIndex { pub trait ExtractIndex {
fn try_index(&self) -> Option<Vec<usize>>; fn try_index(&self) -> Option<usize>;
} }
// Consider returning a slice or something like that // 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> { 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()) 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> { impl<T: ExtractIndex> ExtractIndex for Arc<T> {
fn try_index(&self) -> Option<Vec<usize>> { fn try_index(&self) -> Option<usize> {
(**self).try_index() (**self).try_index()
} }
} }
@@ -316,7 +316,7 @@ impl ExtractAnimationTime for OwnedContextImpl {
} }
} }
impl ExtractIndex for OwnedContextImpl { impl ExtractIndex for OwnedContextImpl {
fn try_index(&self) -> Option<Vec<usize>> { fn try_index(&self) -> Option<usize> {
self.index.clone() self.index.clone()
} }
} }
@@ -630,8 +630,8 @@ fn get_animation_time(ctx: impl Ctx + ExtractAnimationTime) -> Option<f64> {
} }
#[node_macro::node(category("Context Getter"))] #[node_macro::node(category("Context Getter"))]
fn get_index(ctx: impl Ctx + ExtractIndex) -> Option<u32> { fn get_index(ctx: impl Ctx + ExtractIndex) -> Option<usize> {
ctx.try_index().map(|index| index as u32) ctx.try_index()
} }
// #[node_macro::node(category("Loop"))] // #[node_macro::node(category("Loop"))]
@@ -671,21 +671,21 @@ fn get_index(ctx: impl Ctx + ExtractIndex) -> Option<u32> {
// } // }
// } // }
#[node_macro::node(category("Loop"))] // #[node_macro::node(category("Loop"))]
async fn set_index<T: 'n + 'static>( // async fn set_index<T: 'n + 'static>(
ctx: impl Ctx + ExtractAll + CloneVarArgs + Sync, // ctx: impl Ctx + ExtractAll + CloneVarArgs + Sync,
#[expose] // #[expose]
#[implementations( // #[implementations(
Context -> u32, // Context -> u32,
Context -> (), // Context -> (),
)] // )]
input: impl Node<Context<'static>, Output = T>, // input: impl Node<Context<'static>, Output = T>,
number: u32, // number: u32,
) -> T { // ) -> T {
let mut new_context = OwnedContextImpl::from(ctx); // let mut new_context = OwnedContextImpl::from(ctx);
new_context.index = Some(number.try_into().unwrap()); // new_context.index = Some(number.try_into().unwrap());
input.eval(new_context.into_context()).await // input.eval(new_context.into_context()).await
} // }
// #[node_macro::node(category("Loop"))] // #[node_macro::node(category("Loop"))]
// fn create_arc_mutex(_ctx: impl Ctx) -> Arc<Mutex<Option<OwnedContextImpl>>> { // fn create_arc_mutex(_ctx: impl Ctx) -> Arc<Mutex<Option<OwnedContextImpl>>> {
@@ -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. // 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))] #[node_macro::node(category("Instancing"), path(graphene_core::vector))]
async fn instance_index(ctx: impl Ctx + ExtractIndex, _primary: (), loop_level: u32) -> f64 { async fn instance_index(ctx: impl Ctx + ExtractIndex, _primary: (), _loop_level: u32) -> f64 {
ctx.try_index() 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()) // .and_then(|indexes| indexes.get(indexes.len().wrapping_sub(1).wrapping_sub(loop_level as usize)).copied())
.unwrap_or_default() as f64 // .unwrap_or_default() as f64
} }
// #[cfg(test)] // #[cfg(test)]
+12 -11
View File
@@ -20,7 +20,8 @@ use glam::{DAffine2, DVec2};
use kurbo::{Affine, BezPath, DEFAULT_ACCURACY, ParamCurve, PathEl, PathSeg, Shape}; use kurbo::{Affine, BezPath, DEFAULT_ACCURACY, ParamCurve, PathEl, PathSeg, Shape};
use rand::{Rng, SeedableRng}; use rand::{Rng, SeedableRng};
use std::collections::hash_map::DefaultHasher; 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. /// 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 /// 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 { fn bevel_algorithm(mut vector_data: VectorData, vector_data_transform: DAffine2, distance: f64) -> VectorData {
// Splits a bézier curve based on a distance measurement // Splits a bézier curve based on a distance measurement
fn split_distance(bezier: PathSeg, distance: f64, length: f64) -> PathSeg { 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.) 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 { 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 v1 = (bezier1.end() - bezier1.start()).normalize();
let v2 = (bezier1.end() - bezier2.end()).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 { for i in 0..=INITIAL_SAMPLES {
let distance_sample = max_split * (i as f64 / INITIAL_SAMPLES as f64); 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 x_point_t = bezpath_algorithms::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 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 x_point = bezier1.eval(x_point_t);
let y_point = bezier2.eval(y_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 { for j in 1..=REFINE_STEPS {
let refined_sample = prev_sample + (distance_sample - prev_sample) * (j as f64 / REFINE_STEPS as f64); 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 x_point_t = bezpath_algorithms::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 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 x_point = bezier1.eval(x_point_t);
let y_point = bezier2.eval(y_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); 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 start = point_to_dvec2(bezier.start());
let end = point_to_dvec2(bezier.end()); 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 start = point_to_dvec2(next_bezier.start());
let end = point_to_dvec2(next_bezier.end()); 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(); let inverse_transform = (vector_data_transform.matrix2.determinant() != 0.).then(|| vector_data_transform.inverse()).unwrap_or_default();
+1 -1
View File
@@ -13,7 +13,6 @@ use graphene_core::uuid::NodeId;
use graphene_core::vector::style::Fill; use graphene_core::vector::style::Fill;
use graphene_core::{Color, MemoHash, Node, Type}; use graphene_core::{Color, MemoHash, Node, Type};
use graphene_svg_renderer::{GraphicElementRendered, RenderMetadata}; use graphene_svg_renderer::{GraphicElementRendered, RenderMetadata};
use std::cell::Cell;
use std::fmt::Display; use std::fmt::Display;
use std::hash::Hash; use std::hash::Hash;
use std::marker::PhantomData; use std::marker::PhantomData;
@@ -564,6 +563,7 @@ thumbnail_render! {
graphene_core::GraphicElement, graphene_core::GraphicElement,
Option<Color>, Option<Color>,
Vec<Color>, Vec<Color>,
f64,
} }
pub enum ThumbnailRenderResult { pub enum ThumbnailRenderResult {
+1
View File
@@ -3,6 +3,7 @@ pub use graph_craft::proto::{Any, NodeContainer, TypeErasedBox, TypeErasedNode};
use graph_craft::proto::{DynFuture, FutureAny, SharedNodeContainer}; use graph_craft::proto::{DynFuture, FutureAny, SharedNodeContainer};
use graphene_core::Context; use graphene_core::Context;
use graphene_core::ContextDependencies; use graphene_core::ContextDependencies;
use graphene_core::EditorContext;
use graphene_core::NodeIO; use graphene_core::NodeIO;
use graphene_core::OwnedContextImpl; use graphene_core::OwnedContextImpl;
use graphene_core::WasmNotSend; use graphene_core::WasmNotSend;
+1 -1
View File
@@ -1,4 +1,4 @@
use crate::vector::{VectorData, VectorDataTable}; use crate::vector::{VectorDataTable};
use graphene_core::Ctx; use graphene_core::Ctx;
pub use graphene_core::text::*; pub use graphene_core::text::*;
+3 -2
View File
@@ -213,6 +213,7 @@ pub trait GraphicElementRendered: BoundingBox + RenderComplexity {
fn render_thumbnail(&self) -> String { fn render_thumbnail(&self) -> String {
let Some(bounds) = self.bounding_box(DAffine2::IDENTITY, true) else { let Some(bounds) = self.bounding_box(DAffine2::IDENTITY, true) else {
log::debug!("Could not get bounds");
return String::new(); return String::new();
}; };
@@ -1193,13 +1194,13 @@ impl Primitive for f64 {}
impl Primitive for DVec2 {} impl Primitive for DVec2 {}
fn text_attributes(attributes: &mut SvgRenderAttrs) { fn text_attributes(attributes: &mut SvgRenderAttrs) {
attributes.push("fill", "white"); attributes.push("fill", "black");
attributes.push("y", "30");
attributes.push("font-size", "30"); attributes.push("font-size", "30");
} }
impl<P: Primitive> GraphicElementRendered for P { impl<P: Primitive> GraphicElementRendered for P {
fn render_svg(&self, render: &mut SvgRender, _render_params: &RenderParams) { 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}"))); render.parent_tag("text", text_attributes, |render| render.leaf_node(format!("{self}")));
} }