mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-27 13:28:11 +08:00
Restructure the entire editor codebase to consistently match the message hierarchy
Closes #744
This commit is contained in:
@@ -0,0 +1,37 @@
|
||||
use super::export_dialog::ExportDialogMessage;
|
||||
use super::new_document_dialog::NewDocumentDialogMessage;
|
||||
use crate::messages::prelude::*;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[remain::sorted]
|
||||
#[impl_message(Message, Dialog)]
|
||||
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)]
|
||||
pub enum DialogMessage {
|
||||
// Sub-messages
|
||||
#[remain::unsorted]
|
||||
#[child]
|
||||
ExportDialog(ExportDialogMessage),
|
||||
#[remain::unsorted]
|
||||
#[child]
|
||||
NewDocumentDialog(NewDocumentDialogMessage),
|
||||
|
||||
// Messages
|
||||
CloseAllDocumentsWithConfirmation,
|
||||
CloseDialogAndThen {
|
||||
followups: Vec<Message>,
|
||||
},
|
||||
DisplayDialogError {
|
||||
title: String,
|
||||
description: String,
|
||||
},
|
||||
RequestAboutGraphiteDialog,
|
||||
RequestAboutGraphiteDialogWithLocalizedCommitDate {
|
||||
localized_commit_date: String,
|
||||
},
|
||||
RequestComingSoonDialog {
|
||||
issue: Option<i32>,
|
||||
},
|
||||
RequestExportDialog,
|
||||
RequestNewDocumentDialog,
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
use super::simple_dialogs::{self, AboutGraphiteDialog, ComingSoonDialog};
|
||||
use crate::messages::layout::utility_types::layout_widget::PropertyHolder;
|
||||
use crate::messages::layout::utility_types::misc::LayoutTarget;
|
||||
use crate::messages::prelude::*;
|
||||
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct DialogMessageHandler {
|
||||
export_dialog: ExportDialogMessageHandler,
|
||||
new_document_dialog: NewDocumentDialogMessageHandler,
|
||||
}
|
||||
|
||||
impl MessageHandler<DialogMessage, &PortfolioMessageHandler> for DialogMessageHandler {
|
||||
#[remain::check]
|
||||
fn process_message(&mut self, message: DialogMessage, portfolio: &PortfolioMessageHandler, responses: &mut VecDeque<Message>) {
|
||||
#[remain::sorted]
|
||||
match message {
|
||||
#[remain::unsorted]
|
||||
DialogMessage::ExportDialog(message) => self.export_dialog.process_message(message, (), responses),
|
||||
#[remain::unsorted]
|
||||
DialogMessage::NewDocumentDialog(message) => self.new_document_dialog.process_message(message, (), responses),
|
||||
|
||||
DialogMessage::CloseAllDocumentsWithConfirmation => {
|
||||
let dialog = simple_dialogs::CloseAllDocumentsDialog;
|
||||
dialog.register_properties(responses, LayoutTarget::DialogDetails);
|
||||
responses.push_back(FrontendMessage::DisplayDialog { icon: "Copy".to_string() }.into());
|
||||
}
|
||||
DialogMessage::CloseDialogAndThen { followups } => {
|
||||
responses.push_back(FrontendMessage::DisplayDialogDismiss.into());
|
||||
for message in followups.into_iter() {
|
||||
responses.push_back(message);
|
||||
}
|
||||
}
|
||||
DialogMessage::DisplayDialogError { title, description } => {
|
||||
let dialog = simple_dialogs::ErrorDialog { title, description };
|
||||
dialog.register_properties(responses, LayoutTarget::DialogDetails);
|
||||
responses.push_back(FrontendMessage::DisplayDialog { icon: "Warning".to_string() }.into());
|
||||
}
|
||||
DialogMessage::RequestAboutGraphiteDialog => {
|
||||
responses.push_back(
|
||||
FrontendMessage::TriggerAboutGraphiteLocalizedCommitDate {
|
||||
commit_date: env!("GRAPHITE_GIT_COMMIT_DATE").into(),
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
DialogMessage::RequestAboutGraphiteDialogWithLocalizedCommitDate { localized_commit_date } => {
|
||||
let about_graphite = AboutGraphiteDialog { localized_commit_date };
|
||||
|
||||
about_graphite.register_properties(responses, LayoutTarget::DialogDetails);
|
||||
responses.push_back(FrontendMessage::DisplayDialog { icon: "GraphiteLogo".to_string() }.into());
|
||||
}
|
||||
DialogMessage::RequestComingSoonDialog { issue } => {
|
||||
let coming_soon = ComingSoonDialog { issue };
|
||||
coming_soon.register_properties(responses, LayoutTarget::DialogDetails);
|
||||
responses.push_back(FrontendMessage::DisplayDialog { icon: "Warning".to_string() }.into());
|
||||
}
|
||||
DialogMessage::RequestExportDialog => {
|
||||
if let Some(document) = portfolio.active_document() {
|
||||
let artboard_handler = &document.artboard_message_handler;
|
||||
let mut index = 0;
|
||||
let artboards = artboard_handler
|
||||
.artboard_ids
|
||||
.iter()
|
||||
.rev()
|
||||
.filter_map(|&artboard| artboard_handler.artboards_graphene_document.layer(&[artboard]).ok().map(|layer| (artboard, layer)))
|
||||
.map(|(artboard, layer)| {
|
||||
(
|
||||
artboard,
|
||||
format!(
|
||||
"Artboard: {}",
|
||||
layer.name.clone().unwrap_or_else(|| {
|
||||
index += 1;
|
||||
format!("Untitled {index}")
|
||||
})
|
||||
),
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
|
||||
self.export_dialog = ExportDialogMessageHandler {
|
||||
file_name: document.name.clone(),
|
||||
scale_factor: 1.,
|
||||
artboards,
|
||||
has_selection: document.selected_layers().next().is_some(),
|
||||
..Default::default()
|
||||
};
|
||||
self.export_dialog.register_properties(responses, LayoutTarget::DialogDetails);
|
||||
responses.push_back(FrontendMessage::DisplayDialog { icon: "File".to_string() }.into());
|
||||
}
|
||||
}
|
||||
DialogMessage::RequestNewDocumentDialog => {
|
||||
self.new_document_dialog = NewDocumentDialogMessageHandler {
|
||||
name: portfolio.generate_new_document_name(),
|
||||
infinite: true,
|
||||
dimensions: glam::UVec2::new(1920, 1080),
|
||||
};
|
||||
self.new_document_dialog.register_properties(responses, LayoutTarget::DialogDetails);
|
||||
responses.push_back(FrontendMessage::DisplayDialog { icon: "File".to_string() }.into());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
advertise_actions!(DialogMessageDiscriminant;
|
||||
RequestNewDocumentDialog,
|
||||
RequestExportDialog,
|
||||
CloseAllDocumentsWithConfirmation,
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
use crate::messages::frontend::utility_types::{ExportBounds, FileType};
|
||||
use crate::messages::prelude::*;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[impl_message(Message, DialogMessage, ExportDialog)]
|
||||
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)]
|
||||
pub enum ExportDialogMessage {
|
||||
FileName(String),
|
||||
FileType(FileType),
|
||||
ScaleFactor(f64),
|
||||
ExportBounds(ExportBounds),
|
||||
|
||||
Submit,
|
||||
}
|
||||
@@ -0,0 +1,185 @@
|
||||
use super::ExportDialogMessage;
|
||||
use crate::messages::frontend::utility_types::{ExportBounds, FileType};
|
||||
use crate::messages::layout::utility_types::layout_widget::{Layout, LayoutGroup, PropertyHolder, Widget, WidgetCallback, WidgetHolder, WidgetLayout};
|
||||
use crate::messages::layout::utility_types::misc::LayoutTarget;
|
||||
use crate::messages::layout::utility_types::widgets::button_widgets::TextButton;
|
||||
use crate::messages::layout::utility_types::widgets::input_widgets::{DropdownEntryData, DropdownInput, NumberInput, RadioEntryData, RadioInput, TextInput};
|
||||
use crate::messages::layout::utility_types::widgets::label_widgets::{Separator, SeparatorDirection, SeparatorType, TextLabel};
|
||||
use crate::messages::prelude::*;
|
||||
|
||||
use graphene::LayerId;
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
/// A dialog to allow users to customize their file export.
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct ExportDialogMessageHandler {
|
||||
pub file_name: String,
|
||||
pub file_type: FileType,
|
||||
pub scale_factor: f64,
|
||||
pub bounds: ExportBounds,
|
||||
pub artboards: HashMap<LayerId, String>,
|
||||
pub has_selection: bool,
|
||||
}
|
||||
|
||||
impl MessageHandler<ExportDialogMessage, ()> for ExportDialogMessageHandler {
|
||||
fn process_message(&mut self, message: ExportDialogMessage, _data: (), responses: &mut VecDeque<Message>) {
|
||||
match message {
|
||||
ExportDialogMessage::FileName(name) => self.file_name = name,
|
||||
ExportDialogMessage::FileType(export_type) => self.file_type = export_type,
|
||||
ExportDialogMessage::ScaleFactor(x) => self.scale_factor = x,
|
||||
ExportDialogMessage::ExportBounds(export_area) => self.bounds = export_area,
|
||||
|
||||
ExportDialogMessage::Submit => responses.push_front(
|
||||
DocumentMessage::ExportDocument {
|
||||
file_name: self.file_name.clone(),
|
||||
file_type: self.file_type,
|
||||
scale_factor: self.scale_factor,
|
||||
bounds: self.bounds,
|
||||
}
|
||||
.into(),
|
||||
),
|
||||
}
|
||||
|
||||
self.register_properties(responses, LayoutTarget::DialogDetails);
|
||||
}
|
||||
|
||||
advertise_actions! {ExportDialogUpdate;}
|
||||
}
|
||||
|
||||
impl PropertyHolder for ExportDialogMessageHandler {
|
||||
fn properties(&self) -> Layout {
|
||||
let file_name = vec![
|
||||
WidgetHolder::new(Widget::TextLabel(TextLabel {
|
||||
value: "File Name".into(),
|
||||
table_align: true,
|
||||
..Default::default()
|
||||
})),
|
||||
WidgetHolder::new(Widget::Separator(Separator {
|
||||
separator_type: SeparatorType::Unrelated,
|
||||
direction: SeparatorDirection::Horizontal,
|
||||
})),
|
||||
WidgetHolder::new(Widget::TextInput(TextInput {
|
||||
value: self.file_name.clone(),
|
||||
on_update: WidgetCallback::new(|text_input: &TextInput| ExportDialogMessage::FileName(text_input.value.clone()).into()),
|
||||
..Default::default()
|
||||
})),
|
||||
];
|
||||
|
||||
let entries = [(FileType::Svg, "SVG"), (FileType::Png, "PNG"), (FileType::Jpg, "JPG")]
|
||||
.into_iter()
|
||||
.map(|(val, name)| RadioEntryData {
|
||||
label: name.into(),
|
||||
on_update: WidgetCallback::new(move |_| ExportDialogMessage::FileType(val).into()),
|
||||
..RadioEntryData::default()
|
||||
})
|
||||
.collect();
|
||||
|
||||
let export_type = vec![
|
||||
WidgetHolder::new(Widget::TextLabel(TextLabel {
|
||||
value: "File Type".into(),
|
||||
table_align: true,
|
||||
..Default::default()
|
||||
})),
|
||||
WidgetHolder::new(Widget::Separator(Separator {
|
||||
separator_type: SeparatorType::Unrelated,
|
||||
direction: SeparatorDirection::Horizontal,
|
||||
})),
|
||||
WidgetHolder::new(Widget::RadioInput(RadioInput {
|
||||
selected_index: self.file_type as u32,
|
||||
entries,
|
||||
})),
|
||||
];
|
||||
|
||||
let artboards = self.artboards.iter().map(|(&val, name)| (ExportBounds::Artboard(val), name.to_string(), false));
|
||||
let mut export_area_options = vec![
|
||||
(ExportBounds::AllArtwork, "All Artwork".to_string(), false),
|
||||
(ExportBounds::Selection, "Selection".to_string(), !self.has_selection),
|
||||
];
|
||||
export_area_options.extend(artboards);
|
||||
let index = export_area_options.iter().position(|(val, _, _)| val == &self.bounds).unwrap();
|
||||
let entries = vec![export_area_options
|
||||
.into_iter()
|
||||
.map(|(val, name, disabled)| DropdownEntryData {
|
||||
label: name,
|
||||
on_update: WidgetCallback::new(move |_| ExportDialogMessage::ExportBounds(val).into()),
|
||||
disabled,
|
||||
..Default::default()
|
||||
})
|
||||
.collect()];
|
||||
|
||||
let export_area = vec![
|
||||
WidgetHolder::new(Widget::TextLabel(TextLabel {
|
||||
value: "Bounds".into(),
|
||||
table_align: true,
|
||||
..Default::default()
|
||||
})),
|
||||
WidgetHolder::new(Widget::Separator(Separator {
|
||||
separator_type: SeparatorType::Unrelated,
|
||||
direction: SeparatorDirection::Horizontal,
|
||||
})),
|
||||
WidgetHolder::new(Widget::DropdownInput(DropdownInput {
|
||||
selected_index: Some(index as u32),
|
||||
entries,
|
||||
..Default::default()
|
||||
})),
|
||||
];
|
||||
|
||||
let resolution = vec![
|
||||
WidgetHolder::new(Widget::TextLabel(TextLabel {
|
||||
value: "Scale Factor".into(),
|
||||
table_align: true,
|
||||
..TextLabel::default()
|
||||
})),
|
||||
WidgetHolder::new(Widget::Separator(Separator {
|
||||
separator_type: SeparatorType::Unrelated,
|
||||
direction: SeparatorDirection::Horizontal,
|
||||
})),
|
||||
WidgetHolder::new(Widget::NumberInput(NumberInput {
|
||||
value: Some(self.scale_factor),
|
||||
label: "".into(),
|
||||
unit: " ".into(),
|
||||
min: Some(0.),
|
||||
disabled: self.file_type == FileType::Svg,
|
||||
on_update: WidgetCallback::new(|number_input: &NumberInput| ExportDialogMessage::ScaleFactor(number_input.value.unwrap()).into()),
|
||||
..NumberInput::default()
|
||||
})),
|
||||
];
|
||||
|
||||
let button_widgets = vec![
|
||||
WidgetHolder::new(Widget::TextButton(TextButton {
|
||||
label: "Export".to_string(),
|
||||
min_width: 96,
|
||||
emphasized: true,
|
||||
on_update: WidgetCallback::new(|_| {
|
||||
DialogMessage::CloseDialogAndThen {
|
||||
followups: vec![ExportDialogMessage::Submit.into()],
|
||||
}
|
||||
.into()
|
||||
}),
|
||||
..Default::default()
|
||||
})),
|
||||
WidgetHolder::new(Widget::TextButton(TextButton {
|
||||
label: "Cancel".to_string(),
|
||||
min_width: 96,
|
||||
on_update: WidgetCallback::new(|_| FrontendMessage::DisplayDialogDismiss.into()),
|
||||
..Default::default()
|
||||
})),
|
||||
];
|
||||
|
||||
Layout::WidgetLayout(WidgetLayout::new(vec![
|
||||
LayoutGroup::Row {
|
||||
widgets: vec![WidgetHolder::new(Widget::TextLabel(TextLabel {
|
||||
value: "Export".to_string(),
|
||||
bold: true,
|
||||
..Default::default()
|
||||
}))],
|
||||
},
|
||||
LayoutGroup::Row { widgets: file_name },
|
||||
LayoutGroup::Row { widgets: export_type },
|
||||
LayoutGroup::Row { widgets: resolution },
|
||||
LayoutGroup::Row { widgets: export_area },
|
||||
LayoutGroup::Row { widgets: button_widgets },
|
||||
]))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
mod export_dialog_message;
|
||||
mod export_dialog_message_handler;
|
||||
|
||||
#[doc(inline)]
|
||||
pub use export_dialog_message::{ExportDialogMessage, ExportDialogMessageDiscriminant};
|
||||
#[doc(inline)]
|
||||
pub use export_dialog_message_handler::ExportDialogMessageHandler;
|
||||
@@ -0,0 +1,18 @@
|
||||
//! Handles modal dialogs that appear as floating menus in the center of the editor window.
|
||||
//!
|
||||
//! Dialogs are represented as structs that implement the `PropertyHolder` trait.
|
||||
//!
|
||||
//! To open a dialog, call the function `register_properties` on the dialog struct with `responses` and the `LayoutTarget::DialogDetails` enum variant.
|
||||
//! Then dialog can be opened by sending the `FrontendMessage::DisplayDialog` message;
|
||||
|
||||
mod dialog_message;
|
||||
mod dialog_message_handler;
|
||||
|
||||
pub mod export_dialog;
|
||||
pub mod new_document_dialog;
|
||||
pub mod simple_dialogs;
|
||||
|
||||
#[doc(inline)]
|
||||
pub use dialog_message::{DialogMessage, DialogMessageDiscriminant};
|
||||
#[doc(inline)]
|
||||
pub use dialog_message_handler::DialogMessageHandler;
|
||||
@@ -0,0 +1,7 @@
|
||||
mod new_document_dialog_message;
|
||||
mod new_document_dialog_message_handler;
|
||||
|
||||
#[doc(inline)]
|
||||
pub use new_document_dialog_message::{NewDocumentDialogMessage, NewDocumentDialogMessageDiscriminant};
|
||||
#[doc(inline)]
|
||||
pub use new_document_dialog_message_handler::NewDocumentDialogMessageHandler;
|
||||
@@ -0,0 +1,14 @@
|
||||
use crate::messages::prelude::*;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[impl_message(Message, DialogMessage, NewDocumentDialog)]
|
||||
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)]
|
||||
pub enum NewDocumentDialogMessage {
|
||||
Name(String),
|
||||
Infinite(bool),
|
||||
DimensionsX(f64),
|
||||
DimensionsY(f64),
|
||||
|
||||
Submit,
|
||||
}
|
||||
+158
@@ -0,0 +1,158 @@
|
||||
use super::NewDocumentDialogMessage;
|
||||
use crate::messages::layout::utility_types::layout_widget::{Layout, LayoutGroup, PropertyHolder, Widget, WidgetCallback, WidgetHolder, WidgetLayout};
|
||||
use crate::messages::layout::utility_types::misc::LayoutTarget;
|
||||
use crate::messages::layout::utility_types::widgets::button_widgets::TextButton;
|
||||
use crate::messages::layout::utility_types::widgets::input_widgets::{CheckboxInput, NumberInput, TextInput};
|
||||
use crate::messages::layout::utility_types::widgets::label_widgets::{Separator, SeparatorDirection, SeparatorType, TextLabel};
|
||||
use crate::messages::prelude::*;
|
||||
|
||||
use glam::UVec2;
|
||||
|
||||
/// A dialog to allow users to set some initial options about a new document.
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct NewDocumentDialogMessageHandler {
|
||||
pub name: String,
|
||||
pub infinite: bool,
|
||||
pub dimensions: UVec2,
|
||||
}
|
||||
|
||||
impl MessageHandler<NewDocumentDialogMessage, ()> for NewDocumentDialogMessageHandler {
|
||||
fn process_message(&mut self, message: NewDocumentDialogMessage, _data: (), responses: &mut VecDeque<Message>) {
|
||||
match message {
|
||||
NewDocumentDialogMessage::Name(name) => self.name = name,
|
||||
NewDocumentDialogMessage::Infinite(infinite) => self.infinite = infinite,
|
||||
NewDocumentDialogMessage::DimensionsX(x) => self.dimensions.x = x as u32,
|
||||
NewDocumentDialogMessage::DimensionsY(y) => self.dimensions.y = y as u32,
|
||||
|
||||
NewDocumentDialogMessage::Submit => {
|
||||
responses.push_back(PortfolioMessage::NewDocumentWithName { name: self.name.clone() }.into());
|
||||
|
||||
if !self.infinite && self.dimensions.x > 0 && self.dimensions.y > 0 {
|
||||
responses.push_back(
|
||||
ArtboardMessage::AddArtboard {
|
||||
id: None,
|
||||
position: (0., 0.),
|
||||
size: (self.dimensions.x as f64, self.dimensions.y as f64),
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
responses.push_back(DocumentMessage::ZoomCanvasToFitAll.into());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.register_properties(responses, LayoutTarget::DialogDetails);
|
||||
}
|
||||
|
||||
advertise_actions! {NewDocumentDialogUpdate;}
|
||||
}
|
||||
|
||||
impl PropertyHolder for NewDocumentDialogMessageHandler {
|
||||
fn properties(&self) -> Layout {
|
||||
let title = vec![WidgetHolder::new(Widget::TextLabel(TextLabel {
|
||||
value: "New document".into(),
|
||||
bold: true,
|
||||
..Default::default()
|
||||
}))];
|
||||
|
||||
let name = vec![
|
||||
WidgetHolder::new(Widget::TextLabel(TextLabel {
|
||||
value: "Name".into(),
|
||||
table_align: true,
|
||||
..Default::default()
|
||||
})),
|
||||
WidgetHolder::new(Widget::Separator(Separator {
|
||||
separator_type: SeparatorType::Unrelated,
|
||||
direction: SeparatorDirection::Horizontal,
|
||||
})),
|
||||
WidgetHolder::new(Widget::TextInput(TextInput {
|
||||
value: self.name.clone(),
|
||||
on_update: WidgetCallback::new(|text_input: &TextInput| NewDocumentDialogMessage::Name(text_input.value.clone()).into()),
|
||||
..Default::default()
|
||||
})),
|
||||
];
|
||||
|
||||
let infinite = vec![
|
||||
WidgetHolder::new(Widget::TextLabel(TextLabel {
|
||||
value: "Infinite Canvas".into(),
|
||||
table_align: true,
|
||||
..Default::default()
|
||||
})),
|
||||
WidgetHolder::new(Widget::Separator(Separator {
|
||||
separator_type: SeparatorType::Unrelated,
|
||||
direction: SeparatorDirection::Horizontal,
|
||||
})),
|
||||
WidgetHolder::new(Widget::CheckboxInput(CheckboxInput {
|
||||
checked: self.infinite,
|
||||
icon: "Checkmark".to_string(),
|
||||
on_update: WidgetCallback::new(|checkbox_input: &CheckboxInput| NewDocumentDialogMessage::Infinite(checkbox_input.checked).into()),
|
||||
..Default::default()
|
||||
})),
|
||||
];
|
||||
|
||||
let scale = vec![
|
||||
WidgetHolder::new(Widget::TextLabel(TextLabel {
|
||||
value: "Dimensions".into(),
|
||||
table_align: true,
|
||||
..TextLabel::default()
|
||||
})),
|
||||
WidgetHolder::new(Widget::Separator(Separator {
|
||||
separator_type: SeparatorType::Unrelated,
|
||||
direction: SeparatorDirection::Horizontal,
|
||||
})),
|
||||
WidgetHolder::new(Widget::NumberInput(NumberInput {
|
||||
value: Some(self.dimensions.x as f64),
|
||||
label: "W".into(),
|
||||
unit: " px".into(),
|
||||
disabled: self.infinite,
|
||||
is_integer: true,
|
||||
min: Some(0.),
|
||||
on_update: WidgetCallback::new(|number_input: &NumberInput| NewDocumentDialogMessage::DimensionsX(number_input.value.unwrap()).into()),
|
||||
..NumberInput::default()
|
||||
})),
|
||||
WidgetHolder::new(Widget::Separator(Separator {
|
||||
separator_type: SeparatorType::Related,
|
||||
direction: SeparatorDirection::Horizontal,
|
||||
})),
|
||||
WidgetHolder::new(Widget::NumberInput(NumberInput {
|
||||
value: Some(self.dimensions.y as f64),
|
||||
label: "H".into(),
|
||||
unit: " px".into(),
|
||||
disabled: self.infinite,
|
||||
is_integer: true,
|
||||
min: Some(0.),
|
||||
on_update: WidgetCallback::new(|number_input: &NumberInput| NewDocumentDialogMessage::DimensionsY(number_input.value.unwrap()).into()),
|
||||
..NumberInput::default()
|
||||
})),
|
||||
];
|
||||
|
||||
let button_widgets = vec![
|
||||
WidgetHolder::new(Widget::TextButton(TextButton {
|
||||
label: "OK".to_string(),
|
||||
min_width: 96,
|
||||
emphasized: true,
|
||||
on_update: WidgetCallback::new(|_| {
|
||||
DialogMessage::CloseDialogAndThen {
|
||||
followups: vec![NewDocumentDialogMessage::Submit.into()],
|
||||
}
|
||||
.into()
|
||||
}),
|
||||
..Default::default()
|
||||
})),
|
||||
WidgetHolder::new(Widget::TextButton(TextButton {
|
||||
label: "Cancel".to_string(),
|
||||
min_width: 96,
|
||||
on_update: WidgetCallback::new(|_| FrontendMessage::DisplayDialogDismiss.into()),
|
||||
..Default::default()
|
||||
})),
|
||||
];
|
||||
|
||||
Layout::WidgetLayout(WidgetLayout::new(vec![
|
||||
LayoutGroup::Row { widgets: title },
|
||||
LayoutGroup::Row { widgets: name },
|
||||
LayoutGroup::Row { widgets: infinite },
|
||||
LayoutGroup::Row { widgets: scale },
|
||||
LayoutGroup::Row { widgets: button_widgets },
|
||||
]))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
use crate::application::{commit_info_localized, release_series};
|
||||
use crate::messages::layout::utility_types::layout_widget::{Layout, LayoutGroup, PropertyHolder, Widget, WidgetCallback, WidgetHolder, WidgetLayout};
|
||||
use crate::messages::layout::utility_types::widgets::button_widgets::TextButton;
|
||||
use crate::messages::layout::utility_types::widgets::label_widgets::TextLabel;
|
||||
use crate::messages::prelude::*;
|
||||
|
||||
/// A dialog for displaying information on [BuildMetadata] viewable via *Help* > *About Graphite* in the menu bar.
|
||||
pub struct AboutGraphiteDialog {
|
||||
pub localized_commit_date: String,
|
||||
}
|
||||
|
||||
impl PropertyHolder for AboutGraphiteDialog {
|
||||
fn properties(&self) -> Layout {
|
||||
let links = [
|
||||
("Website", "https://graphite.rs"),
|
||||
("Credits", "https://github.com/GraphiteEditor/Graphite/graphs/contributors"),
|
||||
("License", "https://raw.githubusercontent.com/GraphiteEditor/Graphite/master/LICENSE.txt"),
|
||||
("Third-Party Licenses", "/third-party-licenses.txt"),
|
||||
];
|
||||
let link_widgets = links
|
||||
.into_iter()
|
||||
.map(|(label, url)| {
|
||||
WidgetHolder::new(Widget::TextButton(TextButton {
|
||||
label: label.to_string(),
|
||||
on_update: WidgetCallback::new(|_| FrontendMessage::TriggerVisitLink { url: url.to_string() }.into()),
|
||||
..Default::default()
|
||||
}))
|
||||
})
|
||||
.collect();
|
||||
Layout::WidgetLayout(WidgetLayout::new(vec![
|
||||
LayoutGroup::Row {
|
||||
widgets: vec![WidgetHolder::new(Widget::TextLabel(TextLabel {
|
||||
value: "Graphite".to_string(),
|
||||
bold: true,
|
||||
..Default::default()
|
||||
}))],
|
||||
},
|
||||
LayoutGroup::Row {
|
||||
widgets: vec![WidgetHolder::new(Widget::TextLabel(TextLabel {
|
||||
value: release_series(),
|
||||
..Default::default()
|
||||
}))],
|
||||
},
|
||||
LayoutGroup::Row {
|
||||
widgets: vec![WidgetHolder::new(Widget::TextLabel(TextLabel {
|
||||
value: commit_info_localized(self.localized_commit_date.as_str()),
|
||||
multiline: true,
|
||||
..Default::default()
|
||||
}))],
|
||||
},
|
||||
LayoutGroup::Row { widgets: link_widgets },
|
||||
]))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
use crate::messages::layout::utility_types::layout_widget::{Layout, LayoutGroup, PropertyHolder, Widget, WidgetCallback, WidgetHolder, WidgetLayout};
|
||||
use crate::messages::layout::utility_types::widgets::button_widgets::TextButton;
|
||||
use crate::messages::layout::utility_types::widgets::label_widgets::TextLabel;
|
||||
use crate::messages::prelude::*;
|
||||
|
||||
/// A dialog for confirming the closing of all documents viewable via `file -> close all` in the menu bar.
|
||||
pub struct CloseAllDocumentsDialog;
|
||||
|
||||
impl PropertyHolder for CloseAllDocumentsDialog {
|
||||
fn properties(&self) -> Layout {
|
||||
let button_widgets = vec![
|
||||
WidgetHolder::new(Widget::TextButton(TextButton {
|
||||
label: "Discard All".to_string(),
|
||||
min_width: 96,
|
||||
on_update: WidgetCallback::new(|_| {
|
||||
DialogMessage::CloseDialogAndThen {
|
||||
followups: vec![PortfolioMessage::CloseAllDocuments.into()],
|
||||
}
|
||||
.into()
|
||||
}),
|
||||
..Default::default()
|
||||
})),
|
||||
WidgetHolder::new(Widget::TextButton(TextButton {
|
||||
label: "Cancel".to_string(),
|
||||
min_width: 96,
|
||||
on_update: WidgetCallback::new(|_| FrontendMessage::DisplayDialogDismiss.into()),
|
||||
..Default::default()
|
||||
})),
|
||||
];
|
||||
|
||||
Layout::WidgetLayout(WidgetLayout::new(vec![
|
||||
LayoutGroup::Row {
|
||||
widgets: vec![WidgetHolder::new(Widget::TextLabel(TextLabel {
|
||||
value: "Close all documents?".to_string(),
|
||||
bold: true,
|
||||
..Default::default()
|
||||
}))],
|
||||
},
|
||||
LayoutGroup::Row {
|
||||
widgets: vec![WidgetHolder::new(Widget::TextLabel(TextLabel {
|
||||
value: "Unsaved work will be lost!".to_string(),
|
||||
multiline: true,
|
||||
..Default::default()
|
||||
}))],
|
||||
},
|
||||
LayoutGroup::Row { widgets: button_widgets },
|
||||
]))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
use crate::messages::broadcast::broadcast_event::BroadcastEvent;
|
||||
use crate::messages::layout::utility_types::layout_widget::{Layout, LayoutGroup, PropertyHolder, Widget, WidgetCallback, WidgetHolder, WidgetLayout};
|
||||
use crate::messages::layout::utility_types::widgets::button_widgets::TextButton;
|
||||
use crate::messages::layout::utility_types::widgets::label_widgets::TextLabel;
|
||||
use crate::messages::portfolio::document::DocumentMessage;
|
||||
use crate::messages::prelude::*;
|
||||
|
||||
/// A dialog for confirming the closing a document with unsaved changes.
|
||||
pub struct CloseDocumentDialog {
|
||||
pub document_name: String,
|
||||
pub document_id: u64,
|
||||
}
|
||||
|
||||
impl PropertyHolder for CloseDocumentDialog {
|
||||
fn properties(&self) -> Layout {
|
||||
let document_id = self.document_id;
|
||||
|
||||
let button_widgets = vec![
|
||||
WidgetHolder::new(Widget::TextButton(TextButton {
|
||||
label: "Save".to_string(),
|
||||
min_width: 96,
|
||||
emphasized: true,
|
||||
on_update: WidgetCallback::new(|_| {
|
||||
DialogMessage::CloseDialogAndThen {
|
||||
followups: vec![DocumentMessage::SaveDocument.into()],
|
||||
}
|
||||
.into()
|
||||
}),
|
||||
..Default::default()
|
||||
})),
|
||||
WidgetHolder::new(Widget::TextButton(TextButton {
|
||||
label: "Discard".to_string(),
|
||||
min_width: 96,
|
||||
on_update: WidgetCallback::new(move |_| {
|
||||
DialogMessage::CloseDialogAndThen {
|
||||
followups: vec![BroadcastEvent::ToolAbort.into(), PortfolioMessage::CloseDocument { document_id }.into()],
|
||||
}
|
||||
.into()
|
||||
}),
|
||||
..Default::default()
|
||||
})),
|
||||
WidgetHolder::new(Widget::TextButton(TextButton {
|
||||
label: "Cancel".to_string(),
|
||||
min_width: 96,
|
||||
on_update: WidgetCallback::new(|_| FrontendMessage::DisplayDialogDismiss.into()),
|
||||
..Default::default()
|
||||
})),
|
||||
];
|
||||
|
||||
Layout::WidgetLayout(WidgetLayout::new(vec![
|
||||
LayoutGroup::Row {
|
||||
widgets: vec![WidgetHolder::new(Widget::TextLabel(TextLabel {
|
||||
value: "Save changes before closing?".to_string(),
|
||||
bold: true,
|
||||
..Default::default()
|
||||
}))],
|
||||
},
|
||||
LayoutGroup::Row {
|
||||
widgets: vec![WidgetHolder::new(Widget::TextLabel(TextLabel {
|
||||
value: self.document_name.clone(),
|
||||
multiline: true,
|
||||
..Default::default()
|
||||
}))],
|
||||
},
|
||||
LayoutGroup::Row { widgets: button_widgets },
|
||||
]))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
use crate::messages::layout::utility_types::layout_widget::{Layout, LayoutGroup, PropertyHolder, Widget, WidgetCallback, WidgetHolder, WidgetLayout};
|
||||
use crate::messages::layout::utility_types::widgets::button_widgets::TextButton;
|
||||
use crate::messages::layout::utility_types::widgets::label_widgets::TextLabel;
|
||||
use crate::messages::prelude::*;
|
||||
|
||||
use std::fmt::Write;
|
||||
|
||||
/// A dialog to notify users of an unfinished issue, optionally with an issue number.
|
||||
pub struct ComingSoonDialog {
|
||||
pub issue: Option<i32>,
|
||||
}
|
||||
|
||||
impl PropertyHolder for ComingSoonDialog {
|
||||
fn properties(&self) -> Layout {
|
||||
let mut details = "This feature is not implemented yet".to_string();
|
||||
let mut buttons = vec![WidgetHolder::new(Widget::TextButton(TextButton {
|
||||
label: "OK".to_string(),
|
||||
emphasized: true,
|
||||
min_width: 96,
|
||||
on_update: WidgetCallback::new(|_| FrontendMessage::DisplayDialogDismiss.into()),
|
||||
..Default::default()
|
||||
}))];
|
||||
if let Some(issue) = self.issue {
|
||||
let _ = write!(details, "— but you can help add it!\nSee issue #{issue} on GitHub.");
|
||||
buttons.push(WidgetHolder::new(Widget::TextButton(TextButton {
|
||||
label: format!("Issue #{issue}"),
|
||||
min_width: 96,
|
||||
on_update: WidgetCallback::new(move |_| {
|
||||
FrontendMessage::TriggerVisitLink {
|
||||
url: format!("https://github.com/GraphiteEditor/Graphite/issues/{issue}"),
|
||||
}
|
||||
.into()
|
||||
}),
|
||||
..Default::default()
|
||||
})));
|
||||
}
|
||||
Layout::WidgetLayout(WidgetLayout::new(vec![
|
||||
LayoutGroup::Row {
|
||||
widgets: vec![WidgetHolder::new(Widget::TextLabel(TextLabel {
|
||||
value: "Coming soon".to_string(),
|
||||
bold: true,
|
||||
..Default::default()
|
||||
}))],
|
||||
},
|
||||
LayoutGroup::Row {
|
||||
widgets: vec![WidgetHolder::new(Widget::TextLabel(TextLabel {
|
||||
value: details,
|
||||
multiline: true,
|
||||
..Default::default()
|
||||
}))],
|
||||
},
|
||||
LayoutGroup::Row { widgets: buttons },
|
||||
]))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
use crate::messages::layout::utility_types::layout_widget::{Layout, LayoutGroup, PropertyHolder, Widget, WidgetCallback, WidgetHolder, WidgetLayout};
|
||||
use crate::messages::layout::utility_types::widgets::button_widgets::TextButton;
|
||||
use crate::messages::layout::utility_types::widgets::label_widgets::TextLabel;
|
||||
use crate::messages::prelude::*;
|
||||
|
||||
/// A dialog to notify users of a non-fatal error.
|
||||
pub struct ErrorDialog {
|
||||
pub title: String,
|
||||
pub description: String,
|
||||
}
|
||||
|
||||
impl PropertyHolder for ErrorDialog {
|
||||
fn properties(&self) -> Layout {
|
||||
Layout::WidgetLayout(WidgetLayout::new(vec![
|
||||
LayoutGroup::Row {
|
||||
widgets: vec![WidgetHolder::new(Widget::TextLabel(TextLabel {
|
||||
value: self.title.clone(),
|
||||
bold: true,
|
||||
..Default::default()
|
||||
}))],
|
||||
},
|
||||
LayoutGroup::Row {
|
||||
widgets: vec![WidgetHolder::new(Widget::TextLabel(TextLabel {
|
||||
value: self.description.clone(),
|
||||
multiline: true,
|
||||
..Default::default()
|
||||
}))],
|
||||
},
|
||||
LayoutGroup::Row {
|
||||
widgets: vec![WidgetHolder::new(Widget::TextButton(TextButton {
|
||||
label: "OK".to_string(),
|
||||
emphasized: true,
|
||||
min_width: 96,
|
||||
on_update: WidgetCallback::new(|_| FrontendMessage::DisplayDialogDismiss.into()),
|
||||
..Default::default()
|
||||
}))],
|
||||
},
|
||||
]))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
mod about_graphite_dialog;
|
||||
mod close_all_documents_dialog;
|
||||
mod close_document_dialog;
|
||||
mod coming_soon_dialog;
|
||||
mod error_dialog;
|
||||
|
||||
pub use about_graphite_dialog::AboutGraphiteDialog;
|
||||
pub use close_all_documents_dialog::CloseAllDocumentsDialog;
|
||||
pub use close_document_dialog::CloseDocumentDialog;
|
||||
pub use coming_soon_dialog::ComingSoonDialog;
|
||||
pub use error_dialog::ErrorDialog;
|
||||
Reference in New Issue
Block a user