Experimental animation support (#2443)
* Implement experimental time routing to the node graph * Allow toggling live preview with SHIFT + SPACE * Add animation message handler * Fix hotkeys * Fix milisecond node * Adevertize set frame index action * Fix frame index * Fix year calculation * Add comment for why month and day are not exposed * Combine animation nodes and fix animation time implementation * Fix animation time interaction with playback * Add set animation time mode message * Captalize UTC * Fix compiling * Fix crash and add text nodes --------- Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
parent
b98711dbdb
commit
44694ff8d6
|
|
@ -13,6 +13,7 @@ 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,
|
||||||
|
|
@ -50,12 +51,9 @@ 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] = &[
|
const DEBUG_MESSAGE_BLOCK_LIST: &[MessageDiscriminant] = &[MessageDiscriminant::Broadcast(BroadcastMessageDiscriminant::TriggerEvent(BroadcastEventDiscriminant::AnimationFrame))];
|
||||||
MessageDiscriminant::Broadcast(BroadcastMessageDiscriminant::TriggerEvent(BroadcastEventDiscriminant::AnimationFrame)),
|
|
||||||
MessageDiscriminant::InputPreprocessor(InputPreprocessorMessageDiscriminant::FrameTimeAdvance),
|
|
||||||
];
|
|
||||||
// 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"];
|
const DEBUG_MESSAGE_ENDING_BLOCK_LIST: &[&str] = &["PointerMove", "PointerOutsideViewport", "Overlays", "Draw", "CurrentTime", "Time"];
|
||||||
|
|
||||||
impl Dispatcher {
|
impl Dispatcher {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
|
|
@ -177,6 +175,9 @@ impl Dispatcher {
|
||||||
// Finish loading persistent data from the browser database
|
// Finish loading persistent data from the browser database
|
||||||
queue.add(FrontendMessage::TriggerLoadRestAutoSaveDocuments);
|
queue.add(FrontendMessage::TriggerLoadRestAutoSaveDocuments);
|
||||||
}
|
}
|
||||||
|
Message::Animation(message) => {
|
||||||
|
self.message_handlers.animation_message_handler.process_message(message, &mut queue, ());
|
||||||
|
}
|
||||||
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));
|
||||||
}
|
}
|
||||||
|
|
@ -232,6 +233,7 @@ impl Dispatcher {
|
||||||
let preferences = &self.message_handlers.preferences_message_handler;
|
let preferences = &self.message_handlers.preferences_message_handler;
|
||||||
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 timing_information = self.message_handlers.animation_message_handler.timing_information();
|
||||||
|
|
||||||
self.message_handlers.portfolio_message_handler.process_message(
|
self.message_handlers.portfolio_message_handler.process_message(
|
||||||
message,
|
message,
|
||||||
|
|
@ -241,6 +243,7 @@ impl Dispatcher {
|
||||||
preferences,
|
preferences,
|
||||||
current_tool,
|
current_tool,
|
||||||
message_logging_verbosity,
|
message_logging_verbosity,
|
||||||
|
timing_information,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -283,6 +286,7 @@ 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());
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
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,
|
||||||
|
ResetAnimation,
|
||||||
|
SetFrameIndex(f64),
|
||||||
|
SetTime(f64),
|
||||||
|
UpdateTime,
|
||||||
|
IncrementFrameCounter,
|
||||||
|
SetAnimationTimeMode(AnimationTimeMode),
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,84 @@
|
||||||
|
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(Debug, Default)]
|
||||||
|
pub struct AnimationMessageHandler {
|
||||||
|
live_preview: bool,
|
||||||
|
timestamp: f64,
|
||||||
|
frame_index: f64,
|
||||||
|
animation_start: Option<f64>,
|
||||||
|
fps: f64,
|
||||||
|
animation_time_mode: AnimationTimeMode,
|
||||||
|
}
|
||||||
|
impl AnimationMessageHandler {
|
||||||
|
pub(crate) fn timing_information(&self) -> TimingInformation {
|
||||||
|
let animation_time = self.timestamp - self.animation_start.unwrap_or(self.timestamp);
|
||||||
|
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 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MessageHandler<AnimationMessage, ()> for AnimationMessageHandler {
|
||||||
|
fn process_message(&mut self, message: AnimationMessage, responses: &mut VecDeque<Message>, _data: ()) {
|
||||||
|
match message {
|
||||||
|
AnimationMessage::ToggleLivePreview => {
|
||||||
|
if self.animation_start.is_none() {
|
||||||
|
self.animation_start = Some(self.timestamp);
|
||||||
|
}
|
||||||
|
self.live_preview = !self.live_preview
|
||||||
|
}
|
||||||
|
AnimationMessage::EnableLivePreview => {
|
||||||
|
if self.animation_start.is_none() {
|
||||||
|
self.animation_start = Some(self.timestamp);
|
||||||
|
}
|
||||||
|
self.live_preview = true
|
||||||
|
}
|
||||||
|
AnimationMessage::DisableLivePreview => self.live_preview = false,
|
||||||
|
AnimationMessage::SetFrameIndex(frame) => {
|
||||||
|
self.frame_index = frame;
|
||||||
|
log::debug!("set frame index to {}", frame);
|
||||||
|
responses.add(PortfolioMessage::SubmitActiveGraphRender)
|
||||||
|
}
|
||||||
|
AnimationMessage::SetTime(time) => {
|
||||||
|
self.timestamp = time;
|
||||||
|
responses.add(AnimationMessage::UpdateTime);
|
||||||
|
}
|
||||||
|
AnimationMessage::IncrementFrameCounter => {
|
||||||
|
if self.live_preview {
|
||||||
|
self.frame_index += 1.;
|
||||||
|
responses.add(AnimationMessage::UpdateTime);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
AnimationMessage::UpdateTime => {
|
||||||
|
if self.live_preview {
|
||||||
|
responses.add(PortfolioMessage::SubmitActiveGraphRender)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
AnimationMessage::ResetAnimation => {
|
||||||
|
self.frame_index = 0.;
|
||||||
|
self.animation_start = None;
|
||||||
|
responses.add(PortfolioMessage::SubmitActiveGraphRender)
|
||||||
|
}
|
||||||
|
AnimationMessage::SetAnimationTimeMode(animation_time_mode) => self.animation_time_mode = animation_time_mode,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
advertise_actions!(AnimationMessageDiscriminant;
|
||||||
|
ToggleLivePreview,
|
||||||
|
SetFrameIndex,
|
||||||
|
ResetAnimation,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
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_core::application_io::TimingInformation;
|
||||||
|
|
@ -430,6 +430,9 @@ pub fn input_mappings() -> Mapping {
|
||||||
entry!(KeyDown(Digit0); modifiers=[Alt], action_dispatch=DebugMessage::MessageOff),
|
entry!(KeyDown(Digit0); modifiers=[Alt], action_dispatch=DebugMessage::MessageOff),
|
||||||
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
|
||||||
|
entry!(KeyDown(Space); modifiers=[Shift], action_dispatch=AnimationMessage::ToggleLivePreview),
|
||||||
|
entry!(KeyDown(ArrowLeft); modifiers=[Control], action_dispatch=AnimationMessage::ResetAnimation),
|
||||||
];
|
];
|
||||||
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;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
use crate::messages::input_mapper::utility_types::input_keyboard::{Key, ModifierKeys};
|
use crate::messages::input_mapper::utility_types::input_keyboard::{Key, ModifierKeys};
|
||||||
use crate::messages::input_mapper::utility_types::input_mouse::{EditorMouseState, ViewportBounds};
|
use crate::messages::input_mapper::utility_types::input_mouse::{EditorMouseState, ViewportBounds};
|
||||||
use crate::messages::prelude::*;
|
use crate::messages::prelude::*;
|
||||||
use core::time::Duration;
|
|
||||||
|
|
||||||
#[impl_message(Message, InputPreprocessor)]
|
#[impl_message(Message, InputPreprocessor)]
|
||||||
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]
|
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]
|
||||||
|
|
@ -13,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 },
|
||||||
FrameTimeAdvance { timestamp: Duration },
|
CurrentTime { timestamp: u64 },
|
||||||
WheelScroll { editor_mouse_state: EditorMouseState, modifier_keys: ModifierKeys },
|
WheelScroll { editor_mouse_state: EditorMouseState, modifier_keys: ModifierKeys },
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ pub struct InputPreprocessorMessageData {
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
pub struct InputPreprocessorMessageHandler {
|
pub struct InputPreprocessorMessageHandler {
|
||||||
pub frame_time: FrameTimeInfo,
|
pub frame_time: FrameTimeInfo,
|
||||||
|
pub time: u64,
|
||||||
pub keyboard: KeyStates,
|
pub keyboard: KeyStates,
|
||||||
pub mouse: MouseState,
|
pub mouse: MouseState,
|
||||||
pub viewport_bounds: ViewportBounds,
|
pub viewport_bounds: ViewportBounds,
|
||||||
|
|
@ -93,8 +94,9 @@ impl MessageHandler<InputPreprocessorMessage, InputPreprocessorMessageData> for
|
||||||
|
|
||||||
self.translate_mouse_event(mouse_state, false, responses);
|
self.translate_mouse_event(mouse_state, false, responses);
|
||||||
}
|
}
|
||||||
InputPreprocessorMessage::FrameTimeAdvance { timestamp } => {
|
InputPreprocessorMessage::CurrentTime { timestamp } => {
|
||||||
self.frame_time.advance_timestamp(timestamp);
|
responses.add(AnimationMessage::SetTime(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);
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,8 @@ pub enum Message {
|
||||||
StartBuffer,
|
StartBuffer,
|
||||||
EndBuffer(graphene_std::renderer::RenderMetadata),
|
EndBuffer(graphene_std::renderer::RenderMetadata),
|
||||||
|
|
||||||
|
#[child]
|
||||||
|
Animation(AnimationMessage),
|
||||||
#[child]
|
#[child]
|
||||||
Broadcast(BroadcastMessage),
|
Broadcast(BroadcastMessage),
|
||||||
#[child]
|
#[child]
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
//! 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;
|
||||||
|
|
|
||||||
|
|
@ -2086,7 +2086,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||||
},
|
},
|
||||||
DocumentNodeDefinition {
|
DocumentNodeDefinition {
|
||||||
identifier: "Text",
|
identifier: "Text",
|
||||||
category: "Vector",
|
category: "Text",
|
||||||
node_template: NodeTemplate {
|
node_template: NodeTemplate {
|
||||||
document_node: DocumentNode {
|
document_node: DocumentNode {
|
||||||
implementation: DocumentNodeImplementation::proto("graphene_std::text::TextNode"),
|
implementation: DocumentNodeImplementation::proto("graphene_std::text::TextNode"),
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ use graphene_core::raster::{
|
||||||
use graphene_core::text::Font;
|
use graphene_core::text::Font;
|
||||||
use graphene_core::vector::misc::CentroidType;
|
use graphene_core::vector::misc::CentroidType;
|
||||||
use graphene_core::vector::style::{GradientType, LineCap, LineJoin};
|
use graphene_core::vector::style::{GradientType, LineCap, LineJoin};
|
||||||
|
use graphene_std::animation::RealTimeMode;
|
||||||
use graphene_std::application_io::TextureFrameTable;
|
use graphene_std::application_io::TextureFrameTable;
|
||||||
use graphene_std::transform::Footprint;
|
use graphene_std::transform::Footprint;
|
||||||
use graphene_std::vector::VectorDataTable;
|
use graphene_std::vector::VectorDataTable;
|
||||||
|
|
@ -165,6 +166,7 @@ pub(crate) fn property_from_type(
|
||||||
last.clone()
|
last.clone()
|
||||||
}
|
}
|
||||||
Some(x) if x == TypeId::of::<BlendMode>() => blend_mode(document_node, node_id, index, name, true),
|
Some(x) if x == TypeId::of::<BlendMode>() => blend_mode(document_node, node_id, index, name, true),
|
||||||
|
Some(x) if x == TypeId::of::<RealTimeMode>() => real_time_mode(document_node, node_id, index, name, true),
|
||||||
Some(x) if x == TypeId::of::<RedGreenBlue>() => color_channel(document_node, node_id, index, name, true),
|
Some(x) if x == TypeId::of::<RedGreenBlue>() => color_channel(document_node, node_id, index, name, true),
|
||||||
Some(x) if x == TypeId::of::<RedGreenBlueAlpha>() => rgba_channel(document_node, node_id, index, name, true),
|
Some(x) if x == TypeId::of::<RedGreenBlueAlpha>() => rgba_channel(document_node, node_id, index, name, true),
|
||||||
Some(x) if x == TypeId::of::<NoiseType>() => noise_type(document_node, node_id, index, name, true),
|
Some(x) if x == TypeId::of::<NoiseType>() => noise_type(document_node, node_id, index, name, true),
|
||||||
|
|
@ -778,6 +780,40 @@ pub fn color_channel(document_node: &DocumentNode, node_id: NodeId, index: usize
|
||||||
LayoutGroup::Row { widgets }.with_tooltip("Color Channel")
|
LayoutGroup::Row { widgets }.with_tooltip("Color Channel")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn real_time_mode(document_node: &DocumentNode, node_id: NodeId, index: usize, name: &str, blank_assist: bool) -> LayoutGroup {
|
||||||
|
let mut widgets = start_widgets(document_node, node_id, index, name, FrontendGraphDataType::General, blank_assist);
|
||||||
|
let Some(input) = document_node.inputs.get(index) else {
|
||||||
|
log::warn!("A widget failed to be built because its node's input index is invalid.");
|
||||||
|
return LayoutGroup::Row { widgets: vec![] };
|
||||||
|
};
|
||||||
|
if let Some(&TaggedValue::RealTimeMode(mode)) = input.as_non_exposed_value() {
|
||||||
|
let calculation_modes = [
|
||||||
|
RealTimeMode::Utc,
|
||||||
|
RealTimeMode::Year,
|
||||||
|
RealTimeMode::Hour,
|
||||||
|
RealTimeMode::Minute,
|
||||||
|
RealTimeMode::Second,
|
||||||
|
RealTimeMode::Millisecond,
|
||||||
|
];
|
||||||
|
let mut entries = Vec::with_capacity(calculation_modes.len());
|
||||||
|
for method in calculation_modes {
|
||||||
|
entries.push(
|
||||||
|
MenuListEntry::new(format!("{method:?}"))
|
||||||
|
.label(method.to_string())
|
||||||
|
.on_update(update_value(move |_| TaggedValue::RealTimeMode(method), node_id, index))
|
||||||
|
.on_commit(commit_value),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
let entries = vec![entries];
|
||||||
|
|
||||||
|
widgets.extend_from_slice(&[
|
||||||
|
Separator::new(SeparatorType::Unrelated).widget_holder(),
|
||||||
|
DropdownInput::new(entries).selected_index(Some(mode as u32)).widget_holder(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
LayoutGroup::Row { widgets }.with_tooltip("Real Time Mode")
|
||||||
|
}
|
||||||
|
|
||||||
pub fn rgba_channel(document_node: &DocumentNode, node_id: NodeId, index: usize, name: &str, blank_assist: bool) -> LayoutGroup {
|
pub fn rgba_channel(document_node: &DocumentNode, node_id: NodeId, index: usize, name: &str, blank_assist: bool) -> LayoutGroup {
|
||||||
let mut widgets = start_widgets(document_node, node_id, index, name, FrontendGraphDataType::General, blank_assist);
|
let mut widgets = start_widgets(document_node, node_id, index, name, FrontendGraphDataType::General, blank_assist);
|
||||||
let Some(input) = document_node.inputs.get(index) else {
|
let Some(input) = document_node.inputs.get(index) else {
|
||||||
|
|
|
||||||
|
|
@ -115,6 +115,7 @@ pub enum PortfolioMessage {
|
||||||
bounds: ExportBounds,
|
bounds: ExportBounds,
|
||||||
transparent_background: bool,
|
transparent_background: bool,
|
||||||
},
|
},
|
||||||
|
SubmitActiveGraphRender,
|
||||||
SubmitGraphRender {
|
SubmitGraphRender {
|
||||||
document_id: DocumentId,
|
document_id: DocumentId,
|
||||||
ignore_hash: bool,
|
ignore_hash: bool,
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ use super::spreadsheet::SpreadsheetMessageHandler;
|
||||||
use super::utility_types::{PanelType, PersistentData};
|
use super::utility_types::{PanelType, PersistentData};
|
||||||
use crate::application::generate_uuid;
|
use crate::application::generate_uuid;
|
||||||
use crate::consts::DEFAULT_DOCUMENT_NAME;
|
use crate::consts::DEFAULT_DOCUMENT_NAME;
|
||||||
|
use crate::messages::animation::TimingInformation;
|
||||||
use crate::messages::debug::utility_types::MessageLoggingVerbosity;
|
use crate::messages::debug::utility_types::MessageLoggingVerbosity;
|
||||||
use crate::messages::dialog::simple_dialogs;
|
use crate::messages::dialog::simple_dialogs;
|
||||||
use crate::messages::frontend::utility_types::FrontendDocumentDetails;
|
use crate::messages::frontend::utility_types::FrontendDocumentDetails;
|
||||||
|
|
@ -32,6 +33,7 @@ pub struct PortfolioMessageData<'a> {
|
||||||
pub preferences: &'a PreferencesMessageHandler,
|
pub preferences: &'a PreferencesMessageHandler,
|
||||||
pub current_tool: &'a ToolType,
|
pub current_tool: &'a ToolType,
|
||||||
pub message_logging_verbosity: MessageLoggingVerbosity,
|
pub message_logging_verbosity: MessageLoggingVerbosity,
|
||||||
|
pub timing_information: TimingInformation,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
|
|
@ -56,6 +58,7 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageData<'_>> for PortfolioMes
|
||||||
preferences,
|
preferences,
|
||||||
current_tool,
|
current_tool,
|
||||||
message_logging_verbosity,
|
message_logging_verbosity,
|
||||||
|
timing_information,
|
||||||
} = data;
|
} = data;
|
||||||
|
|
||||||
match message {
|
match message {
|
||||||
|
|
@ -306,6 +309,7 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageData<'_>> for PortfolioMes
|
||||||
let _ = self.executor.submit_node_graph_evaluation(
|
let _ = self.executor.submit_node_graph_evaluation(
|
||||||
self.documents.get_mut(document_id).expect("Tried to render non-existent document"),
|
self.documents.get_mut(document_id).expect("Tried to render non-existent document"),
|
||||||
ipp.viewport_bounds.size().as_uvec2(),
|
ipp.viewport_bounds.size().as_uvec2(),
|
||||||
|
timing_information,
|
||||||
inspect_node,
|
inspect_node,
|
||||||
true,
|
true,
|
||||||
);
|
);
|
||||||
|
|
@ -1072,11 +1076,17 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageData<'_>> for PortfolioMes
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
PortfolioMessage::SubmitActiveGraphRender => {
|
||||||
|
if let Some(document_id) = self.active_document_id {
|
||||||
|
responses.add(PortfolioMessage::SubmitGraphRender { document_id, ignore_hash: false });
|
||||||
|
}
|
||||||
|
}
|
||||||
PortfolioMessage::SubmitGraphRender { document_id, ignore_hash } => {
|
PortfolioMessage::SubmitGraphRender { document_id, ignore_hash } => {
|
||||||
let inspect_node = self.inspect_node_id();
|
let inspect_node = self.inspect_node_id();
|
||||||
let result = self.executor.submit_node_graph_evaluation(
|
let result = self.executor.submit_node_graph_evaluation(
|
||||||
self.documents.get_mut(&document_id).expect("Tried to render non-existent document"),
|
self.documents.get_mut(&document_id).expect("Tried to render non-existent document"),
|
||||||
ipp.viewport_bounds.size().as_uvec2(),
|
ipp.viewport_bounds.size().as_uvec2(),
|
||||||
|
timing_information,
|
||||||
inspect_node,
|
inspect_node,
|
||||||
ignore_hash,
|
ignore_hash,
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
pub use crate::utility_traits::{ActionList, AsMessage, MessageHandler, ToDiscriminant, TransitiveChild};
|
pub use crate::utility_traits::{ActionList, AsMessage, MessageHandler, ToDiscriminant, TransitiveChild};
|
||||||
|
|
||||||
// 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, ExportDialogMessageData, ExportDialogMessageDiscriminant, ExportDialogMessageHandler};
|
pub use crate::messages::dialog::export_dialog::{ExportDialogMessage, ExportDialogMessageData, ExportDialogMessageDiscriminant, ExportDialogMessageHandler};
|
||||||
|
|
|
||||||
|
|
@ -57,7 +57,9 @@ where
|
||||||
|
|
||||||
/// Calculates the bounding box of the layer's text, based on the settings for max width and height specified in the typesetting config.
|
/// Calculates the bounding box of the layer's text, based on the settings for max width and height specified in the typesetting config.
|
||||||
pub fn text_bounding_box(layer: LayerNodeIdentifier, document: &DocumentMessageHandler, font_cache: &FontCache) -> Quad {
|
pub fn text_bounding_box(layer: LayerNodeIdentifier, document: &DocumentMessageHandler, font_cache: &FontCache) -> Quad {
|
||||||
let (text, font, typesetting) = get_text(layer, &document.network_interface).expect("Text layer should have text when interacting with the Text tool");
|
let Some((text, font, typesetting)) = get_text(layer, &document.network_interface) else {
|
||||||
|
return Quad::from_box([DVec2::ZERO, DVec2::ZERO]);
|
||||||
|
};
|
||||||
|
|
||||||
let buzz_face = font_cache.get(font).map(|data| load_face(data));
|
let buzz_face = font_cache.get(font).map(|data| load_face(data));
|
||||||
let far = graphene_core::text::bounding_box(text, buzz_face.as_ref(), typesetting, false);
|
let far = graphene_core::text::bounding_box(text, buzz_face.as_ref(), typesetting, false);
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
use crate::consts::FILE_SAVE_SUFFIX;
|
use crate::consts::FILE_SAVE_SUFFIX;
|
||||||
|
use crate::messages::animation::TimingInformation;
|
||||||
use crate::messages::frontend::utility_types::{ExportBounds, FileType};
|
use crate::messages::frontend::utility_types::{ExportBounds, FileType};
|
||||||
use crate::messages::prelude::*;
|
use crate::messages::prelude::*;
|
||||||
use glam::{DAffine2, DVec2, UVec2};
|
use glam::{DAffine2, DVec2, UVec2};
|
||||||
|
|
@ -572,13 +573,14 @@ impl NodeGraphExecutor {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Adds an evaluate request for whatever current network is cached.
|
/// Adds an evaluate request for whatever current network is cached.
|
||||||
pub(crate) fn submit_current_node_graph_evaluation(&mut self, document: &mut DocumentMessageHandler, viewport_resolution: UVec2) -> Result<(), String> {
|
pub(crate) fn submit_current_node_graph_evaluation(&mut self, document: &mut DocumentMessageHandler, viewport_resolution: UVec2, time: TimingInformation) -> Result<(), String> {
|
||||||
let render_config = RenderConfig {
|
let render_config = RenderConfig {
|
||||||
viewport: Footprint {
|
viewport: Footprint {
|
||||||
transform: document.metadata().document_to_viewport,
|
transform: document.metadata().document_to_viewport,
|
||||||
resolution: viewport_resolution,
|
resolution: viewport_resolution,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
|
time,
|
||||||
#[cfg(any(feature = "resvg", feature = "vello"))]
|
#[cfg(any(feature = "resvg", feature = "vello"))]
|
||||||
export_format: graphene_core::application_io::ExportFormat::Canvas,
|
export_format: graphene_core::application_io::ExportFormat::Canvas,
|
||||||
#[cfg(not(any(feature = "resvg", feature = "vello")))]
|
#[cfg(not(any(feature = "resvg", feature = "vello")))]
|
||||||
|
|
@ -596,9 +598,16 @@ impl NodeGraphExecutor {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Evaluates a node graph, computing the entire graph
|
/// Evaluates a node graph, computing the entire graph
|
||||||
pub fn submit_node_graph_evaluation(&mut self, document: &mut DocumentMessageHandler, viewport_resolution: UVec2, inspect_node: Option<NodeId>, ignore_hash: bool) -> Result<(), String> {
|
pub fn submit_node_graph_evaluation(
|
||||||
|
&mut self,
|
||||||
|
document: &mut DocumentMessageHandler,
|
||||||
|
viewport_resolution: UVec2,
|
||||||
|
time: TimingInformation,
|
||||||
|
inspect_node: Option<NodeId>,
|
||||||
|
ignore_hash: bool,
|
||||||
|
) -> Result<(), String> {
|
||||||
self.update_node_graph(document, inspect_node, ignore_hash)?;
|
self.update_node_graph(document, inspect_node, ignore_hash)?;
|
||||||
self.submit_current_node_graph_evaluation(document, viewport_resolution)?;
|
self.submit_current_node_graph_evaluation(document, viewport_resolution, time)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
@ -623,6 +632,7 @@ impl NodeGraphExecutor {
|
||||||
resolution: (size * export_config.scale_factor).as_uvec2(),
|
resolution: (size * export_config.scale_factor).as_uvec2(),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
|
time: Default::default(),
|
||||||
export_format: graphene_core::application_io::ExportFormat::Svg,
|
export_format: graphene_core::application_io::ExportFormat::Svg,
|
||||||
view_mode: document.view_mode,
|
view_mode: document.view_mode,
|
||||||
hide_artboards: export_config.transparent_background,
|
hide_artboards: export_config.transparent_background,
|
||||||
|
|
|
||||||
|
|
@ -46,7 +46,7 @@ impl EditorTestUtils {
|
||||||
|
|
||||||
let viewport_resolution = glam::UVec2::ONE;
|
let viewport_resolution = glam::UVec2::ONE;
|
||||||
exector
|
exector
|
||||||
.submit_current_node_graph_evaluation(document, viewport_resolution)
|
.submit_current_node_graph_evaluation(document, viewport_resolution, Default::default())
|
||||||
.expect("submit_current_node_graph_evaluation failed");
|
.expect("submit_current_node_graph_evaluation failed");
|
||||||
runtime.run().await;
|
runtime.run().await;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -138,19 +138,18 @@ 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| {
|
||||||
let micros: f64 = timestamp * 1000.;
|
for message in editor.handle_message(InputPreprocessorMessage::CurrentTime {
|
||||||
let timestamp = Duration::from_micros(micros.round() as u64);
|
timestamp: js_sys::Date::now() as u64,
|
||||||
|
}) {
|
||||||
for message in editor.handle_message(InputPreprocessorMessage::FrameTimeAdvance { timestamp }) {
|
|
||||||
handle.send_frontend_message_to_js(message);
|
handle.send_frontend_message_to_js(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
for message in editor.handle_message(BroadcastMessage::TriggerEvent(BroadcastEvent::AnimationFrame)) {
|
for message in editor.handle_message(AnimationMessage::IncrementFrameCounter) {
|
||||||
handle.send_frontend_message_to_js(message);
|
handle.send_frontend_message_to_js(message);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
@ -826,7 +825,13 @@ impl EditorHandle {
|
||||||
let portfolio = &mut editor.dispatcher.message_handlers.portfolio_message_handler;
|
let portfolio = &mut editor.dispatcher.message_handlers.portfolio_message_handler;
|
||||||
portfolio
|
portfolio
|
||||||
.executor
|
.executor
|
||||||
.submit_node_graph_evaluation(portfolio.documents.get_mut(&portfolio.active_document_id().unwrap()).unwrap(), glam::UVec2::ONE, None, true)
|
.submit_node_graph_evaluation(
|
||||||
|
portfolio.documents.get_mut(&portfolio.active_document_id().unwrap()).unwrap(),
|
||||||
|
glam::UVec2::ONE,
|
||||||
|
Default::default(),
|
||||||
|
None,
|
||||||
|
true,
|
||||||
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
editor::node_graph_executor::run_node_graph().await;
|
editor::node_graph_executor::run_node_graph().await;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -295,6 +295,7 @@ impl<PointId: crate::Identifier> Subpath<PointId> {
|
||||||
|
|
||||||
/// Constructs a regular polygon (ngon). Based on `sides` and `radius`, which is the distance from the center to any vertex.
|
/// Constructs a regular polygon (ngon). Based on `sides` and `radius`, which is the distance from the center to any vertex.
|
||||||
pub fn new_regular_polygon(center: DVec2, sides: u64, radius: f64) -> Self {
|
pub fn new_regular_polygon(center: DVec2, sides: u64, radius: f64) -> Self {
|
||||||
|
let sides = sides.max(3);
|
||||||
let angle_increment = std::f64::consts::TAU / (sides as f64);
|
let angle_increment = std::f64::consts::TAU / (sides as f64);
|
||||||
let anchor_positions = (0..sides).map(|i| {
|
let anchor_positions = (0..sides).map(|i| {
|
||||||
let angle = (i as f64) * angle_increment - std::f64::consts::FRAC_PI_2;
|
let angle = (i as f64) * angle_increment - std::f64::consts::FRAC_PI_2;
|
||||||
|
|
@ -306,6 +307,7 @@ impl<PointId: crate::Identifier> Subpath<PointId> {
|
||||||
|
|
||||||
/// Constructs a star polygon (n-star). See [new_regular_polygon], but with interspersed vertices at an `inner_radius`.
|
/// Constructs a star polygon (n-star). See [new_regular_polygon], but with interspersed vertices at an `inner_radius`.
|
||||||
pub fn new_star_polygon(center: DVec2, sides: u64, radius: f64, inner_radius: f64) -> Self {
|
pub fn new_star_polygon(center: DVec2, sides: u64, radius: f64, inner_radius: f64) -> Self {
|
||||||
|
let sides = sides.max(2);
|
||||||
let angle_increment = 0.5 * std::f64::consts::TAU / (sides as f64);
|
let angle_increment = 0.5 * std::f64::consts::TAU / (sides as f64);
|
||||||
let anchor_positions = (0..sides * 2).map(|i| {
|
let anchor_positions = (0..sides * 2).map(|i| {
|
||||||
let angle = (i as f64) * angle_increment - std::f64::consts::FRAC_PI_2;
|
let angle = (i as f64) * angle_increment - std::f64::consts::FRAC_PI_2;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,64 @@
|
||||||
|
use crate::{Ctx, ExtractAnimationTime, ExtractTime};
|
||||||
|
|
||||||
|
const DAY: f64 = 1000. * 3600. * 24.;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, dyn_any::DynAny, Default, Hash)]
|
||||||
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
|
pub enum RealTimeMode {
|
||||||
|
Utc,
|
||||||
|
Year,
|
||||||
|
Hour,
|
||||||
|
Minute,
|
||||||
|
#[default]
|
||||||
|
Second,
|
||||||
|
Millisecond,
|
||||||
|
}
|
||||||
|
impl core::fmt::Display for RealTimeMode {
|
||||||
|
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||||
|
match self {
|
||||||
|
RealTimeMode::Utc => write!(f, "UTC"),
|
||||||
|
RealTimeMode::Year => write!(f, "Year"),
|
||||||
|
RealTimeMode::Hour => write!(f, "Hour"),
|
||||||
|
RealTimeMode::Minute => write!(f, "Minute"),
|
||||||
|
RealTimeMode::Second => write!(f, "Second"),
|
||||||
|
RealTimeMode::Millisecond => write!(f, "Millisecond"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub enum AnimationTimeMode {
|
||||||
|
AnimationTime,
|
||||||
|
FrameNumber,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[node_macro::node(category("Animation"))]
|
||||||
|
fn real_time(ctx: impl Ctx + ExtractTime, _primary: (), mode: RealTimeMode) -> f64 {
|
||||||
|
let time = ctx.try_time().unwrap_or_default();
|
||||||
|
// TODO: Implement proper conversion using and existing time implementation
|
||||||
|
match mode {
|
||||||
|
RealTimeMode::Utc => time,
|
||||||
|
RealTimeMode::Year => (time / DAY / 365.25).floor() + 1970.,
|
||||||
|
RealTimeMode::Hour => (time / 1000. / 3600.).floor() % 24.,
|
||||||
|
RealTimeMode::Minute => (time / 1000. / 60.).floor() % 60.,
|
||||||
|
|
||||||
|
RealTimeMode::Second => (time / 1000.).floor() % 60.,
|
||||||
|
RealTimeMode::Millisecond => time % 1000.,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[node_macro::node(category("Animation"))]
|
||||||
|
fn animation_time(ctx: impl Ctx + ExtractAnimationTime) -> f64 {
|
||||||
|
ctx.try_animation_time().unwrap_or_default()
|
||||||
|
}
|
||||||
|
|
||||||
|
// These nodes require more sophistcated algorithms for giving the correct result
|
||||||
|
|
||||||
|
// #[node_macro::node(category("Animation"))]
|
||||||
|
// fn month(ctx: impl Ctx + ExtractTime) -> f64 {
|
||||||
|
// ((ctx.try_time().unwrap_or_default() / DAY / 365.25 % 1.) * 12.).floor()
|
||||||
|
// }
|
||||||
|
// #[node_macro::node(category("Animation"))]
|
||||||
|
// fn day(ctx: impl Ctx + ExtractTime) -> f64 {
|
||||||
|
// (ctx.try_time().unwrap_or_default() / DAY
|
||||||
|
// }
|
||||||
|
|
@ -8,6 +8,7 @@ use core::future::Future;
|
||||||
use core::hash::{Hash, Hasher};
|
use core::hash::{Hash, Hasher};
|
||||||
use core::pin::Pin;
|
use core::pin::Pin;
|
||||||
use core::ptr::addr_of;
|
use core::ptr::addr_of;
|
||||||
|
use core::time::Duration;
|
||||||
use dyn_any::{DynAny, StaticType, StaticTypeSized};
|
use dyn_any::{DynAny, StaticType, StaticTypeSized};
|
||||||
use glam::{DAffine2, UVec2};
|
use glam::{DAffine2, UVec2};
|
||||||
|
|
||||||
|
|
@ -250,10 +251,17 @@ pub enum ExportFormat {
|
||||||
Canvas,
|
Canvas,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Default)]
|
||||||
|
pub struct TimingInformation {
|
||||||
|
pub time: f64,
|
||||||
|
pub animation_time: Duration,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default, Clone, Copy, PartialEq, DynAny)]
|
#[derive(Debug, Default, Clone, Copy, PartialEq, DynAny)]
|
||||||
pub struct RenderConfig {
|
pub struct RenderConfig {
|
||||||
pub viewport: Footprint,
|
pub viewport: Footprint,
|
||||||
pub export_format: ExportFormat,
|
pub export_format: ExportFormat,
|
||||||
|
pub time: TimingInformation,
|
||||||
pub view_mode: ViewMode,
|
pub view_mode: ViewMode,
|
||||||
pub hide_artboards: bool,
|
pub hide_artboards: bool,
|
||||||
pub for_export: bool,
|
pub for_export: bool,
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,10 @@ pub trait ExtractTime {
|
||||||
fn try_time(&self) -> Option<f64>;
|
fn try_time(&self) -> Option<f64>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub trait ExtractAnimationTime {
|
||||||
|
fn try_animation_time(&self) -> Option<f64>;
|
||||||
|
}
|
||||||
|
|
||||||
pub trait ExtractIndex {
|
pub trait ExtractIndex {
|
||||||
fn try_index(&self) -> Option<usize>;
|
fn try_index(&self) -> Option<usize>;
|
||||||
}
|
}
|
||||||
|
|
@ -38,9 +42,9 @@ pub trait CloneVarArgs: ExtractVarArgs {
|
||||||
fn arc_clone(&self) -> Option<Arc<dyn ExtractVarArgs + Send + Sync>>;
|
fn arc_clone(&self) -> Option<Arc<dyn ExtractVarArgs + Send + Sync>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait ExtractAll: ExtractFootprint + ExtractIndex + ExtractTime + ExtractVarArgs {}
|
pub trait ExtractAll: ExtractFootprint + ExtractIndex + ExtractTime + ExtractAnimationTime + ExtractVarArgs {}
|
||||||
|
|
||||||
impl<T: ?Sized + ExtractFootprint + ExtractIndex + ExtractTime + ExtractVarArgs> ExtractAll for T {}
|
impl<T: ?Sized + ExtractFootprint + ExtractIndex + ExtractTime + ExtractAnimationTime + ExtractVarArgs> ExtractAll for T {}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub enum VarArgsResult {
|
pub enum VarArgsResult {
|
||||||
|
|
@ -81,6 +85,11 @@ impl<T: ExtractTime + Sync> ExtractTime for Option<T> {
|
||||||
self.as_ref().and_then(|x| x.try_time())
|
self.as_ref().and_then(|x| x.try_time())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
impl<T: ExtractAnimationTime + Sync> ExtractAnimationTime for Option<T> {
|
||||||
|
fn try_animation_time(&self) -> Option<f64> {
|
||||||
|
self.as_ref().and_then(|x| x.try_animation_time())
|
||||||
|
}
|
||||||
|
}
|
||||||
impl<T: ExtractIndex> ExtractIndex for Option<T> {
|
impl<T: ExtractIndex> ExtractIndex for Option<T> {
|
||||||
fn try_index(&self) -> Option<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())
|
||||||
|
|
@ -107,6 +116,11 @@ impl<T: ExtractTime + Sync> ExtractTime for Arc<T> {
|
||||||
(**self).try_time()
|
(**self).try_time()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
impl<T: ExtractAnimationTime + Sync> ExtractAnimationTime for Arc<T> {
|
||||||
|
fn try_animation_time(&self) -> Option<f64> {
|
||||||
|
(**self).try_animation_time()
|
||||||
|
}
|
||||||
|
}
|
||||||
impl<T: ExtractIndex> ExtractIndex for Arc<T> {
|
impl<T: ExtractIndex> ExtractIndex for Arc<T> {
|
||||||
fn try_index(&self) -> Option<usize> {
|
fn try_index(&self) -> Option<usize> {
|
||||||
(**self).try_index()
|
(**self).try_index()
|
||||||
|
|
@ -182,6 +196,11 @@ impl ExtractTime for OwnedContextImpl {
|
||||||
self.time
|
self.time
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
impl ExtractAnimationTime for OwnedContextImpl {
|
||||||
|
fn try_animation_time(&self) -> Option<f64> {
|
||||||
|
self.animation_time
|
||||||
|
}
|
||||||
|
}
|
||||||
impl ExtractIndex for OwnedContextImpl {
|
impl ExtractIndex for OwnedContextImpl {
|
||||||
fn try_index(&self) -> Option<usize> {
|
fn try_index(&self) -> Option<usize> {
|
||||||
self.index
|
self.index
|
||||||
|
|
@ -227,6 +246,7 @@ pub struct OwnedContextImpl {
|
||||||
// This could be converted into a single enum to save extra bytes
|
// This could be converted into a single enum to save extra bytes
|
||||||
index: Option<usize>,
|
index: Option<usize>,
|
||||||
time: Option<f64>,
|
time: Option<f64>,
|
||||||
|
animation_time: Option<f64>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for OwnedContextImpl {
|
impl Default for OwnedContextImpl {
|
||||||
|
|
@ -252,6 +272,7 @@ impl OwnedContextImpl {
|
||||||
let footprint = value.try_footprint().copied();
|
let footprint = value.try_footprint().copied();
|
||||||
let index = value.try_index();
|
let index = value.try_index();
|
||||||
let time = value.try_time();
|
let time = value.try_time();
|
||||||
|
let frame_time = value.try_animation_time();
|
||||||
let parent = value.arc_clone();
|
let parent = value.arc_clone();
|
||||||
OwnedContextImpl {
|
OwnedContextImpl {
|
||||||
footprint,
|
footprint,
|
||||||
|
|
@ -259,6 +280,7 @@ impl OwnedContextImpl {
|
||||||
parent,
|
parent,
|
||||||
index,
|
index,
|
||||||
time,
|
time,
|
||||||
|
animation_time: frame_time,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub const fn empty() -> Self {
|
pub const fn empty() -> Self {
|
||||||
|
|
@ -268,6 +290,7 @@ impl OwnedContextImpl {
|
||||||
parent: None,
|
parent: None,
|
||||||
index: None,
|
index: None,
|
||||||
time: None,
|
time: None,
|
||||||
|
animation_time: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -280,6 +303,14 @@ impl OwnedContextImpl {
|
||||||
self.footprint = Some(footprint);
|
self.footprint = Some(footprint);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
pub fn with_time(mut self, time: f64) -> Self {
|
||||||
|
self.time = Some(time);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
pub fn with_animation_time(mut self, animation_time: f64) -> Self {
|
||||||
|
self.animation_time = Some(animation_time);
|
||||||
|
self
|
||||||
|
}
|
||||||
pub fn into_context(self) -> Option<Arc<Self>> {
|
pub fn into_context(self) -> Option<Arc<Self>> {
|
||||||
Some(Arc::new(self))
|
Some(Arc::new(self))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -296,31 +296,31 @@ async fn layer(_: impl Ctx, mut stack: GraphicGroupTable, element: GraphicElemen
|
||||||
stack
|
stack
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Once we have nicely working spreadsheet tables, test this and make it nicely user-facing and move it from "Debug" to "General"
|
// // TODO: Once we have nicely working spreadsheet tables, test this and make it nicely user-facing and move it from "Debug" to "General"
|
||||||
#[node_macro::node(category("Debug"))]
|
// #[node_macro::node(category("Debug"))]
|
||||||
async fn concatenate<T: Clone>(
|
// async fn concatenate<T: Clone>(
|
||||||
_: impl Ctx,
|
// _: impl Ctx,
|
||||||
#[implementations(
|
// #[implementations(
|
||||||
GraphicGroupTable,
|
// GraphicGroupTable,
|
||||||
VectorDataTable,
|
// VectorDataTable,
|
||||||
ImageFrameTable<Color>,
|
// ImageFrameTable<Color>,
|
||||||
TextureFrameTable,
|
// TextureFrameTable,
|
||||||
)]
|
// )]
|
||||||
from: Instances<T>,
|
// from: Instances<T>,
|
||||||
#[expose]
|
// #[expose]
|
||||||
#[implementations(
|
// #[implementations(
|
||||||
GraphicGroupTable,
|
// GraphicGroupTable,
|
||||||
VectorDataTable,
|
// VectorDataTable,
|
||||||
ImageFrameTable<Color>,
|
// ImageFrameTable<Color>,
|
||||||
TextureFrameTable,
|
// TextureFrameTable,
|
||||||
)]
|
// )]
|
||||||
mut to: Instances<T>,
|
// mut to: Instances<T>,
|
||||||
) -> Instances<T> {
|
// ) -> Instances<T> {
|
||||||
for instance in from.instances() {
|
// for instance in from.instances() {
|
||||||
to.push_instance(instance);
|
// to.push_instance(instance);
|
||||||
}
|
// }
|
||||||
to
|
// to
|
||||||
}
|
// }
|
||||||
|
|
||||||
#[node_macro::node(category("Debug"))]
|
#[node_macro::node(category("Debug"))]
|
||||||
async fn to_element<Data: Into<GraphicElement> + 'n>(
|
async fn to_element<Data: Into<GraphicElement> + 'n>(
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ pub use crate as graphene_core;
|
||||||
#[cfg(feature = "reflections")]
|
#[cfg(feature = "reflections")]
|
||||||
pub use ctor;
|
pub use ctor;
|
||||||
|
|
||||||
|
pub mod animation;
|
||||||
pub mod consts;
|
pub mod consts;
|
||||||
pub mod context;
|
pub mod context;
|
||||||
pub mod generic;
|
pub mod generic;
|
||||||
|
|
|
||||||
|
|
@ -10,12 +10,35 @@ fn log_to_console<T: core::fmt::Debug>(_: impl Ctx, #[implementations(String, bo
|
||||||
value
|
value
|
||||||
}
|
}
|
||||||
|
|
||||||
#[node_macro::node(category("Debug"), skip_impl)]
|
#[node_macro::node(category("Text"))]
|
||||||
fn to_string<T: core::fmt::Debug>(_: impl Ctx, #[implementations(String, bool, f64, u32, u64, DVec2, VectorDataTable, DAffine2)] value: T) -> String {
|
fn to_string<T: core::fmt::Debug>(_: impl Ctx, #[implementations(String, bool, f64, u32, u64, DVec2, VectorDataTable, DAffine2)] value: T) -> String {
|
||||||
format!("{:?}", value)
|
format!("{:?}", value)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[node_macro::node(category("Debug"))]
|
#[node_macro::node(category("Text"))]
|
||||||
|
fn string_concatenate(_: impl Ctx, #[implementations(String)] first: String, #[implementations(String)] second: String) -> String {
|
||||||
|
first.clone() + &second
|
||||||
|
}
|
||||||
|
|
||||||
|
#[node_macro::node(category("Text"))]
|
||||||
|
fn string_replace(_: impl Ctx, #[implementations(String)] string: String, from: String, to: String) -> String {
|
||||||
|
string.replace(&from, &to)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[node_macro::node(category("Text"))]
|
||||||
|
fn string_slice(_: impl Ctx, #[implementations(String)] string: String, start: f64, end: f64) -> String {
|
||||||
|
let start = if start < 0. { string.len() - start.abs() as usize } else { start as usize };
|
||||||
|
let end = if end <= 0. { string.len() - end.abs() as usize } else { end as usize };
|
||||||
|
let n = end.saturating_sub(start);
|
||||||
|
string.char_indices().skip(start).take(n).map(|(_, c)| c).collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[node_macro::node(category("Text"))]
|
||||||
|
fn string_length(_: impl Ctx, #[implementations(String)] string: String) -> usize {
|
||||||
|
string.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[node_macro::node(category("Text"))]
|
||||||
async fn switch<T, C: Send + 'n + Clone>(
|
async fn switch<T, C: Send + 'n + Clone>(
|
||||||
#[implementations(Context)] ctx: C,
|
#[implementations(Context)] ctx: C,
|
||||||
condition: bool,
|
condition: bool,
|
||||||
|
|
|
||||||
|
|
@ -412,6 +412,12 @@ fn blend_mode_value(_: impl Ctx, _primary: (), blend_mode: BlendMode) -> BlendMo
|
||||||
blend_mode
|
blend_mode
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Constructs a string value which may be set to any plain text.
|
||||||
|
#[node_macro::node(category("Value"))]
|
||||||
|
fn string_value(_: impl Ctx, _primary: (), string: String) -> String {
|
||||||
|
string
|
||||||
|
}
|
||||||
|
|
||||||
/// Meant for debugging purposes, not general use. Returns the size of the input type in bytes.
|
/// Meant for debugging purposes, not general use. Returns the size of the input type in bytes.
|
||||||
#[cfg(feature = "std")]
|
#[cfg(feature = "std")]
|
||||||
#[node_macro::node(category("Debug"))]
|
#[node_macro::node(category("Debug"))]
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,8 @@ use crate::vector::{HandleId, VectorData, VectorDataTable};
|
||||||
use bezier_rs::Subpath;
|
use bezier_rs::Subpath;
|
||||||
use glam::DVec2;
|
use glam::DVec2;
|
||||||
|
|
||||||
|
use super::misc::AsU64;
|
||||||
|
|
||||||
trait CornerRadius {
|
trait CornerRadius {
|
||||||
fn generate(self, size: DVec2, clamped: bool) -> VectorDataTable;
|
fn generate(self, size: DVec2, clamped: bool) -> VectorDataTable;
|
||||||
}
|
}
|
||||||
|
|
@ -70,30 +72,32 @@ fn rectangle<T: CornerRadius>(
|
||||||
}
|
}
|
||||||
|
|
||||||
#[node_macro::node(category("Vector: Shape"))]
|
#[node_macro::node(category("Vector: Shape"))]
|
||||||
fn regular_polygon(
|
fn regular_polygon<T: AsU64>(
|
||||||
_: impl Ctx,
|
_: impl Ctx,
|
||||||
_primary: (),
|
_primary: (),
|
||||||
#[default(6)]
|
#[default(6)]
|
||||||
#[min(3.)]
|
#[min(3.)]
|
||||||
sides: u32,
|
#[implementations(u32, u64, f64)]
|
||||||
|
sides: T,
|
||||||
#[default(50)] radius: f64,
|
#[default(50)] radius: f64,
|
||||||
) -> VectorDataTable {
|
) -> VectorDataTable {
|
||||||
let points = sides.into();
|
let points = sides.as_u64();
|
||||||
let radius: f64 = radius * 2.;
|
let radius: f64 = radius * 2.;
|
||||||
VectorDataTable::new(VectorData::from_subpath(Subpath::new_regular_polygon(DVec2::splat(-radius), points, radius)))
|
VectorDataTable::new(VectorData::from_subpath(Subpath::new_regular_polygon(DVec2::splat(-radius), points, radius)))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[node_macro::node(category("Vector: Shape"))]
|
#[node_macro::node(category("Vector: Shape"))]
|
||||||
fn star(
|
fn star<T: AsU64>(
|
||||||
_: impl Ctx,
|
_: impl Ctx,
|
||||||
_primary: (),
|
_primary: (),
|
||||||
#[default(5)]
|
#[default(5)]
|
||||||
#[min(2.)]
|
#[min(2.)]
|
||||||
sides: u32,
|
#[implementations(u32, u64, f64)]
|
||||||
|
sides: T,
|
||||||
#[default(50)] radius: f64,
|
#[default(50)] radius: f64,
|
||||||
#[default(25)] inner_radius: f64,
|
#[default(25)] inner_radius: f64,
|
||||||
) -> VectorDataTable {
|
) -> VectorDataTable {
|
||||||
let points = sides.into();
|
let points = sides.as_u64();
|
||||||
let diameter: f64 = radius * 2.;
|
let diameter: f64 = radius * 2.;
|
||||||
let inner_diameter = inner_radius * 2.;
|
let inner_diameter = inner_radius * 2.;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -47,3 +47,41 @@ impl core::fmt::Display for BooleanOperation {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub trait AsU64 {
|
||||||
|
fn as_u64(&self) -> u64;
|
||||||
|
}
|
||||||
|
impl AsU64 for u32 {
|
||||||
|
fn as_u64(&self) -> u64 {
|
||||||
|
*self as u64
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl AsU64 for u64 {
|
||||||
|
fn as_u64(&self) -> u64 {
|
||||||
|
*self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl AsU64 for f64 {
|
||||||
|
fn as_u64(&self) -> u64 {
|
||||||
|
*self as u64
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait AsI64 {
|
||||||
|
fn as_i64(&self) -> i64;
|
||||||
|
}
|
||||||
|
impl AsI64 for u32 {
|
||||||
|
fn as_i64(&self) -> i64 {
|
||||||
|
*self as i64
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl AsI64 for u64 {
|
||||||
|
fn as_i64(&self) -> i64 {
|
||||||
|
*self as i64
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl AsI64 for f64 {
|
||||||
|
fn as_i64(&self) -> i64 {
|
||||||
|
*self as i64
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -182,6 +182,7 @@ tagged_value! {
|
||||||
NodePath(Vec<NodeId>),
|
NodePath(Vec<NodeId>),
|
||||||
VecDVec2(Vec<DVec2>),
|
VecDVec2(Vec<DVec2>),
|
||||||
RedGreenBlue(graphene_core::raster::RedGreenBlue),
|
RedGreenBlue(graphene_core::raster::RedGreenBlue),
|
||||||
|
RealTimeMode(graphene_core::animation::RealTimeMode),
|
||||||
RedGreenBlueAlpha(graphene_core::raster::RedGreenBlueAlpha),
|
RedGreenBlueAlpha(graphene_core::raster::RedGreenBlueAlpha),
|
||||||
NoiseType(graphene_core::raster::NoiseType),
|
NoiseType(graphene_core::raster::NoiseType),
|
||||||
FractalType(graphene_core::raster::FractalType),
|
FractalType(graphene_core::raster::FractalType),
|
||||||
|
|
|
||||||
|
|
@ -559,12 +559,10 @@ impl core::fmt::Debug for GraphErrorType {
|
||||||
let inputs = inputs.replace("Option<Arc<OwnedContextImpl>>", "Context");
|
let inputs = inputs.replace("Option<Arc<OwnedContextImpl>>", "Context");
|
||||||
write!(
|
write!(
|
||||||
f,
|
f,
|
||||||
"This node isn't compatible with the com-\n\
|
"This node isn't compatible with the combination of types for the data it is given:\n\
|
||||||
bination of types for the data it is given:\n\
|
|
||||||
{inputs}\n\
|
{inputs}\n\
|
||||||
\n\
|
\n\
|
||||||
Each invalid input should be replaced by\n\
|
Each invalid input should be replaced by data with one of these supported types:\n\
|
||||||
data with one of these supported types:\n\
|
|
||||||
{}",
|
{}",
|
||||||
errors.join("\n")
|
errors.join("\n")
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -235,7 +235,11 @@ async fn render<'a: 'n, T: 'n + GraphicElementRendered + WasmNotSend>(
|
||||||
_surface_handle: impl Node<Context<'static>, Output = Option<wgpu_executor::WgpuSurface>>,
|
_surface_handle: impl Node<Context<'static>, Output = Option<wgpu_executor::WgpuSurface>>,
|
||||||
) -> RenderOutput {
|
) -> RenderOutput {
|
||||||
let footprint = render_config.viewport;
|
let footprint = render_config.viewport;
|
||||||
let ctx = OwnedContextImpl::default().with_footprint(footprint).into_context();
|
let ctx = OwnedContextImpl::default()
|
||||||
|
.with_footprint(footprint)
|
||||||
|
.with_time(render_config.time.time)
|
||||||
|
.with_animation_time(render_config.time.animation_time.as_secs_f64())
|
||||||
|
.into_context();
|
||||||
ctx.footprint();
|
ctx.footprint();
|
||||||
|
|
||||||
let RenderConfig { hide_artboards, for_export, .. } = render_config;
|
let RenderConfig { hide_artboards, for_export, .. } = render_config;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue