Skip to content

Commit be0c531

Browse files
author
indierusty
committed
Merge branch 'master' into merge-spline-path
2 parents 533f0cc + 93f7004 commit be0c531

File tree

118 files changed

+4712
-1038
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

118 files changed

+4712
-1038
lines changed

.github/workflows/build-dev-and-ci.yml

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -108,17 +108,6 @@ jobs:
108108
- name: 📥 Clone and checkout repository
109109
uses: actions/checkout@v3
110110

111-
- name: 🔒 Check crate security advisories for root workspace
112-
uses: EmbarkStudios/cargo-deny-action@v2
113-
with:
114-
command: check advisories
115-
116-
- name: 🔒 Check crate security advisories for /libraries/rawkit
117-
uses: EmbarkStudios/cargo-deny-action@v2
118-
with:
119-
command: check advisories
120-
manifest-path: libraries/rawkit/Cargo.toml
121-
122111
- name: 📜 Check crate license compatibility for root workspace
123112
uses: EmbarkStudios/cargo-deny-action@v2
124113
with:

.github/workflows/cargo-deny.yml

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
name: "Audit Security Advisories"
2+
3+
on:
4+
# Run once each day
5+
schedule:
6+
- cron: "0 0 * * *"
7+
8+
jobs:
9+
cargo-deny:
10+
runs-on: ubuntu-latest
11+
12+
steps:
13+
- name: 📥 Clone and checkout repository
14+
uses: actions/checkout@v3
15+
16+
- name: 🔒 Check crate security advisories for root workspace
17+
uses: EmbarkStudios/cargo-deny-action@v2
18+
with:
19+
command: check advisories
20+
21+
- name: 🔒 Check crate security advisories for /libraries/rawkit
22+
uses: EmbarkStudios/cargo-deny-action@v2
23+
with:
24+
command: check advisories
25+
manifest-path: libraries/rawkit/Cargo.toml

Cargo.lock

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

demo-artwork/parametric-dunescape.graphite

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

editor/src/consts.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ pub const VIEWPORT_ZOOM_LEVELS: [f64; 74] = [
1616
0.04, 0.05, 0.06, 0.08, 0.1, 0.125, 0.15, 0.2, 0.25, 0.33333333, 0.4, 0.5, 0.66666666, 0.8, 1., 1.25, 1.6, 2., 2.5, 3.2, 4., 5., 6.4, 8., 10., 12.5, 16., 20., 25., 32., 40., 50., 64., 80., 100.,
1717
128., 160., 200., 256., 320., 400., 512., 640., 800., 1024., 1280., 1600., 2048., 2560.,
1818
];
19+
/// Higher values create a steeper curve (a faster zoom rate change)
20+
pub const VIEWPORT_ZOOM_WHEEL_RATE_CHANGE: f64 = 3.;
1921

2022
/// Helps push values that end in approximately half, plus or minus some floating point imprecision, towards the same side of the round() function.
2123
pub const VIEWPORT_GRID_ROUNDING_BIAS: f64 = 0.002;

editor/src/dispatcher.rs

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ pub struct Dispatcher {
1313

1414
#[derive(Debug, Default)]
1515
pub struct DispatcherMessageHandlers {
16+
animation_message_handler: AnimationMessageHandler,
1617
broadcast_message_handler: BroadcastMessageHandler,
1718
debug_message_handler: DebugMessageHandler,
1819
dialog_message_handler: DialogMessageHandler,
@@ -50,12 +51,9 @@ const SIDE_EFFECT_FREE_MESSAGES: &[MessageDiscriminant] = &[
5051
MessageDiscriminant::Frontend(FrontendMessageDiscriminant::UpdateDocumentLayerStructure),
5152
MessageDiscriminant::Frontend(FrontendMessageDiscriminant::TriggerFontLoad),
5253
];
53-
const DEBUG_MESSAGE_BLOCK_LIST: &[MessageDiscriminant] = &[
54-
MessageDiscriminant::Broadcast(BroadcastMessageDiscriminant::TriggerEvent(BroadcastEventDiscriminant::AnimationFrame)),
55-
MessageDiscriminant::InputPreprocessor(InputPreprocessorMessageDiscriminant::FrameTimeAdvance),
56-
];
54+
const DEBUG_MESSAGE_BLOCK_LIST: &[MessageDiscriminant] = &[MessageDiscriminant::Broadcast(BroadcastMessageDiscriminant::TriggerEvent(BroadcastEventDiscriminant::AnimationFrame))];
5755
// 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.
58-
const DEBUG_MESSAGE_ENDING_BLOCK_LIST: &[&str] = &["PointerMove", "PointerOutsideViewport", "Overlays", "Draw"];
56+
const DEBUG_MESSAGE_ENDING_BLOCK_LIST: &[&str] = &["PointerMove", "PointerOutsideViewport", "Overlays", "Draw", "CurrentTime", "Time"];
5957

6058
impl Dispatcher {
6159
pub fn new() -> Self {
@@ -177,6 +175,9 @@ impl Dispatcher {
177175
// Finish loading persistent data from the browser database
178176
queue.add(FrontendMessage::TriggerLoadRestAutoSaveDocuments);
179177
}
178+
Message::Animation(message) => {
179+
self.message_handlers.animation_message_handler.process_message(message, &mut queue, ());
180+
}
180181
Message::Batched(messages) => {
181182
messages.iter().for_each(|message| self.handle_message(message.to_owned(), false));
182183
}
@@ -232,6 +233,8 @@ impl Dispatcher {
232233
let preferences = &self.message_handlers.preferences_message_handler;
233234
let current_tool = &self.message_handlers.tool_message_handler.tool_state.tool_data.active_tool_type;
234235
let message_logging_verbosity = self.message_handlers.debug_message_handler.message_logging_verbosity;
236+
let timing_information = self.message_handlers.animation_message_handler.timing_information();
237+
let animation = &self.message_handlers.animation_message_handler;
235238

236239
self.message_handlers.portfolio_message_handler.process_message(
237240
message,
@@ -241,6 +244,8 @@ impl Dispatcher {
241244
preferences,
242245
current_tool,
243246
message_logging_verbosity,
247+
timing_information,
248+
animation,
244249
},
245250
);
246251
}
@@ -283,6 +288,7 @@ impl Dispatcher {
283288
// TODO: Reduce the number of heap allocations
284289
let mut list = Vec::new();
285290
list.extend(self.message_handlers.dialog_message_handler.actions());
291+
list.extend(self.message_handlers.animation_message_handler.actions());
286292
list.extend(self.message_handlers.input_preprocessor_message_handler.actions());
287293
list.extend(self.message_handlers.key_mapping_message_handler.actions());
288294
list.extend(self.message_handlers.debug_message_handler.actions());
@@ -509,6 +515,7 @@ mod test {
509515
// include_str!("../../demo-artwork/isometric-fountain.graphite"),
510516
// include_str!("../../demo-artwork/painted-dreams.graphite"),
511517
// include_str!("../../demo-artwork/procedural-string-lights.graphite"),
518+
// include_str!("../../demo-artwork/parametric-dunescape.graphite"),
512519
// include_str!("../../demo-artwork/red-dress.graphite"),
513520
// include_str!("../../demo-artwork/valley-of-spires.graphite"),
514521
// ];
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
use crate::messages::prelude::*;
2+
3+
use super::animation_message_handler::AnimationTimeMode;
4+
5+
#[impl_message(Message, Animation)]
6+
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]
7+
pub enum AnimationMessage {
8+
ToggleLivePreview,
9+
EnableLivePreview,
10+
DisableLivePreview,
11+
RestartAnimation,
12+
SetFrameIndex(f64),
13+
SetTime(f64),
14+
UpdateTime,
15+
IncrementFrameCounter,
16+
SetAnimationTimeMode(AnimationTimeMode),
17+
}
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
use std::time::Duration;
2+
3+
use crate::messages::prelude::*;
4+
5+
use super::TimingInformation;
6+
7+
#[derive(PartialEq, Clone, Default, Debug, serde::Serialize, serde::Deserialize)]
8+
pub enum AnimationTimeMode {
9+
#[default]
10+
TimeBased,
11+
FrameBased,
12+
}
13+
14+
#[derive(Default, Debug, Clone, PartialEq)]
15+
enum AnimationState {
16+
#[default]
17+
Stopped,
18+
Playing {
19+
start: f64,
20+
},
21+
Paused {
22+
start: f64,
23+
pause_time: f64,
24+
},
25+
}
26+
27+
#[derive(Default, Debug, Clone, PartialEq)]
28+
pub struct AnimationMessageHandler {
29+
/// Used to re-send the UI on the next frame after playback starts
30+
live_preview_recently_zero: bool,
31+
timestamp: f64,
32+
frame_index: f64,
33+
animation_state: AnimationState,
34+
fps: f64,
35+
animation_time_mode: AnimationTimeMode,
36+
}
37+
impl AnimationMessageHandler {
38+
pub(crate) fn timing_information(&self) -> TimingInformation {
39+
let animation_time = self.timestamp - self.animation_start();
40+
let animation_time = match self.animation_time_mode {
41+
AnimationTimeMode::TimeBased => Duration::from_millis(animation_time as u64),
42+
AnimationTimeMode::FrameBased => Duration::from_secs((self.frame_index / self.fps) as u64),
43+
};
44+
TimingInformation { time: self.timestamp, animation_time }
45+
}
46+
47+
pub(crate) fn animation_start(&self) -> f64 {
48+
match self.animation_state {
49+
AnimationState::Stopped => self.timestamp,
50+
AnimationState::Playing { start } => start,
51+
AnimationState::Paused { start, pause_time } => start + self.timestamp - pause_time,
52+
}
53+
}
54+
55+
pub fn is_playing(&self) -> bool {
56+
matches!(self.animation_state, AnimationState::Playing { .. })
57+
}
58+
}
59+
60+
impl MessageHandler<AnimationMessage, ()> for AnimationMessageHandler {
61+
fn process_message(&mut self, message: AnimationMessage, responses: &mut VecDeque<Message>, _data: ()) {
62+
match message {
63+
AnimationMessage::ToggleLivePreview => match self.animation_state {
64+
AnimationState::Stopped => responses.add(AnimationMessage::EnableLivePreview),
65+
AnimationState::Playing { .. } => responses.add(AnimationMessage::DisableLivePreview),
66+
AnimationState::Paused { .. } => responses.add(AnimationMessage::EnableLivePreview),
67+
},
68+
AnimationMessage::EnableLivePreview => {
69+
self.animation_state = AnimationState::Playing { start: self.animation_start() };
70+
71+
// Update the restart and pause/play buttons
72+
responses.add(PortfolioMessage::UpdateDocumentWidgets);
73+
}
74+
AnimationMessage::DisableLivePreview => {
75+
match self.animation_state {
76+
AnimationState::Stopped => (),
77+
AnimationState::Playing { start } => self.animation_state = AnimationState::Paused { start, pause_time: self.timestamp },
78+
AnimationState::Paused { .. } => (),
79+
}
80+
81+
// Update the restart and pause/play buttons
82+
responses.add(PortfolioMessage::UpdateDocumentWidgets);
83+
}
84+
AnimationMessage::SetFrameIndex(frame) => {
85+
self.frame_index = frame;
86+
responses.add(PortfolioMessage::SubmitActiveGraphRender);
87+
// Update the restart and pause/play buttons
88+
responses.add(PortfolioMessage::UpdateDocumentWidgets);
89+
}
90+
AnimationMessage::SetTime(time) => {
91+
self.timestamp = time;
92+
responses.add(AnimationMessage::UpdateTime);
93+
}
94+
AnimationMessage::IncrementFrameCounter => {
95+
if self.is_playing() {
96+
self.frame_index += 1.;
97+
responses.add(AnimationMessage::UpdateTime);
98+
}
99+
}
100+
AnimationMessage::UpdateTime => {
101+
if self.is_playing() {
102+
responses.add(PortfolioMessage::SubmitActiveGraphRender);
103+
104+
if self.live_preview_recently_zero {
105+
// Update the restart and pause/play buttons
106+
responses.add(PortfolioMessage::UpdateDocumentWidgets);
107+
self.live_preview_recently_zero = false;
108+
}
109+
}
110+
}
111+
AnimationMessage::RestartAnimation => {
112+
self.frame_index = 0.;
113+
self.animation_state = match self.animation_state {
114+
AnimationState::Playing { .. } => AnimationState::Playing { start: self.timestamp },
115+
_ => AnimationState::Stopped,
116+
};
117+
self.live_preview_recently_zero = true;
118+
responses.add(PortfolioMessage::SubmitActiveGraphRender);
119+
// Update the restart and pause/play buttons
120+
responses.add(PortfolioMessage::UpdateDocumentWidgets);
121+
}
122+
AnimationMessage::SetAnimationTimeMode(animation_time_mode) => {
123+
self.animation_time_mode = animation_time_mode;
124+
}
125+
}
126+
}
127+
128+
advertise_actions!(AnimationMessageDiscriminant;
129+
ToggleLivePreview,
130+
SetFrameIndex,
131+
RestartAnimation,
132+
);
133+
}

editor/src/messages/animation/mod.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
mod animation_message;
2+
mod animation_message_handler;
3+
4+
#[doc(inline)]
5+
pub use animation_message::{AnimationMessage, AnimationMessageDiscriminant};
6+
#[doc(inline)]
7+
pub use animation_message_handler::AnimationMessageHandler;
8+
9+
pub use graphene_core::application_io::TimingInformation;

editor/src/messages/dialog/preferences_dialog/preferences_dialog_message_handler.rs

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use crate::consts::{VIEWPORT_ZOOM_WHEEL_RATE, VIEWPORT_ZOOM_WHEEL_RATE_CHANGE};
12
use crate::messages::layout::utility_types::widget_prelude::*;
23
use crate::messages::portfolio::document::node_graph::utility_types::GraphWireStyle;
34
use crate::messages::preferences::SelectionMode;
@@ -39,6 +40,32 @@ impl PreferencesDialogMessageHandler {
3940

4041
let navigation_header = vec![TextLabel::new("Navigation").italic(true).widget_holder()];
4142

43+
let zoom_rate_tooltip = "Adjust how fast zooming occurs when using the scroll wheel or pinch gesture (relative to a default of 50)";
44+
let zoom_rate_label = vec![
45+
Separator::new(SeparatorType::Unrelated).widget_holder(),
46+
Separator::new(SeparatorType::Unrelated).widget_holder(),
47+
TextLabel::new("Zoom Rate").tooltip(zoom_rate_tooltip).widget_holder(),
48+
];
49+
let zoom_rate = vec![
50+
Separator::new(SeparatorType::Unrelated).widget_holder(),
51+
Separator::new(SeparatorType::Unrelated).widget_holder(),
52+
NumberInput::new(Some(map_zoom_rate_to_display(preferences.viewport_zoom_wheel_rate)))
53+
.tooltip(zoom_rate_tooltip)
54+
.mode_range()
55+
.int()
56+
.min(1.)
57+
.max(100.)
58+
.on_update(|number_input: &NumberInput| {
59+
if let Some(display_value) = number_input.value {
60+
let actual_rate = map_display_to_zoom_rate(display_value);
61+
PreferencesMessage::ViewportZoomWheelRate { rate: actual_rate }.into()
62+
} else {
63+
PreferencesMessage::ViewportZoomWheelRate { rate: VIEWPORT_ZOOM_WHEEL_RATE }.into()
64+
}
65+
})
66+
.widget_holder(),
67+
];
68+
4269
let zoom_with_scroll_tooltip = "Use the scroll wheel for zooming instead of vertically panning (not recommended for trackpads)";
4370
let zoom_with_scroll = vec![
4471
Separator::new(SeparatorType::Unrelated).widget_holder(),
@@ -184,6 +211,8 @@ impl PreferencesDialogMessageHandler {
184211

185212
Layout::WidgetLayout(WidgetLayout::new(vec![
186213
LayoutGroup::Row { widgets: navigation_header },
214+
LayoutGroup::Row { widgets: zoom_rate_label },
215+
LayoutGroup::Row { widgets: zoom_rate },
187216
LayoutGroup::Row { widgets: zoom_with_scroll },
188217
LayoutGroup::Row { widgets: editing_header },
189218
LayoutGroup::Row { widgets: selection_label },
@@ -250,3 +279,20 @@ impl PreferencesDialogMessageHandler {
250279
});
251280
}
252281
}
282+
283+
/// Maps display values (1-100) to actual zoom rates.
284+
fn map_display_to_zoom_rate(display: f64) -> f64 {
285+
// Calculate the relative distance from the reference point (50)
286+
let distance_from_reference = display - 50.;
287+
let scaling_factor = (VIEWPORT_ZOOM_WHEEL_RATE_CHANGE * distance_from_reference / 50.).exp();
288+
VIEWPORT_ZOOM_WHEEL_RATE * scaling_factor
289+
}
290+
291+
/// Maps actual zoom rates back to display values (1-100).
292+
fn map_zoom_rate_to_display(rate: f64) -> f64 {
293+
// Calculate the scaling factor from the reference rate
294+
let scaling_factor = rate / VIEWPORT_ZOOM_WHEEL_RATE;
295+
let distance_from_reference = 50. * scaling_factor.ln() / VIEWPORT_ZOOM_WHEEL_RATE_CHANGE;
296+
let display = 50. + distance_from_reference;
297+
display.clamp(1., 100.).round()
298+
}

editor/src/messages/dialog/simple_dialogs/demo_artwork_dialog.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,11 @@ use crate::messages::prelude::*;
55
pub struct DemoArtworkDialog;
66

77
/// `(name, thumbnail, filename)`
8-
pub const ARTWORK: [(&str, &str, &str); 6] = [
8+
pub const ARTWORK: [(&str, &str, &str); 7] = [
99
("Isometric Fountain", "ThumbnailIsometricFountain", "isometric-fountain.graphite"),
1010
("Changing Seasons", "ThumbnailChangingSeasons", "changing-seasons.graphite"),
1111
("Painted Dreams", "ThumbnailPaintedDreams", "painted-dreams.graphite"),
12+
("Parametric Dunescape", "ThumbnailParametricDunescape", "parametric-dunescape.graphite"),
1213
("Red Dress", "ThumbnailRedDress", "red-dress.graphite"),
1314
("Procedural String Lights", "ThumbnailProceduralStringLights", "procedural-string-lights.graphite"),
1415
("Valley of Spires", "ThumbnailValleyOfSpires", "valley-of-spires.graphite"),
@@ -28,7 +29,7 @@ impl DialogLayoutHolder for DemoArtworkDialog {
2829
impl LayoutHolder for DemoArtworkDialog {
2930
fn layout(&self) -> Layout {
3031
let mut rows_of_images_with_buttons: Vec<_> = ARTWORK
31-
.chunks(3)
32+
.chunks(4)
3233
.flat_map(|chunk| {
3334
fn make_dialog(name: &str, filename: &str) -> Message {
3435
DialogMessage::CloseDialogAndThen {

0 commit comments

Comments
 (0)