Skip to content

Commit e8fdfd6

Browse files
0HyperCubeKeavon
andauthored
Migrate dialogs to Rust and add a New File dialog (#623)
* Migrate coming soon and about dialog to Rust * Migrate confirm close and close all * Migrate dialog error * Improve keyboard navigation throughout UI * Cleanup and fix panic dialog * Reduce css spacing to better match old dialogs * Add new document modal * Fix crash when generating default name * Populate rust about graphite data on startup * Code review changes * Move one more :focus CSS rule into App.vue * Add a dialog message and move dialogs * Split out keyboard input navigation from this branch * Improvements including simplifying panic dialog code Co-authored-by: Keavon Chambers <keavon@keavon.com>
1 parent e6eba09 commit e8fdfd6

Some content is hidden

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

44 files changed

+1001
-374
lines changed
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
use serde::{Deserialize, Serialize};
2+
3+
/// Provides metadata about the build environment.
4+
///
5+
/// This data is viewable in the editor via the [`crate::dialog::AboutGraphite`] dialog.
6+
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
7+
pub struct BuildMetadata {
8+
pub release: String,
9+
pub timestamp: String,
10+
pub hash: String,
11+
pub branch: String,
12+
}
13+
14+
impl Default for BuildMetadata {
15+
fn default() -> Self {
16+
Self {
17+
release: "unknown".to_string(),
18+
timestamp: "unknown".to_string(),
19+
hash: "unknown".to_string(),
20+
branch: "unknown".to_string(),
21+
}
22+
}
23+
}
24+
25+
impl BuildMetadata {
26+
pub fn release_series(&self) -> String {
27+
format!("Release Series: {}", self.release)
28+
}
29+
30+
pub fn commit_info(&self) -> String {
31+
format!("{}\n{}\n{}", self.commit_timestamp(), self.commit_hash(), self.commit_branch())
32+
}
33+
34+
pub fn commit_timestamp(&self) -> String {
35+
format!("Date: {}", self.timestamp)
36+
}
37+
38+
pub fn commit_hash(&self) -> String {
39+
format!("Hash: {}", self.hash)
40+
}
41+
42+
pub fn commit_branch(&self) -> String {
43+
format!("Branch: {}", self.branch)
44+
}
45+
}

editor/src/communication/dispatcher.rs

Lines changed: 32 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,20 @@ use crate::viewport_tools::tool_message_handler::ToolMessageHandler;
77

88
use std::collections::VecDeque;
99

10+
use super::BuildMetadata;
11+
1012
#[derive(Debug, Default)]
1113
pub struct Dispatcher {
1214
message_queue: VecDeque<Message>,
1315
pub responses: Vec<FrontendMessage>,
1416
message_handlers: DispatcherMessageHandlers,
17+
build_metadata: BuildMetadata,
1518
}
1619

1720
#[remain::sorted]
1821
#[derive(Debug, Default)]
1922
struct DispatcherMessageHandlers {
23+
dialog_message_handler: DialogMessageHandler,
2024
global_message_handler: GlobalMessageHandler,
2125
input_mapper_message_handler: InputMapperMessageHandler,
2226
input_preprocessor_message_handler: InputPreprocessorMessageHandler,
@@ -31,6 +35,9 @@ struct DispatcherMessageHandlers {
3135
const SIDE_EFFECT_FREE_MESSAGES: &[MessageDiscriminant] = &[
3236
MessageDiscriminant::Portfolio(PortfolioMessageDiscriminant::Document(DocumentMessageDiscriminant::RenderDocument)),
3337
MessageDiscriminant::Portfolio(PortfolioMessageDiscriminant::Document(DocumentMessageDiscriminant::Overlays(OverlaysMessageDiscriminant::Rerender))),
38+
MessageDiscriminant::Portfolio(PortfolioMessageDiscriminant::Document(DocumentMessageDiscriminant::Artboard(
39+
ArtboardMessageDiscriminant::RenderArtboards,
40+
))),
3441
MessageDiscriminant::Portfolio(PortfolioMessageDiscriminant::Document(DocumentMessageDiscriminant::FolderChanged)),
3542
MessageDiscriminant::Frontend(FrontendMessageDiscriminant::UpdateDocumentLayer),
3643
MessageDiscriminant::Frontend(FrontendMessageDiscriminant::DisplayDocumentLayerTreeStructure),
@@ -63,6 +70,11 @@ impl Dispatcher {
6370
match message {
6471
#[remain::unsorted]
6572
NoOp => {}
73+
Dialog(message) => {
74+
self.message_handlers
75+
.dialog_message_handler
76+
.process_action(message, (&self.build_metadata, &self.message_handlers.portfolio_message_handler), &mut self.message_queue);
77+
}
6678
Frontend(message) => {
6779
// Image and font loading should be immediately handled
6880
if let FrontendMessage::UpdateImageData { .. } | FrontendMessage::TriggerFontLoad { .. } = message {
@@ -101,13 +113,17 @@ impl Dispatcher {
101113
&mut self.message_queue,
102114
);
103115
}
116+
117+
#[remain::unsorted]
118+
PopulateBuildMetadata { new } => self.build_metadata = new,
104119
}
105120
}
106121
}
107122

108123
pub fn collect_actions(&self) -> ActionList {
109124
// TODO: Reduce the number of heap allocations
110125
let mut list = Vec::new();
126+
list.extend(self.message_handlers.dialog_message_handler.actions());
111127
list.extend(self.message_handlers.input_preprocessor_message_handler.actions());
112128
list.extend(self.message_handlers.input_mapper_message_handler.actions());
113129
list.extend(self.message_handlers.global_message_handler.actions());
@@ -434,18 +450,22 @@ mod test {
434450
});
435451

436452
for response in responses {
437-
if let FrontendMessage::DisplayDialogError { title, description } = response {
438-
println!();
439-
println!("-------------------------------------------------");
440-
println!("Failed test due to receiving a DisplayDialogError while loading the graphite sample file!");
441-
println!("This is most likely caused by forgetting to bump the `GRAPHITE_DOCUMENT_VERSION` in `editor/src/consts.rs`");
442-
println!("Once bumping this version number please replace the `graphite-test-document.graphite` with a valid file");
443-
println!("DisplayDialogError details:");
444-
println!("Title: {}", title);
445-
println!("description: {}", description);
446-
println!("-------------------------------------------------");
447-
println!();
448-
panic!()
453+
if let FrontendMessage::UpdateDialogDetails { layout_target: _, layout } = response {
454+
if let crate::layout::widgets::LayoutRow::Row { widgets } = &layout[0] {
455+
if let crate::layout::widgets::Widget::TextLabel(crate::layout::widgets::TextLabel { value, .. }) = &widgets[0].widget {
456+
println!();
457+
println!("-------------------------------------------------");
458+
println!("Failed test due to receiving a DisplayDialogError while loading the Graphite sample file!");
459+
println!("This is most likely caused by forgetting to bump the `GRAPHITE_DOCUMENT_VERSION` in `editor/src/consts.rs`");
460+
println!("Once bumping this version number please replace the `graphite-test-document.graphite` with a valid file.");
461+
println!("DisplayDialogError details:");
462+
println!();
463+
println!("Description: {}", value);
464+
println!("-------------------------------------------------");
465+
println!();
466+
panic!()
467+
}
468+
}
449469
}
450470
}
451471
}

editor/src/communication/message.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use super::BuildMetadata;
12
use crate::message_prelude::*;
23

34
use graphite_proc_macros::*;
@@ -23,6 +24,8 @@ pub enum Message {
2324
#[remain::unsorted]
2425
NoOp,
2526
#[child]
27+
Dialog(DialogMessage),
28+
#[child]
2629
Frontend(FrontendMessage),
2730
#[child]
2831
Global(GlobalMessage),
@@ -36,6 +39,9 @@ pub enum Message {
3639
Portfolio(PortfolioMessage),
3740
#[child]
3841
Tool(ToolMessage),
42+
43+
#[remain::unsorted]
44+
PopulateBuildMetadata { new: BuildMetadata },
3945
}
4046

4147
impl Message {

editor/src/communication/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
1+
mod build_metadata;
12
pub mod dispatcher;
23
pub mod message;
34
pub mod message_handler;
45

56
pub use crate::communication::dispatcher::*;
67
pub use crate::input::InputPreprocessorMessageHandler;
8+
pub use build_metadata::BuildMetadata;
79

810
use rand_chacha::rand_core::{RngCore, SeedableRng};
911
use rand_chacha::ChaCha20Rng;
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
use crate::message_prelude::*;
2+
use serde::{Deserialize, Serialize};
3+
4+
use super::NewDocumentDialogUpdate;
5+
6+
#[remain::sorted]
7+
#[impl_message(Message, Dialog)]
8+
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)]
9+
pub enum DialogMessage {
10+
#[remain::unsorted]
11+
#[child]
12+
NewDocumentDialog(NewDocumentDialogUpdate),
13+
14+
CloseAllDocumentsWithConfirmation,
15+
CloseDialogAndThen {
16+
followup: Box<Message>,
17+
},
18+
DisplayDialogError {
19+
title: String,
20+
description: String,
21+
},
22+
RequestAboutGraphiteDialog,
23+
RequestComingSoonDialog {
24+
issue: Option<i32>,
25+
},
26+
RequestNewDocumentDialog,
27+
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
use crate::communication::BuildMetadata;
2+
use crate::document::PortfolioMessageHandler;
3+
use crate::layout::{layout_message::LayoutTarget, widgets::PropertyHolder};
4+
use crate::message_prelude::*;
5+
6+
use super::*;
7+
8+
#[derive(Debug, Default, Clone)]
9+
pub struct DialogMessageHandler {
10+
new_document_dialog: NewDocument,
11+
}
12+
13+
impl MessageHandler<DialogMessage, (&BuildMetadata, &PortfolioMessageHandler)> for DialogMessageHandler {
14+
#[remain::check]
15+
fn process_action(&mut self, message: DialogMessage, (build_metadata, portfolio): (&BuildMetadata, &PortfolioMessageHandler), responses: &mut VecDeque<Message>) {
16+
#[remain::sorted]
17+
match message {
18+
#[remain::unsorted]
19+
DialogMessage::NewDocumentDialog(message) => self.new_document_dialog.process_action(message, (), responses),
20+
21+
DialogMessage::CloseAllDocumentsWithConfirmation => {
22+
let dialog = dialogs::CloseAllDocuments;
23+
dialog.register_properties(responses, LayoutTarget::DialogDetails);
24+
responses.push_back(FrontendMessage::DisplayDialog { icon: "Copy".to_string() }.into());
25+
}
26+
DialogMessage::CloseDialogAndThen { followup } => {
27+
responses.push_back(FrontendMessage::DisplayDialogDismiss.into());
28+
responses.push_back(*followup);
29+
}
30+
DialogMessage::DisplayDialogError { title, description } => {
31+
let dialog = dialogs::Error { title, description };
32+
dialog.register_properties(responses, LayoutTarget::DialogDetails);
33+
responses.push_back(FrontendMessage::DisplayDialog { icon: "Warning".to_string() }.into());
34+
}
35+
DialogMessage::RequestAboutGraphiteDialog => {
36+
let about_graphite = AboutGraphite {
37+
build_metadata: build_metadata.clone(),
38+
};
39+
about_graphite.register_properties(responses, LayoutTarget::DialogDetails);
40+
responses.push_back(FrontendMessage::DisplayDialog { icon: "GraphiteLogo".to_string() }.into());
41+
}
42+
DialogMessage::RequestComingSoonDialog { issue } => {
43+
let coming_soon = ComingSoon { issue };
44+
coming_soon.register_properties(responses, LayoutTarget::DialogDetails);
45+
responses.push_back(FrontendMessage::DisplayDialog { icon: "Warning".to_string() }.into());
46+
}
47+
DialogMessage::RequestNewDocumentDialog => {
48+
self.new_document_dialog = NewDocument {
49+
name: portfolio.generate_new_document_name(),
50+
infinite: true,
51+
dimensions: glam::UVec2::new(1920, 1080),
52+
};
53+
self.new_document_dialog.register_properties(responses, LayoutTarget::DialogDetails);
54+
responses.push_back(FrontendMessage::DisplayDialog { icon: "File".to_string() }.into());
55+
}
56+
}
57+
}
58+
59+
advertise_actions!(DialogMessageDiscriminant;RequestNewDocumentDialog,CloseAllDocumentsWithConfirmation);
60+
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
use crate::{communication::BuildMetadata, layout::widgets::*, message_prelude::FrontendMessage};
2+
3+
/// A dialog for displaying information on [`BuildMetadata`] viewable via `help -> about graphite` in the menu bar.
4+
pub struct AboutGraphite {
5+
pub build_metadata: BuildMetadata,
6+
}
7+
8+
impl PropertyHolder for AboutGraphite {
9+
fn properties(&self) -> WidgetLayout {
10+
let links = [
11+
("Website", "https://graphite.rs"),
12+
("Credits", "https://github.com/GraphiteEditor/Graphite/graphs/contributors"),
13+
("License", "https://raw.githubusercontent.com/GraphiteEditor/Graphite/master/LICENSE.txt"),
14+
("Third-Party Licenses", "/third-party-licenses.txt"),
15+
];
16+
let link_widgets = links
17+
.into_iter()
18+
.map(|(label, url)| {
19+
WidgetHolder::new(Widget::TextButton(TextButton {
20+
label: label.to_string(),
21+
on_update: WidgetCallback::new(|_| FrontendMessage::TriggerVisitLink { url: url.to_string() }.into()),
22+
..Default::default()
23+
}))
24+
})
25+
.collect();
26+
WidgetLayout::new(vec![
27+
LayoutRow::Row {
28+
widgets: vec![WidgetHolder::new(Widget::TextLabel(TextLabel {
29+
value: "Graphite".to_string(),
30+
bold: true,
31+
..Default::default()
32+
}))],
33+
},
34+
LayoutRow::Row {
35+
widgets: vec![WidgetHolder::new(Widget::TextLabel(TextLabel {
36+
value: self.build_metadata.release_series(),
37+
..Default::default()
38+
}))],
39+
},
40+
LayoutRow::Row {
41+
widgets: vec![WidgetHolder::new(Widget::TextLabel(TextLabel {
42+
value: self.build_metadata.commit_info(),
43+
multiline: true,
44+
..Default::default()
45+
}))],
46+
},
47+
LayoutRow::Row { widgets: link_widgets },
48+
])
49+
}
50+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
use crate::layout::widgets::*;
2+
use crate::message_prelude::{DialogMessage, FrontendMessage, PortfolioMessage};
3+
4+
/// A dialog for confirming the closing of all documents viewable via `file -> close all` in the menu bar.
5+
pub struct CloseAllDocuments;
6+
7+
impl PropertyHolder for CloseAllDocuments {
8+
fn properties(&self) -> WidgetLayout {
9+
let button_widgets = vec![
10+
WidgetHolder::new(Widget::TextButton(TextButton {
11+
label: "Discard All".to_string(),
12+
min_width: 96,
13+
on_update: WidgetCallback::new(|_| {
14+
DialogMessage::CloseDialogAndThen {
15+
followup: Box::new(PortfolioMessage::CloseAllDocuments.into()),
16+
}
17+
.into()
18+
}),
19+
..Default::default()
20+
})),
21+
WidgetHolder::new(Widget::TextButton(TextButton {
22+
label: "Cancel".to_string(),
23+
min_width: 96,
24+
on_update: WidgetCallback::new(|_| FrontendMessage::DisplayDialogDismiss.into()),
25+
..Default::default()
26+
})),
27+
];
28+
29+
WidgetLayout::new(vec![
30+
LayoutRow::Row {
31+
widgets: vec![WidgetHolder::new(Widget::TextLabel(TextLabel {
32+
value: "Close all documents?".to_string(),
33+
bold: true,
34+
..Default::default()
35+
}))],
36+
},
37+
LayoutRow::Row {
38+
widgets: vec![WidgetHolder::new(Widget::TextLabel(TextLabel {
39+
value: "Unsaved work will be lost!".to_string(),
40+
multiline: true,
41+
..Default::default()
42+
}))],
43+
},
44+
LayoutRow::Row { widgets: button_widgets },
45+
])
46+
}
47+
}

0 commit comments

Comments
 (0)