Add animation control buttons to document bar
This commit is contained in:
parent
44694ff8d6
commit
08a4b69948
|
|
@ -234,6 +234,7 @@ impl Dispatcher {
|
||||||
let current_tool = &self.message_handlers.tool_message_handler.tool_state.tool_data.active_tool_type;
|
let current_tool = &self.message_handlers.tool_message_handler.tool_state.tool_data.active_tool_type;
|
||||||
let message_logging_verbosity = self.message_handlers.debug_message_handler.message_logging_verbosity;
|
let message_logging_verbosity = self.message_handlers.debug_message_handler.message_logging_verbosity;
|
||||||
let timing_information = self.message_handlers.animation_message_handler.timing_information();
|
let timing_information = self.message_handlers.animation_message_handler.timing_information();
|
||||||
|
let animation = &self.message_handlers.animation_message_handler;
|
||||||
|
|
||||||
self.message_handlers.portfolio_message_handler.process_message(
|
self.message_handlers.portfolio_message_handler.process_message(
|
||||||
message,
|
message,
|
||||||
|
|
@ -244,6 +245,7 @@ impl Dispatcher {
|
||||||
current_tool,
|
current_tool,
|
||||||
message_logging_verbosity,
|
message_logging_verbosity,
|
||||||
timing_information,
|
timing_information,
|
||||||
|
animation,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ pub enum AnimationMessage {
|
||||||
ToggleLivePreview,
|
ToggleLivePreview,
|
||||||
EnableLivePreview,
|
EnableLivePreview,
|
||||||
DisableLivePreview,
|
DisableLivePreview,
|
||||||
ResetAnimation,
|
RestartAnimation,
|
||||||
SetFrameIndex(f64),
|
SetFrameIndex(f64),
|
||||||
SetTime(f64),
|
SetTime(f64),
|
||||||
UpdateTime,
|
UpdateTime,
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,8 @@ pub enum AnimationTimeMode {
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
pub struct AnimationMessageHandler {
|
pub struct AnimationMessageHandler {
|
||||||
live_preview: bool,
|
live_preview: bool,
|
||||||
|
/// Used to re-send the UI on the next frame after playback starts
|
||||||
|
live_preview_recently_zero: bool,
|
||||||
timestamp: f64,
|
timestamp: f64,
|
||||||
frame_index: f64,
|
frame_index: f64,
|
||||||
animation_start: Option<f64>,
|
animation_start: Option<f64>,
|
||||||
|
|
@ -29,6 +31,10 @@ impl AnimationMessageHandler {
|
||||||
};
|
};
|
||||||
TimingInformation { time: self.timestamp, animation_time }
|
TimingInformation { time: self.timestamp, animation_time }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn is_playing(&self) -> bool {
|
||||||
|
self.live_preview
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MessageHandler<AnimationMessage, ()> for AnimationMessageHandler {
|
impl MessageHandler<AnimationMessage, ()> for AnimationMessageHandler {
|
||||||
|
|
@ -38,23 +44,37 @@ impl MessageHandler<AnimationMessage, ()> for AnimationMessageHandler {
|
||||||
if self.animation_start.is_none() {
|
if self.animation_start.is_none() {
|
||||||
self.animation_start = Some(self.timestamp);
|
self.animation_start = Some(self.timestamp);
|
||||||
}
|
}
|
||||||
self.live_preview = !self.live_preview
|
self.live_preview = !self.live_preview;
|
||||||
|
|
||||||
|
// Update the restart and pause/play buttons
|
||||||
|
responses.add(PortfolioMessage::UpdateDocumentWidgets);
|
||||||
}
|
}
|
||||||
AnimationMessage::EnableLivePreview => {
|
AnimationMessage::EnableLivePreview => {
|
||||||
if self.animation_start.is_none() {
|
if self.animation_start.is_none() {
|
||||||
self.animation_start = Some(self.timestamp);
|
self.animation_start = Some(self.timestamp);
|
||||||
}
|
}
|
||||||
self.live_preview = true
|
self.live_preview = true;
|
||||||
|
|
||||||
|
// Update the restart and pause/play buttons
|
||||||
|
responses.add(PortfolioMessage::UpdateDocumentWidgets);
|
||||||
|
}
|
||||||
|
AnimationMessage::DisableLivePreview => {
|
||||||
|
self.live_preview = false;
|
||||||
|
|
||||||
|
// Update the restart and pause/play buttons
|
||||||
|
responses.add(PortfolioMessage::UpdateDocumentWidgets);
|
||||||
}
|
}
|
||||||
AnimationMessage::DisableLivePreview => self.live_preview = false,
|
|
||||||
AnimationMessage::SetFrameIndex(frame) => {
|
AnimationMessage::SetFrameIndex(frame) => {
|
||||||
self.frame_index = frame;
|
self.frame_index = frame;
|
||||||
log::debug!("set frame index to {}", frame);
|
responses.add(PortfolioMessage::SubmitActiveGraphRender);
|
||||||
responses.add(PortfolioMessage::SubmitActiveGraphRender)
|
// Update the restart and pause/play buttons
|
||||||
|
responses.add(PortfolioMessage::UpdateDocumentWidgets);
|
||||||
}
|
}
|
||||||
AnimationMessage::SetTime(time) => {
|
AnimationMessage::SetTime(time) => {
|
||||||
self.timestamp = time;
|
self.timestamp = time;
|
||||||
responses.add(AnimationMessage::UpdateTime);
|
if self.live_preview {
|
||||||
|
responses.add(AnimationMessage::UpdateTime);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
AnimationMessage::IncrementFrameCounter => {
|
AnimationMessage::IncrementFrameCounter => {
|
||||||
if self.live_preview {
|
if self.live_preview {
|
||||||
|
|
@ -64,21 +84,32 @@ impl MessageHandler<AnimationMessage, ()> for AnimationMessageHandler {
|
||||||
}
|
}
|
||||||
AnimationMessage::UpdateTime => {
|
AnimationMessage::UpdateTime => {
|
||||||
if self.live_preview {
|
if self.live_preview {
|
||||||
responses.add(PortfolioMessage::SubmitActiveGraphRender)
|
responses.add(PortfolioMessage::SubmitActiveGraphRender);
|
||||||
|
|
||||||
|
if self.live_preview_recently_zero {
|
||||||
|
// Update the restart and pause/play buttons
|
||||||
|
responses.add(PortfolioMessage::UpdateDocumentWidgets);
|
||||||
|
self.live_preview_recently_zero = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
AnimationMessage::ResetAnimation => {
|
AnimationMessage::RestartAnimation => {
|
||||||
self.frame_index = 0.;
|
self.frame_index = 0.;
|
||||||
self.animation_start = None;
|
self.animation_start = None;
|
||||||
responses.add(PortfolioMessage::SubmitActiveGraphRender)
|
self.live_preview_recently_zero = true;
|
||||||
|
responses.add(PortfolioMessage::SubmitActiveGraphRender);
|
||||||
|
// Update the restart and pause/play buttons
|
||||||
|
responses.add(PortfolioMessage::UpdateDocumentWidgets);
|
||||||
|
}
|
||||||
|
AnimationMessage::SetAnimationTimeMode(animation_time_mode) => {
|
||||||
|
self.animation_time_mode = animation_time_mode;
|
||||||
}
|
}
|
||||||
AnimationMessage::SetAnimationTimeMode(animation_time_mode) => self.animation_time_mode = animation_time_mode,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
advertise_actions!(AnimationMessageDiscriminant;
|
advertise_actions!(AnimationMessageDiscriminant;
|
||||||
ToggleLivePreview,
|
ToggleLivePreview,
|
||||||
SetFrameIndex,
|
SetFrameIndex,
|
||||||
ResetAnimation,
|
RestartAnimation,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -432,7 +432,7 @@ pub fn input_mappings() -> Mapping {
|
||||||
entry!(KeyDown(Digit2); modifiers=[Alt], action_dispatch=DebugMessage::MessageContents),
|
entry!(KeyDown(Digit2); modifiers=[Alt], action_dispatch=DebugMessage::MessageContents),
|
||||||
// AnimationMessage
|
// AnimationMessage
|
||||||
entry!(KeyDown(Space); modifiers=[Shift], action_dispatch=AnimationMessage::ToggleLivePreview),
|
entry!(KeyDown(Space); modifiers=[Shift], action_dispatch=AnimationMessage::ToggleLivePreview),
|
||||||
entry!(KeyDown(ArrowLeft); modifiers=[Control], action_dispatch=AnimationMessage::ResetAnimation),
|
entry!(KeyDown(Home); modifiers=[Shift], action_dispatch=AnimationMessage::RestartAnimation),
|
||||||
];
|
];
|
||||||
let (mut key_up, mut key_down, mut key_up_no_repeat, mut key_down_no_repeat, mut double_click, mut wheel_scroll, mut pointer_move) = mappings;
|
let (mut key_up, mut key_down, mut key_up_no_repeat, mut key_down_no_repeat, mut double_click, mut wheel_scroll, mut pointer_move) = mappings;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,7 @@ use graphene_core::raster::image::ImageFrameTable;
|
||||||
use graphene_core::vector::style::ViewMode;
|
use graphene_core::vector::style::ViewMode;
|
||||||
use graphene_std::renderer::{ClickTarget, Quad};
|
use graphene_std::renderer::{ClickTarget, Quad};
|
||||||
use graphene_std::vector::{PointId, path_bool_lib};
|
use graphene_std::vector::{PointId, path_bool_lib};
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
pub struct DocumentMessageData<'a> {
|
pub struct DocumentMessageData<'a> {
|
||||||
pub document_id: DocumentId,
|
pub document_id: DocumentId,
|
||||||
|
|
@ -1988,7 +1989,7 @@ impl DocumentMessageHandler {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update_document_widgets(&self, responses: &mut VecDeque<Message>) {
|
pub fn update_document_widgets(&self, responses: &mut VecDeque<Message>, animation_is_playing: bool, time: Duration) {
|
||||||
// Document mode (dropdown menu at the left of the bar above the viewport, before the tool options)
|
// Document mode (dropdown menu at the left of the bar above the viewport, before the tool options)
|
||||||
|
|
||||||
let document_mode_layout = WidgetLayout::new(vec![LayoutGroup::Row {
|
let document_mode_layout = WidgetLayout::new(vec![LayoutGroup::Row {
|
||||||
|
|
@ -2026,6 +2027,18 @@ impl DocumentMessageHandler {
|
||||||
let mut snapping_state2 = self.snapping_state.clone();
|
let mut snapping_state2 = self.snapping_state.clone();
|
||||||
|
|
||||||
let mut widgets = vec![
|
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)
|
||||||
|
.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())
|
||||||
|
.widget_holder(),
|
||||||
|
Separator::new(SeparatorType::Unrelated).widget_holder(),
|
||||||
CheckboxInput::new(self.overlays_visible)
|
CheckboxInput::new(self.overlays_visible)
|
||||||
.icon("Overlays")
|
.icon("Overlays")
|
||||||
.tooltip("Overlays")
|
.tooltip("Overlays")
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,7 @@ pub struct PortfolioMessageData<'a> {
|
||||||
pub current_tool: &'a ToolType,
|
pub current_tool: &'a ToolType,
|
||||||
pub message_logging_verbosity: MessageLoggingVerbosity,
|
pub message_logging_verbosity: MessageLoggingVerbosity,
|
||||||
pub timing_information: TimingInformation,
|
pub timing_information: TimingInformation,
|
||||||
|
pub animation: &'a AnimationMessageHandler,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
|
|
@ -59,6 +60,7 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageData<'_>> for PortfolioMes
|
||||||
current_tool,
|
current_tool,
|
||||||
message_logging_verbosity,
|
message_logging_verbosity,
|
||||||
timing_information,
|
timing_information,
|
||||||
|
animation,
|
||||||
} = data;
|
} = data;
|
||||||
|
|
||||||
match message {
|
match message {
|
||||||
|
|
@ -1108,7 +1110,7 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageData<'_>> for PortfolioMes
|
||||||
}
|
}
|
||||||
PortfolioMessage::UpdateDocumentWidgets => {
|
PortfolioMessage::UpdateDocumentWidgets => {
|
||||||
if let Some(document) = self.active_document() {
|
if let Some(document) = self.active_document() {
|
||||||
document.update_document_widgets(responses);
|
document.update_document_widgets(responses, animation.is_playing(), timing_information.animation_time);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
PortfolioMessage::UpdateOpenDocumentsList => {
|
PortfolioMessage::UpdateOpenDocumentsList => {
|
||||||
|
|
@ -1275,16 +1277,19 @@ impl PortfolioMessageHandler {
|
||||||
|
|
||||||
/// Get the id of the node that should be used as the target for the spreadsheet
|
/// Get the id of the node that should be used as the target for the spreadsheet
|
||||||
pub fn inspect_node_id(&self) -> Option<NodeId> {
|
pub fn inspect_node_id(&self) -> Option<NodeId> {
|
||||||
|
// Spreadsheet not open, skipping
|
||||||
if !self.spreadsheet.spreadsheet_view_open {
|
if !self.spreadsheet.spreadsheet_view_open {
|
||||||
warn!("Spreadsheet not open, skipping…");
|
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
let document = self.documents.get(&self.active_document_id?)?;
|
let document = self.documents.get(&self.active_document_id?)?;
|
||||||
let selected_nodes = document.network_interface.selected_nodes().0;
|
let selected_nodes = document.network_interface.selected_nodes().0;
|
||||||
|
|
||||||
|
// Selected nodes != 1, skipping
|
||||||
if selected_nodes.len() != 1 {
|
if selected_nodes.len() != 1 {
|
||||||
warn!("selected nodes != 1, skipping…");
|
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
selected_nodes.first().copied()
|
selected_nodes.first().copied()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
|
||||||
|
<rect x="2" width="3" height="16" />
|
||||||
|
<rect x="11" width="3" height="16" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 145 B |
|
|
@ -0,0 +1,3 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
|
||||||
|
<polygon points="1,0 1,16 15,8" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 104 B |
|
|
@ -0,0 +1,4 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
|
||||||
|
<polygon points="3,3 3,13 11,8" />
|
||||||
|
<rect x="11" y="3" width="2" height="10" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 149 B |
|
|
@ -0,0 +1,4 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
|
||||||
|
<polygon points="13,3 13,13 5,8" />
|
||||||
|
<rect x="3" y="3" width="2" height="10" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 149 B |
|
|
@ -163,6 +163,10 @@ import PadlockUnlocked from "@graphite-frontend/assets/icon-16px-solid/padlock-u
|
||||||
import Paste from "@graphite-frontend/assets/icon-16px-solid/paste.svg";
|
import Paste from "@graphite-frontend/assets/icon-16px-solid/paste.svg";
|
||||||
import PinActive from "@graphite-frontend/assets/icon-16px-solid/pin-active.svg";
|
import PinActive from "@graphite-frontend/assets/icon-16px-solid/pin-active.svg";
|
||||||
import PinInactive from "@graphite-frontend/assets/icon-16px-solid/pin-inactive.svg";
|
import PinInactive from "@graphite-frontend/assets/icon-16px-solid/pin-inactive.svg";
|
||||||
|
import PlaybackPause from "@graphite-frontend/assets/icon-16px-solid/playback-pause.svg";
|
||||||
|
import PlaybackPlay from "@graphite-frontend/assets/icon-16px-solid/playback-play.svg";
|
||||||
|
import PlaybackToEnd from "@graphite-frontend/assets/icon-16px-solid/playback-to-end.svg";
|
||||||
|
import PlaybackToStart from "@graphite-frontend/assets/icon-16px-solid/playback-to-start.svg";
|
||||||
import Random from "@graphite-frontend/assets/icon-16px-solid/random.svg";
|
import Random from "@graphite-frontend/assets/icon-16px-solid/random.svg";
|
||||||
import Reload from "@graphite-frontend/assets/icon-16px-solid/reload.svg";
|
import Reload from "@graphite-frontend/assets/icon-16px-solid/reload.svg";
|
||||||
import Reset from "@graphite-frontend/assets/icon-16px-solid/reset.svg";
|
import Reset from "@graphite-frontend/assets/icon-16px-solid/reset.svg";
|
||||||
|
|
@ -279,6 +283,10 @@ const SOLID_16PX = {
|
||||||
Paste: { svg: Paste, size: 16 },
|
Paste: { svg: Paste, size: 16 },
|
||||||
PinActive: { svg: PinActive, size: 16 },
|
PinActive: { svg: PinActive, size: 16 },
|
||||||
PinInactive: { svg: PinInactive, size: 16 },
|
PinInactive: { svg: PinInactive, size: 16 },
|
||||||
|
PlaybackPause: { svg: PlaybackPause, size: 16 },
|
||||||
|
PlaybackPlay: { svg: PlaybackPlay, size: 16 },
|
||||||
|
PlaybackToEnd: { svg: PlaybackToEnd, size: 16 },
|
||||||
|
PlaybackToStart: { svg: PlaybackToStart, size: 16 },
|
||||||
Random: { svg: Random, size: 16 },
|
Random: { svg: Random, size: 16 },
|
||||||
Reload: { svg: Reload, size: 16 },
|
Reload: { svg: Reload, size: 16 },
|
||||||
Reset: { svg: Reset, size: 16 },
|
Reset: { svg: Reset, size: 16 },
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue