mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-15 22:28:10 +08:00
fix animation
This commit is contained in:
@@ -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());
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
}
|
||||
@@ -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(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;
|
||||
|
||||
|
||||
@@ -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 },
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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`.
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -170,6 +170,8 @@ pub enum DocumentMessage {
|
||||
RepeatedAbortTransaction {
|
||||
undo_count: usize,
|
||||
},
|
||||
ToggleAnimation,
|
||||
RestartAnimation,
|
||||
ToggleLayerExpansion {
|
||||
id: NodeId,
|
||||
recursive: bool,
|
||||
|
||||
@@ -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::*;
|
||||
|
||||
@@ -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],
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}) {
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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>>> {
|
||||
|
||||
@@ -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)]
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::vector::{VectorData, VectorDataTable};
|
||||
use crate::vector::{VectorDataTable};
|
||||
use graphene_core::Ctx;
|
||||
pub use graphene_core::text::*;
|
||||
|
||||
|
||||
@@ -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}")));
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user