mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-22 13:28:12 +08:00
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>
This commit is contained in:
committed by
Keavon Chambers
co-authored by
Keavon Chambers
parent
7729b6219e
commit
f177f63217
@@ -0,0 +1,27 @@
|
||||
use crate::message_prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use super::NewDocumentDialogUpdate;
|
||||
|
||||
#[remain::sorted]
|
||||
#[impl_message(Message, Dialog)]
|
||||
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)]
|
||||
pub enum DialogMessage {
|
||||
#[remain::unsorted]
|
||||
#[child]
|
||||
NewDocumentDialog(NewDocumentDialogUpdate),
|
||||
|
||||
CloseAllDocumentsWithConfirmation,
|
||||
CloseDialogAndThen {
|
||||
followup: Box<Message>,
|
||||
},
|
||||
DisplayDialogError {
|
||||
title: String,
|
||||
description: String,
|
||||
},
|
||||
RequestAboutGraphiteDialog,
|
||||
RequestComingSoonDialog {
|
||||
issue: Option<i32>,
|
||||
},
|
||||
RequestNewDocumentDialog,
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
use crate::communication::BuildMetadata;
|
||||
use crate::document::PortfolioMessageHandler;
|
||||
use crate::layout::{layout_message::LayoutTarget, widgets::PropertyHolder};
|
||||
use crate::message_prelude::*;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct DialogMessageHandler {
|
||||
new_document_dialog: NewDocument,
|
||||
}
|
||||
|
||||
impl MessageHandler<DialogMessage, (&BuildMetadata, &PortfolioMessageHandler)> for DialogMessageHandler {
|
||||
#[remain::check]
|
||||
fn process_action(&mut self, message: DialogMessage, (build_metadata, portfolio): (&BuildMetadata, &PortfolioMessageHandler), responses: &mut VecDeque<Message>) {
|
||||
#[remain::sorted]
|
||||
match message {
|
||||
#[remain::unsorted]
|
||||
DialogMessage::NewDocumentDialog(message) => self.new_document_dialog.process_action(message, (), responses),
|
||||
|
||||
DialogMessage::CloseAllDocumentsWithConfirmation => {
|
||||
let dialog = dialogs::CloseAllDocuments;
|
||||
dialog.register_properties(responses, LayoutTarget::DialogDetails);
|
||||
responses.push_back(FrontendMessage::DisplayDialog { icon: "Copy".to_string() }.into());
|
||||
}
|
||||
DialogMessage::CloseDialogAndThen { followup } => {
|
||||
responses.push_back(FrontendMessage::DisplayDialogDismiss.into());
|
||||
responses.push_back(*followup);
|
||||
}
|
||||
DialogMessage::DisplayDialogError { title, description } => {
|
||||
let dialog = dialogs::Error { title, description };
|
||||
dialog.register_properties(responses, LayoutTarget::DialogDetails);
|
||||
responses.push_back(FrontendMessage::DisplayDialog { icon: "Warning".to_string() }.into());
|
||||
}
|
||||
DialogMessage::RequestAboutGraphiteDialog => {
|
||||
let about_graphite = AboutGraphite {
|
||||
build_metadata: build_metadata.clone(),
|
||||
};
|
||||
about_graphite.register_properties(responses, LayoutTarget::DialogDetails);
|
||||
responses.push_back(FrontendMessage::DisplayDialog { icon: "GraphiteLogo".to_string() }.into());
|
||||
}
|
||||
DialogMessage::RequestComingSoonDialog { issue } => {
|
||||
let coming_soon = ComingSoon { issue };
|
||||
coming_soon.register_properties(responses, LayoutTarget::DialogDetails);
|
||||
responses.push_back(FrontendMessage::DisplayDialog { icon: "Warning".to_string() }.into());
|
||||
}
|
||||
DialogMessage::RequestNewDocumentDialog => {
|
||||
self.new_document_dialog = NewDocument {
|
||||
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,CloseAllDocumentsWithConfirmation);
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
use crate::{communication::BuildMetadata, layout::widgets::*, message_prelude::FrontendMessage};
|
||||
|
||||
/// A dialog for displaying information on [`BuildMetadata`] viewable via `help -> about graphite` in the menu bar.
|
||||
pub struct AboutGraphite {
|
||||
pub build_metadata: BuildMetadata,
|
||||
}
|
||||
|
||||
impl PropertyHolder for AboutGraphite {
|
||||
fn properties(&self) -> WidgetLayout {
|
||||
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();
|
||||
WidgetLayout::new(vec![
|
||||
LayoutRow::Row {
|
||||
widgets: vec![WidgetHolder::new(Widget::TextLabel(TextLabel {
|
||||
value: "Graphite".to_string(),
|
||||
bold: true,
|
||||
..Default::default()
|
||||
}))],
|
||||
},
|
||||
LayoutRow::Row {
|
||||
widgets: vec![WidgetHolder::new(Widget::TextLabel(TextLabel {
|
||||
value: self.build_metadata.release_series(),
|
||||
..Default::default()
|
||||
}))],
|
||||
},
|
||||
LayoutRow::Row {
|
||||
widgets: vec![WidgetHolder::new(Widget::TextLabel(TextLabel {
|
||||
value: self.build_metadata.commit_info(),
|
||||
multiline: true,
|
||||
..Default::default()
|
||||
}))],
|
||||
},
|
||||
LayoutRow::Row { widgets: link_widgets },
|
||||
])
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
use crate::layout::widgets::*;
|
||||
use crate::message_prelude::{DialogMessage, FrontendMessage, PortfolioMessage};
|
||||
|
||||
/// A dialog for confirming the closing of all documents viewable via `file -> close all` in the menu bar.
|
||||
pub struct CloseAllDocuments;
|
||||
|
||||
impl PropertyHolder for CloseAllDocuments {
|
||||
fn properties(&self) -> WidgetLayout {
|
||||
let button_widgets = vec![
|
||||
WidgetHolder::new(Widget::TextButton(TextButton {
|
||||
label: "Discard All".to_string(),
|
||||
min_width: 96,
|
||||
on_update: WidgetCallback::new(|_| {
|
||||
DialogMessage::CloseDialogAndThen {
|
||||
followup: Box::new(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()
|
||||
})),
|
||||
];
|
||||
|
||||
WidgetLayout::new(vec![
|
||||
LayoutRow::Row {
|
||||
widgets: vec![WidgetHolder::new(Widget::TextLabel(TextLabel {
|
||||
value: "Close all documents?".to_string(),
|
||||
bold: true,
|
||||
..Default::default()
|
||||
}))],
|
||||
},
|
||||
LayoutRow::Row {
|
||||
widgets: vec![WidgetHolder::new(Widget::TextLabel(TextLabel {
|
||||
value: "Unsaved work will be lost!".to_string(),
|
||||
multiline: true,
|
||||
..Default::default()
|
||||
}))],
|
||||
},
|
||||
LayoutRow::Row { widgets: button_widgets },
|
||||
])
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
use crate::layout::widgets::*;
|
||||
use crate::message_prelude::{DialogMessage, DocumentMessage, FrontendMessage, PortfolioMessage};
|
||||
|
||||
/// A dialog for confirming the closing a document with unsaved changes.
|
||||
pub struct CloseDocument {
|
||||
pub document_name: String,
|
||||
pub document_id: u64,
|
||||
}
|
||||
|
||||
impl PropertyHolder for CloseDocument {
|
||||
fn properties(&self) -> WidgetLayout {
|
||||
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 {
|
||||
followup: Box::new(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 {
|
||||
followup: Box::new(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()
|
||||
})),
|
||||
];
|
||||
|
||||
WidgetLayout::new(vec![
|
||||
LayoutRow::Row {
|
||||
widgets: vec![WidgetHolder::new(Widget::TextLabel(TextLabel {
|
||||
value: "Save changes before closing?".to_string(),
|
||||
bold: true,
|
||||
..Default::default()
|
||||
}))],
|
||||
},
|
||||
LayoutRow::Row {
|
||||
widgets: vec![WidgetHolder::new(Widget::TextLabel(TextLabel {
|
||||
value: self.document_name.clone(),
|
||||
multiline: true,
|
||||
..Default::default()
|
||||
}))],
|
||||
},
|
||||
LayoutRow::Row { widgets: button_widgets },
|
||||
])
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
use crate::{layout::widgets::*, message_prelude::FrontendMessage};
|
||||
|
||||
/// A dialog to notify users of an unfinished issue, optionally with an issue number.
|
||||
pub struct ComingSoon {
|
||||
pub issue: Option<i32>,
|
||||
}
|
||||
|
||||
impl PropertyHolder for ComingSoon {
|
||||
fn properties(&self) -> WidgetLayout {
|
||||
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 {
|
||||
details += &format!("— 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()
|
||||
})));
|
||||
}
|
||||
WidgetLayout::new(vec![
|
||||
LayoutRow::Row {
|
||||
widgets: vec![WidgetHolder::new(Widget::TextLabel(TextLabel {
|
||||
value: "Coming soon".to_string(),
|
||||
bold: true,
|
||||
..Default::default()
|
||||
}))],
|
||||
},
|
||||
LayoutRow::Row {
|
||||
widgets: vec![WidgetHolder::new(Widget::TextLabel(TextLabel {
|
||||
value: details,
|
||||
multiline: true,
|
||||
..Default::default()
|
||||
}))],
|
||||
},
|
||||
LayoutRow::Row { widgets: buttons },
|
||||
])
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
use crate::{layout::widgets::*, message_prelude::FrontendMessage};
|
||||
|
||||
/// A dialog to notify users of a non-fatal error.
|
||||
pub struct Error {
|
||||
pub title: String,
|
||||
pub description: String,
|
||||
}
|
||||
|
||||
impl PropertyHolder for Error {
|
||||
fn properties(&self) -> WidgetLayout {
|
||||
WidgetLayout::new(vec![
|
||||
LayoutRow::Row {
|
||||
widgets: vec![WidgetHolder::new(Widget::TextLabel(TextLabel {
|
||||
value: self.title.clone(),
|
||||
bold: true,
|
||||
..Default::default()
|
||||
}))],
|
||||
},
|
||||
LayoutRow::Row {
|
||||
widgets: vec![WidgetHolder::new(Widget::TextLabel(TextLabel {
|
||||
value: self.description.clone(),
|
||||
multiline: true,
|
||||
..Default::default()
|
||||
}))],
|
||||
},
|
||||
LayoutRow::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,13 @@
|
||||
mod about_dialog;
|
||||
mod close_all_documents_dialog;
|
||||
mod close_document_dialog;
|
||||
mod coming_soon_dialog;
|
||||
mod error_dialog;
|
||||
mod new_document_dialog;
|
||||
|
||||
pub use about_dialog::AboutGraphite;
|
||||
pub use close_all_documents_dialog::CloseAllDocuments;
|
||||
pub use close_document_dialog::CloseDocument;
|
||||
pub use coming_soon_dialog::ComingSoon;
|
||||
pub use error_dialog::Error;
|
||||
pub use new_document_dialog::{NewDocument, NewDocumentDialogUpdate, NewDocumentDialogUpdateDiscriminant};
|
||||
@@ -0,0 +1,177 @@
|
||||
use crate::layout::layout_message::LayoutTarget;
|
||||
use crate::layout::widgets::*;
|
||||
use crate::message_prelude::*;
|
||||
|
||||
use glam::UVec2;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// A dialog to allow users to set some initial options about a new document.
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct NewDocument {
|
||||
pub name: String,
|
||||
pub infinite: bool,
|
||||
pub dimensions: UVec2,
|
||||
}
|
||||
|
||||
impl PropertyHolder for NewDocument {
|
||||
fn properties(&self) -> WidgetLayout {
|
||||
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| NewDocumentDialogUpdate::Name(text_input.value.clone()).into()),
|
||||
})),
|
||||
];
|
||||
|
||||
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| NewDocumentDialogUpdate::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: 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| NewDocumentDialogUpdate::DimensionsX(number_input.value).into()),
|
||||
..NumberInput::default()
|
||||
})),
|
||||
WidgetHolder::new(Widget::Separator(Separator {
|
||||
separator_type: SeparatorType::Related,
|
||||
direction: SeparatorDirection::Horizontal,
|
||||
})),
|
||||
WidgetHolder::new(Widget::NumberInput(NumberInput {
|
||||
value: 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| NewDocumentDialogUpdate::DimensionsY(number_input.value).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 {
|
||||
followup: Box::new(NewDocumentDialogUpdate::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()
|
||||
})),
|
||||
];
|
||||
|
||||
WidgetLayout::new(vec![
|
||||
LayoutRow::Row { widgets: title },
|
||||
LayoutRow::Row { widgets: name },
|
||||
LayoutRow::Row { widgets: infinite },
|
||||
LayoutRow::Row { widgets: scale },
|
||||
LayoutRow::Row { widgets: button_widgets },
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
#[impl_message(Message, DialogMessage, NewDocumentDialog)]
|
||||
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)]
|
||||
pub enum NewDocumentDialogUpdate {
|
||||
Name(String),
|
||||
Infinite(bool),
|
||||
DimensionsX(f64),
|
||||
DimensionsY(f64),
|
||||
|
||||
Submit,
|
||||
BufferArtboard,
|
||||
AddArtboard,
|
||||
FitCanvas,
|
||||
}
|
||||
|
||||
impl MessageHandler<NewDocumentDialogUpdate, ()> for NewDocument {
|
||||
fn process_action(&mut self, action: NewDocumentDialogUpdate, _data: (), responses: &mut VecDeque<Message>) {
|
||||
match action {
|
||||
NewDocumentDialogUpdate::Name(name) => self.name = name,
|
||||
NewDocumentDialogUpdate::Infinite(infinite) => self.infinite = infinite,
|
||||
NewDocumentDialogUpdate::DimensionsX(x) => self.dimensions.x = x as u32,
|
||||
NewDocumentDialogUpdate::DimensionsY(y) => self.dimensions.y = y as u32,
|
||||
|
||||
NewDocumentDialogUpdate::Submit => {
|
||||
responses.push_back(PortfolioMessage::NewDocumentWithName { name: self.name.clone() }.into());
|
||||
|
||||
responses.push_back(NewDocumentDialogUpdate::BufferArtboard.into());
|
||||
}
|
||||
NewDocumentDialogUpdate::BufferArtboard => {
|
||||
if !self.infinite {
|
||||
responses.push_back(NewDocumentDialogUpdate::AddArtboard.into());
|
||||
}
|
||||
}
|
||||
NewDocumentDialogUpdate::AddArtboard => {
|
||||
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(NewDocumentDialogUpdate::FitCanvas.into());
|
||||
}
|
||||
NewDocumentDialogUpdate::FitCanvas => {
|
||||
responses.push_back(DocumentMessage::ZoomCanvasToFitAll.into());
|
||||
}
|
||||
}
|
||||
|
||||
self.register_properties(responses, LayoutTarget::DialogDetails);
|
||||
}
|
||||
|
||||
advertise_actions! {NewDocumentDialogUpdate;}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
//! Handles dialogs/modals/popups that appear as boxes in the centre of the editor.
|
||||
//!
|
||||
//! Dialogs are represented as structs that implement the [`crate::layout::widgets::PropertyHolder`] trait.
|
||||
//!
|
||||
//! To open a dialog, call the function `register_properties` on the dialog struct with `responses` and the `LayoutTarget::DialogDetails`
|
||||
//! and then you can open the dialog with [`crate::message_prelude::FrontendMessage::DisplayDialog`]
|
||||
|
||||
mod dialog_message;
|
||||
mod dialog_message_handler;
|
||||
mod dialogs;
|
||||
|
||||
pub mod messages {
|
||||
pub use super::dialog_message::{DialogMessage, DialogMessageDiscriminant};
|
||||
pub use super::dialog_message_handler::DialogMessageHandler;
|
||||
}
|
||||
|
||||
pub use dialogs::*;
|
||||
Reference in New Issue
Block a user