mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-15 14:18:04 +08:00
Welcome screen, refactor to allow zero documents, and add TS typing to widgets (#702)
* unfinished implementation * Add frontend for the empty panel screen * Add an icon for Folder based on NodeFolder * fixed messages causing peicees of ui not to render on new document * Standardize nextTick syntax * WIP generisization of component subscriptions (not compiling yet) * Fix crash when loading font and there is no active document * Only advertise tool actions with a document * Fix failure to create new document * Initalise the properties panel * Fix highlight tab, canvas jump, warns and layer tree * Fix tests * Possibly fix some things? * Move WorkingColors layout definition to backend * Standardize action macro formatting * Provide typing for widgets in TS/Vue and associated cleanup * Fix viewport positioning initialization * Fix menu bar init at startup not document creation * Fix no viewport bounds bug * Change !=0 to >0 * Simplify the init system Closes #656 Co-authored-by: Keavon Chambers <keavon@keavon.com> Co-authored-by: 0hypercube <0hypercube@gmail.com>
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
use super::broadcast_message_handler::BroadcastMessageHandler;
|
||||
use crate::consts::{DEFAULT_FONT_FAMILY, DEFAULT_FONT_STYLE};
|
||||
use crate::document::PortfolioMessageHandler;
|
||||
use crate::global::GlobalMessageHandler;
|
||||
use crate::input::{InputMapperMessageHandler, InputPreprocessorMessageHandler};
|
||||
@@ -7,6 +8,8 @@ use crate::message_prelude::*;
|
||||
use crate::viewport_tools::tool_message_handler::ToolMessageHandler;
|
||||
use crate::workspace::WorkspaceMessageHandler;
|
||||
|
||||
use graphene::layers::text_layer::Font;
|
||||
|
||||
use std::collections::VecDeque;
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
@@ -42,8 +45,6 @@ const SIDE_EFFECT_FREE_MESSAGES: &[MessageDiscriminant] = &[
|
||||
MessageDiscriminant::Portfolio(PortfolioMessageDiscriminant::Document(DocumentMessageDiscriminant::FolderChanged)),
|
||||
MessageDiscriminant::Portfolio(PortfolioMessageDiscriminant::Document(DocumentMessageDiscriminant::DocumentStructureChanged)),
|
||||
MessageDiscriminant::Frontend(FrontendMessageDiscriminant::UpdateDocumentLayerTreeStructure),
|
||||
MessageDiscriminant::Frontend(FrontendMessageDiscriminant::UpdateActiveDocument),
|
||||
MessageDiscriminant::Frontend(FrontendMessageDiscriminant::UpdateOpenDocumentsList),
|
||||
MessageDiscriminant::Frontend(FrontendMessageDiscriminant::TriggerFontLoad),
|
||||
MessageDiscriminant::Broadcast(BroadcastMessageDiscriminant::TriggerSignal(BroadcastSignalDiscriminant::DocumentIsDirty)),
|
||||
];
|
||||
@@ -87,6 +88,18 @@ impl Dispatcher {
|
||||
match message {
|
||||
#[remain::unsorted]
|
||||
NoOp => {}
|
||||
#[remain::unsorted]
|
||||
Init => {
|
||||
// Display the menu bar at the top of the window
|
||||
let message = MenuBarMessage::SendLayout.into();
|
||||
queue.push_back(message);
|
||||
|
||||
// Load the default font
|
||||
let font = Font::new(DEFAULT_FONT_FAMILY.into(), DEFAULT_FONT_STYLE.into());
|
||||
let message = FrontendMessage::TriggerFontLoad { font, is_default: true }.into();
|
||||
queue.push_back(message);
|
||||
}
|
||||
|
||||
Broadcast(message) => self.message_handlers.broadcast_message_handler.process_action(message, (), &mut queue),
|
||||
Dialog(message) => {
|
||||
self.message_handlers
|
||||
@@ -94,14 +107,16 @@ impl Dispatcher {
|
||||
.process_action(message, &self.message_handlers.portfolio_message_handler, &mut queue);
|
||||
}
|
||||
Frontend(message) => {
|
||||
// Image and font loading should be immediately handled
|
||||
if let FrontendMessage::UpdateImageData { .. } | FrontendMessage::TriggerFontLoad { .. } = message {
|
||||
// Handle these messages immediately by returning early
|
||||
if let FrontendMessage::UpdateImageData { .. } | FrontendMessage::TriggerFontLoad { .. } | FrontendMessage::TriggerRefreshBoundsOfViewports = message {
|
||||
self.responses.push(message);
|
||||
return;
|
||||
}
|
||||
|
||||
// `FrontendMessage`s are saved and will be sent to the frontend after the message queue is done being processed
|
||||
self.responses.push(message);
|
||||
// Return early to avoid running the code after the match block
|
||||
return;
|
||||
} else {
|
||||
// `FrontendMessage`s are saved and will be sent to the frontend after the message queue is done being processed
|
||||
self.responses.push(message);
|
||||
}
|
||||
}
|
||||
Global(message) => {
|
||||
self.message_handlers.global_message_handler.process_action(message, (), &mut queue);
|
||||
@@ -122,15 +137,19 @@ impl Dispatcher {
|
||||
.process_action(message, &self.message_handlers.input_preprocessor_message_handler, &mut queue);
|
||||
}
|
||||
Tool(message) => {
|
||||
self.message_handlers.tool_message_handler.process_action(
|
||||
message,
|
||||
(
|
||||
self.message_handlers.portfolio_message_handler.active_document(),
|
||||
&self.message_handlers.input_preprocessor_message_handler,
|
||||
self.message_handlers.portfolio_message_handler.font_cache(),
|
||||
),
|
||||
&mut queue,
|
||||
);
|
||||
if let Some(document) = self.message_handlers.portfolio_message_handler.active_document() {
|
||||
self.message_handlers.tool_message_handler.process_action(
|
||||
message,
|
||||
(
|
||||
document,
|
||||
&self.message_handlers.input_preprocessor_message_handler,
|
||||
self.message_handlers.portfolio_message_handler.font_cache(),
|
||||
),
|
||||
&mut queue,
|
||||
);
|
||||
} else {
|
||||
log::warn!("Called ToolMessage without an active document.\nGot {:?}", message);
|
||||
}
|
||||
}
|
||||
Workspace(message) => {
|
||||
self.message_handlers
|
||||
@@ -153,7 +172,9 @@ impl Dispatcher {
|
||||
list.extend(self.message_handlers.input_preprocessor_message_handler.actions());
|
||||
list.extend(self.message_handlers.input_mapper_message_handler.actions());
|
||||
list.extend(self.message_handlers.global_message_handler.actions());
|
||||
list.extend(self.message_handlers.tool_message_handler.actions());
|
||||
if self.message_handlers.portfolio_message_handler.active_document().is_some() {
|
||||
list.extend(self.message_handlers.tool_message_handler.actions());
|
||||
}
|
||||
list.extend(self.message_handlers.portfolio_message_handler.actions());
|
||||
list
|
||||
}
|
||||
@@ -192,6 +213,7 @@ mod test {
|
||||
set_uuid_seed(0);
|
||||
let mut editor = Editor::new();
|
||||
|
||||
editor.new_document();
|
||||
editor.select_primary_color(Color::RED);
|
||||
editor.draw_rect(100., 200., 300., 400.);
|
||||
editor.select_primary_color(Color::BLUE);
|
||||
@@ -211,14 +233,14 @@ mod test {
|
||||
init_logger();
|
||||
let mut editor = create_editor_with_three_layers();
|
||||
|
||||
let document_before_copy = editor.dispatcher.message_handlers.portfolio_message_handler.active_document().graphene_document.clone();
|
||||
let document_before_copy = editor.dispatcher.message_handlers.portfolio_message_handler.active_document().unwrap().graphene_document.clone();
|
||||
editor.handle_message(PortfolioMessage::Copy { clipboard: Clipboard::Internal });
|
||||
editor.handle_message(PortfolioMessage::PasteIntoFolder {
|
||||
clipboard: Clipboard::Internal,
|
||||
folder_path: vec![],
|
||||
insert_index: -1,
|
||||
});
|
||||
let document_after_copy = editor.dispatcher.message_handlers.portfolio_message_handler.active_document().graphene_document.clone();
|
||||
let document_after_copy = editor.dispatcher.message_handlers.portfolio_message_handler.active_document().unwrap().graphene_document.clone();
|
||||
|
||||
let layers_before_copy = document_before_copy.root.as_folder().unwrap().layers();
|
||||
let layers_after_copy = document_after_copy.root.as_folder().unwrap().layers();
|
||||
@@ -245,7 +267,7 @@ mod test {
|
||||
init_logger();
|
||||
let mut editor = create_editor_with_three_layers();
|
||||
|
||||
let document_before_copy = editor.dispatcher.message_handlers.portfolio_message_handler.active_document().graphene_document.clone();
|
||||
let document_before_copy = editor.dispatcher.message_handlers.portfolio_message_handler.active_document().unwrap().graphene_document.clone();
|
||||
let shape_id = document_before_copy.root.as_folder().unwrap().layer_ids[1];
|
||||
|
||||
editor.handle_message(DocumentMessage::SetSelectedLayers {
|
||||
@@ -258,7 +280,7 @@ mod test {
|
||||
insert_index: -1,
|
||||
});
|
||||
|
||||
let document_after_copy = editor.dispatcher.message_handlers.portfolio_message_handler.active_document().graphene_document.clone();
|
||||
let document_after_copy = editor.dispatcher.message_handlers.portfolio_message_handler.active_document().unwrap().graphene_document.clone();
|
||||
|
||||
let layers_before_copy = document_before_copy.root.as_folder().unwrap().layers();
|
||||
let layers_after_copy = document_after_copy.root.as_folder().unwrap().layers();
|
||||
@@ -290,7 +312,7 @@ mod test {
|
||||
|
||||
editor.handle_message(DocumentMessage::CreateEmptyFolder { container_path: vec![] });
|
||||
|
||||
let document_before_added_shapes = editor.dispatcher.message_handlers.portfolio_message_handler.active_document().graphene_document.clone();
|
||||
let document_before_added_shapes = editor.dispatcher.message_handlers.portfolio_message_handler.active_document().unwrap().graphene_document.clone();
|
||||
let folder_id = document_before_added_shapes.root.as_folder().unwrap().layer_ids[FOLDER_INDEX];
|
||||
|
||||
// TODO: This adding of a Line and Pen should be rewritten using the corresponding functions in EditorTestUtils.
|
||||
@@ -314,7 +336,7 @@ mod test {
|
||||
replacement_selected_layers: vec![vec![folder_id]],
|
||||
});
|
||||
|
||||
let document_before_copy = editor.dispatcher.message_handlers.portfolio_message_handler.active_document().graphene_document.clone();
|
||||
let document_before_copy = editor.dispatcher.message_handlers.portfolio_message_handler.active_document().unwrap().graphene_document.clone();
|
||||
|
||||
editor.handle_message(PortfolioMessage::Copy { clipboard: Clipboard::Internal });
|
||||
editor.handle_message(DocumentMessage::DeleteSelectedLayers);
|
||||
@@ -329,7 +351,7 @@ mod test {
|
||||
insert_index: -1,
|
||||
});
|
||||
|
||||
let document_after_copy = editor.dispatcher.message_handlers.portfolio_message_handler.active_document().graphene_document.clone();
|
||||
let document_after_copy = editor.dispatcher.message_handlers.portfolio_message_handler.active_document().unwrap().graphene_document.clone();
|
||||
|
||||
let layers_before_copy = document_before_copy.root.as_folder().unwrap().layers();
|
||||
let layers_after_copy = document_after_copy.root.as_folder().unwrap().layers();
|
||||
@@ -380,7 +402,7 @@ mod test {
|
||||
const SHAPE_INDEX: usize = 1;
|
||||
const RECT_INDEX: usize = 0;
|
||||
|
||||
let document_before_copy = editor.dispatcher.message_handlers.portfolio_message_handler.active_document().graphene_document.clone();
|
||||
let document_before_copy = editor.dispatcher.message_handlers.portfolio_message_handler.active_document().unwrap().graphene_document.clone();
|
||||
let rect_id = document_before_copy.root.as_folder().unwrap().layer_ids[RECT_INDEX];
|
||||
let ellipse_id = document_before_copy.root.as_folder().unwrap().layer_ids[ELLIPSE_INDEX];
|
||||
|
||||
@@ -401,7 +423,7 @@ mod test {
|
||||
insert_index: -1,
|
||||
});
|
||||
|
||||
let document_after_copy = editor.dispatcher.message_handlers.portfolio_message_handler.active_document().graphene_document.clone();
|
||||
let document_after_copy = editor.dispatcher.message_handlers.portfolio_message_handler.active_document().unwrap().graphene_document.clone();
|
||||
|
||||
let layers_before_copy = document_before_copy.root.as_folder().unwrap().layers();
|
||||
let layers_after_copy = document_after_copy.root.as_folder().unwrap().layers();
|
||||
@@ -431,7 +453,7 @@ mod test {
|
||||
fn map_to_vec(paths: Vec<&[LayerId]>) -> Vec<Vec<LayerId>> {
|
||||
paths.iter().map(|layer| layer.to_vec()).collect::<Vec<_>>()
|
||||
}
|
||||
let sorted_layers = map_to_vec(editor.dispatcher.message_handlers.portfolio_message_handler.active_document().all_layers_sorted());
|
||||
let sorted_layers = map_to_vec(editor.dispatcher.message_handlers.portfolio_message_handler.active_document().unwrap().all_layers_sorted());
|
||||
println!("Sorted layers: {:?}", sorted_layers);
|
||||
|
||||
let verify_order = |handler: &mut DocumentMessageHandler| {
|
||||
@@ -447,15 +469,15 @@ mod test {
|
||||
});
|
||||
|
||||
editor.handle_message(DocumentMessage::ReorderSelectedLayers { relative_index_offset: 1 });
|
||||
let (all, non_selected, selected) = verify_order(editor.dispatcher.message_handlers.portfolio_message_handler.active_document_mut());
|
||||
let (all, non_selected, selected) = verify_order(editor.dispatcher.message_handlers.portfolio_message_handler.active_document_mut().unwrap());
|
||||
assert_eq!(all, non_selected.into_iter().chain(selected.into_iter()).collect::<Vec<_>>());
|
||||
|
||||
editor.handle_message(DocumentMessage::ReorderSelectedLayers { relative_index_offset: -1 });
|
||||
let (all, non_selected, selected) = verify_order(editor.dispatcher.message_handlers.portfolio_message_handler.active_document_mut());
|
||||
let (all, non_selected, selected) = verify_order(editor.dispatcher.message_handlers.portfolio_message_handler.active_document_mut().unwrap());
|
||||
assert_eq!(all, selected.into_iter().chain(non_selected.into_iter()).collect::<Vec<_>>());
|
||||
|
||||
editor.handle_message(DocumentMessage::ReorderSelectedLayers { relative_index_offset: isize::MAX });
|
||||
let (all, non_selected, selected) = verify_order(editor.dispatcher.message_handlers.portfolio_message_handler.active_document_mut());
|
||||
let (all, non_selected, selected) = verify_order(editor.dispatcher.message_handlers.portfolio_message_handler.active_document_mut().unwrap());
|
||||
assert_eq!(all, non_selected.into_iter().chain(selected.into_iter()).collect::<Vec<_>>());
|
||||
}
|
||||
|
||||
|
||||
@@ -22,6 +22,8 @@ where
|
||||
pub enum Message {
|
||||
#[remain::unsorted]
|
||||
NoOp,
|
||||
#[remain::unsorted]
|
||||
Init,
|
||||
#[child]
|
||||
Broadcast(BroadcastMessage),
|
||||
#[child]
|
||||
|
||||
@@ -18,7 +18,7 @@ pub enum DialogMessage {
|
||||
// Messages
|
||||
CloseAllDocumentsWithConfirmation,
|
||||
CloseDialogAndThen {
|
||||
followup: Box<Message>,
|
||||
followups: Vec<Message>,
|
||||
},
|
||||
DisplayDialogError {
|
||||
title: String,
|
||||
|
||||
@@ -25,9 +25,11 @@ impl MessageHandler<DialogMessage, &PortfolioMessageHandler> for DialogMessageHa
|
||||
dialog.register_properties(responses, LayoutTarget::DialogDetails);
|
||||
responses.push_back(FrontendMessage::DisplayDialog { icon: "Copy".to_string() }.into());
|
||||
}
|
||||
DialogMessage::CloseDialogAndThen { followup } => {
|
||||
DialogMessage::CloseDialogAndThen { followups } => {
|
||||
responses.push_back(FrontendMessage::DisplayDialogDismiss.into());
|
||||
responses.push_back(*followup);
|
||||
for message in followups.into_iter() {
|
||||
responses.push_back(message);
|
||||
}
|
||||
}
|
||||
DialogMessage::DisplayDialogError { title, description } => {
|
||||
let dialog = dialogs::Error { title, description };
|
||||
@@ -54,36 +56,38 @@ impl MessageHandler<DialogMessage, &PortfolioMessageHandler> for DialogMessageHa
|
||||
responses.push_back(FrontendMessage::DisplayDialog { icon: "Warning".to_string() }.into());
|
||||
}
|
||||
DialogMessage::RequestExportDialog => {
|
||||
let artboard_handler = &portfolio.active_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();
|
||||
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 = Export {
|
||||
file_name: portfolio.active_document().name.clone(),
|
||||
scale_factor: 1.,
|
||||
artboards,
|
||||
has_selection: portfolio.active_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());
|
||||
self.export_dialog = Export {
|
||||
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 = NewDocument {
|
||||
@@ -97,5 +101,9 @@ impl MessageHandler<DialogMessage, &PortfolioMessageHandler> for DialogMessageHa
|
||||
}
|
||||
}
|
||||
|
||||
advertise_actions!(DialogMessageDiscriminant;RequestNewDocumentDialog,RequestExportDialog,CloseAllDocumentsWithConfirmation);
|
||||
advertise_actions!(DialogMessageDiscriminant;
|
||||
RequestNewDocumentDialog,
|
||||
RequestExportDialog,
|
||||
CloseAllDocumentsWithConfirmation,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ impl PropertyHolder for CloseAllDocuments {
|
||||
min_width: 96,
|
||||
on_update: WidgetCallback::new(|_| {
|
||||
DialogMessage::CloseDialogAndThen {
|
||||
followup: Box::new(PortfolioMessage::CloseAllDocuments.into()),
|
||||
followups: vec![PortfolioMessage::CloseAllDocuments.into()],
|
||||
}
|
||||
.into()
|
||||
}),
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use crate::layout::widgets::*;
|
||||
use crate::message_prelude::{DialogMessage, DocumentMessage, FrontendMessage, PortfolioMessage};
|
||||
use crate::message_prelude::*;
|
||||
|
||||
/// A dialog for confirming the closing a document with unsaved changes.
|
||||
pub struct CloseDocument {
|
||||
@@ -18,7 +18,7 @@ impl PropertyHolder for CloseDocument {
|
||||
emphasized: true,
|
||||
on_update: WidgetCallback::new(|_| {
|
||||
DialogMessage::CloseDialogAndThen {
|
||||
followup: Box::new(DocumentMessage::SaveDocument.into()),
|
||||
followups: vec![DocumentMessage::SaveDocument.into()],
|
||||
}
|
||||
.into()
|
||||
}),
|
||||
@@ -29,7 +29,7 @@ impl PropertyHolder for CloseDocument {
|
||||
min_width: 96,
|
||||
on_update: WidgetCallback::new(move |_| {
|
||||
DialogMessage::CloseDialogAndThen {
|
||||
followup: Box::new(PortfolioMessage::CloseDocument { document_id }.into()),
|
||||
followups: vec![BroadcastSignal::ToolAbort.into(), PortfolioMessage::CloseDocument { document_id }.into()],
|
||||
}
|
||||
.into()
|
||||
}),
|
||||
|
||||
@@ -33,6 +33,7 @@ impl PropertyHolder for Export {
|
||||
WidgetHolder::new(Widget::TextInput(TextInput {
|
||||
value: self.file_name.clone(),
|
||||
on_update: WidgetCallback::new(|text_input: &TextInput| ExportDialogUpdate::FileName(text_input.value.clone()).into()),
|
||||
..Default::default()
|
||||
})),
|
||||
];
|
||||
|
||||
@@ -123,7 +124,7 @@ impl PropertyHolder for Export {
|
||||
emphasized: true,
|
||||
on_update: WidgetCallback::new(|_| {
|
||||
DialogMessage::CloseDialogAndThen {
|
||||
followup: Box::new(ExportDialogUpdate::Submit.into()),
|
||||
followups: vec![ExportDialogUpdate::Submit.into()],
|
||||
}
|
||||
.into()
|
||||
}),
|
||||
|
||||
@@ -34,6 +34,7 @@ impl PropertyHolder for NewDocument {
|
||||
WidgetHolder::new(Widget::TextInput(TextInput {
|
||||
value: self.name.clone(),
|
||||
on_update: WidgetCallback::new(|text_input: &TextInput| NewDocumentDialogUpdate::Name(text_input.value.clone()).into()),
|
||||
..Default::default()
|
||||
})),
|
||||
];
|
||||
|
||||
@@ -98,7 +99,7 @@ impl PropertyHolder for NewDocument {
|
||||
emphasized: true,
|
||||
on_update: WidgetCallback::new(|_| {
|
||||
DialogMessage::CloseDialogAndThen {
|
||||
followup: Box::new(NewDocumentDialogUpdate::Submit.into()),
|
||||
followups: vec![NewDocumentDialogUpdate::Submit.into()],
|
||||
}
|
||||
.into()
|
||||
}),
|
||||
@@ -131,9 +132,6 @@ pub enum NewDocumentDialogUpdate {
|
||||
DimensionsY(f64),
|
||||
|
||||
Submit,
|
||||
BufferArtboard,
|
||||
AddArtboard,
|
||||
FitCanvas,
|
||||
}
|
||||
|
||||
impl MessageHandler<NewDocumentDialogUpdate, ()> for NewDocument {
|
||||
@@ -147,27 +145,18 @@ impl MessageHandler<NewDocumentDialogUpdate, ()> for NewDocument {
|
||||
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());
|
||||
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());
|
||||
}
|
||||
}
|
||||
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);
|
||||
|
||||
@@ -552,8 +552,9 @@ impl DocumentMessageHandler {
|
||||
on_update: WidgetCallback::new(|optional_input: &OptionalInput| DocumentMessage::SetSnapping { snap: optional_input.checked }.into()),
|
||||
})),
|
||||
WidgetHolder::new(Widget::PopoverButton(PopoverButton {
|
||||
title: "Snapping".into(),
|
||||
header: "Snapping".into(),
|
||||
text: "The contents of this popover menu are coming soon".into(),
|
||||
..Default::default()
|
||||
})),
|
||||
WidgetHolder::new(Widget::Separator(Separator {
|
||||
separator_type: SeparatorType::Unrelated,
|
||||
@@ -566,8 +567,9 @@ impl DocumentMessageHandler {
|
||||
on_update: WidgetCallback::new(|_| DialogMessage::RequestComingSoonDialog { issue: Some(318) }.into()),
|
||||
})),
|
||||
WidgetHolder::new(Widget::PopoverButton(PopoverButton {
|
||||
title: "Grid".into(),
|
||||
header: "Grid".into(),
|
||||
text: "The contents of this popover menu are coming soon".into(),
|
||||
..Default::default()
|
||||
})),
|
||||
WidgetHolder::new(Widget::Separator(Separator {
|
||||
separator_type: SeparatorType::Unrelated,
|
||||
@@ -580,8 +582,9 @@ impl DocumentMessageHandler {
|
||||
on_update: WidgetCallback::new(|optional_input: &OptionalInput| DocumentMessage::SetOverlaysVisibility { visible: optional_input.checked }.into()),
|
||||
})),
|
||||
WidgetHolder::new(Widget::PopoverButton(PopoverButton {
|
||||
title: "Overlays".into(),
|
||||
header: "Overlays".into(),
|
||||
text: "The contents of this popover menu are coming soon".into(),
|
||||
..Default::default()
|
||||
})),
|
||||
WidgetHolder::new(Widget::Separator(Separator {
|
||||
separator_type: SeparatorType::Unrelated,
|
||||
@@ -614,8 +617,9 @@ impl DocumentMessageHandler {
|
||||
],
|
||||
})),
|
||||
WidgetHolder::new(Widget::PopoverButton(PopoverButton {
|
||||
title: "View Mode".into(),
|
||||
header: "View Mode".into(),
|
||||
text: "The contents of this popover menu are coming soon".into(),
|
||||
..Default::default()
|
||||
})),
|
||||
WidgetHolder::new(Widget::Separator(Separator {
|
||||
separator_type: SeparatorType::Section,
|
||||
|
||||
@@ -221,6 +221,7 @@ impl MessageHandler<MovementMessage, (&Document, &InputPreprocessorMessageHandle
|
||||
responses.push_back(SetCanvasZoom { zoom_factor: self.zoom }.into());
|
||||
}
|
||||
}
|
||||
|
||||
self.mouse_position = ipp.mouse.position;
|
||||
}
|
||||
RotateCanvasBegin => {
|
||||
|
||||
@@ -45,6 +45,8 @@ impl MessageHandler<OverlaysMessage, (bool, &FontCache, &InputPreprocessorMessag
|
||||
}
|
||||
|
||||
fn actions(&self) -> ActionList {
|
||||
actions!(OverlaysMessageDiscriminant; ClearAllOverlays)
|
||||
actions!(OverlaysMessageDiscriminant;
|
||||
ClearAllOverlays,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,6 +37,7 @@ pub enum PortfolioMessage {
|
||||
Cut {
|
||||
clipboard: Clipboard,
|
||||
},
|
||||
DestroyAllDocuments,
|
||||
FontLoaded {
|
||||
font_family: String,
|
||||
font_style: String,
|
||||
@@ -48,7 +49,6 @@ pub enum PortfolioMessage {
|
||||
font: Font,
|
||||
is_default: bool,
|
||||
},
|
||||
NewDocument,
|
||||
NewDocumentWithName {
|
||||
name: String,
|
||||
},
|
||||
|
||||
@@ -15,23 +15,23 @@ use graphene::Operation as DocumentOperation;
|
||||
use log::warn;
|
||||
use std::collections::{HashMap, VecDeque};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct PortfolioMessageHandler {
|
||||
menu_bar_message_handler: MenuBarMessageHandler,
|
||||
documents: HashMap<u64, DocumentMessageHandler>,
|
||||
document_ids: Vec<u64>,
|
||||
active_document_id: u64,
|
||||
active_document_id: Option<u64>,
|
||||
copy_buffer: [Vec<CopyBufferEntry>; INTERNAL_CLIPBOARD_COUNT as usize],
|
||||
font_cache: FontCache,
|
||||
}
|
||||
|
||||
impl PortfolioMessageHandler {
|
||||
pub fn active_document(&self) -> &DocumentMessageHandler {
|
||||
self.documents.get(&self.active_document_id).unwrap()
|
||||
pub fn active_document(&self) -> Option<&DocumentMessageHandler> {
|
||||
self.active_document_id.and_then(|id| self.documents.get(&id))
|
||||
}
|
||||
|
||||
pub fn active_document_mut(&mut self) -> &mut DocumentMessageHandler {
|
||||
self.documents.get_mut(&self.active_document_id).unwrap()
|
||||
pub fn active_document_mut(&mut self) -> Option<&mut DocumentMessageHandler> {
|
||||
self.active_document_id.and_then(|id| self.documents.get_mut(&id))
|
||||
}
|
||||
|
||||
pub fn generate_new_document_name(&self) -> String {
|
||||
@@ -56,21 +56,8 @@ impl PortfolioMessageHandler {
|
||||
}
|
||||
|
||||
// TODO Fix how this doesn't preserve tab order upon loading new document from *File > Load*
|
||||
fn load_document(&mut self, new_document: DocumentMessageHandler, document_id: u64, replace_first_empty: bool, responses: &mut VecDeque<Message>) {
|
||||
// Special case when loading a document on an empty page
|
||||
if replace_first_empty && self.active_document().is_unmodified_default() {
|
||||
responses.push_back(BroadcastSignal::ToolAbort.into());
|
||||
responses.push_back(PortfolioMessage::CloseDocument { document_id: self.active_document_id }.into());
|
||||
|
||||
let active_document_index = self
|
||||
.document_ids
|
||||
.iter()
|
||||
.position(|id| self.active_document_id == *id)
|
||||
.expect("Did not find matching active document id");
|
||||
self.document_ids.insert(active_document_index + 1, document_id);
|
||||
} else {
|
||||
self.document_ids.push(document_id);
|
||||
}
|
||||
fn load_document(&mut self, new_document: DocumentMessageHandler, document_id: u64, responses: &mut VecDeque<Message>) {
|
||||
self.document_ids.push(document_id);
|
||||
|
||||
responses.extend(
|
||||
new_document
|
||||
@@ -86,9 +73,19 @@ impl PortfolioMessageHandler {
|
||||
|
||||
self.documents.insert(document_id, new_document);
|
||||
|
||||
responses.push_back(PortfolioMessage::UpdateOpenDocumentsList.into());
|
||||
if self.active_document().is_some() {
|
||||
responses.push_back(PropertiesPanelMessage::Deactivate.into());
|
||||
responses.push_back(BroadcastSignal::ToolAbort.into());
|
||||
responses.push_back(ToolMessage::DeactivateTools.into());
|
||||
}
|
||||
|
||||
responses.push_back(PortfolioMessage::UpdateOpenDocumentsList.into());
|
||||
responses.push_back(PortfolioMessage::SelectDocument { document_id }.into());
|
||||
responses.push_back(PortfolioMessage::UpdateDocumentWidgets.into());
|
||||
responses.push_back(ToolMessage::InitTools.into());
|
||||
responses.push_back(PropertiesPanelMessage::Init.into());
|
||||
responses.push_back(MovementMessage::TranslateCanvas { delta: (0., 0.).into() }.into());
|
||||
responses.push_back(DocumentMessage::DocumentStructureChanged.into())
|
||||
}
|
||||
|
||||
/// Returns an iterator over the open documents in order.
|
||||
@@ -105,25 +102,6 @@ impl PortfolioMessageHandler {
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for PortfolioMessageHandler {
|
||||
fn default() -> Self {
|
||||
let mut documents_map: HashMap<u64, DocumentMessageHandler> = HashMap::with_capacity(1);
|
||||
let starting_key = generate_uuid();
|
||||
documents_map.insert(starting_key, DocumentMessageHandler::default());
|
||||
|
||||
const EMPTY_VEC: Vec<CopyBufferEntry> = vec![];
|
||||
|
||||
Self {
|
||||
documents: documents_map,
|
||||
document_ids: vec![starting_key],
|
||||
copy_buffer: [EMPTY_VEC; INTERNAL_CLIPBOARD_COUNT as usize],
|
||||
active_document_id: starting_key,
|
||||
font_cache: Default::default(),
|
||||
menu_bar_message_handler: MenuBarMessageHandler::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl MessageHandler<PortfolioMessage, &InputPreprocessorMessageHandler> for PortfolioMessageHandler {
|
||||
#[remain::check]
|
||||
fn process_action(&mut self, message: PortfolioMessage, ipp: &InputPreprocessorMessageHandler, responses: &mut VecDeque<Message>) {
|
||||
@@ -134,12 +112,20 @@ impl MessageHandler<PortfolioMessage, &InputPreprocessorMessageHandler> for Port
|
||||
match message {
|
||||
// Sub-messages
|
||||
#[remain::unsorted]
|
||||
Document(message) => self.documents.get_mut(&self.active_document_id).unwrap().process_action(message, (ipp, &self.font_cache), responses),
|
||||
Document(message) => {
|
||||
if let Some(document) = self.active_document_id.and_then(|id| self.documents.get_mut(&id)) {
|
||||
document.process_action(message, (ipp, &self.font_cache), responses)
|
||||
}
|
||||
}
|
||||
#[remain::unsorted]
|
||||
MenuBar(message) => self.menu_bar_message_handler.process_action(message, (), responses),
|
||||
|
||||
// Messages
|
||||
AutoSaveActiveDocument => responses.push_back(PortfolioMessage::AutoSaveDocument { document_id: self.active_document_id }.into()),
|
||||
AutoSaveActiveDocument => {
|
||||
if let Some(document_id) = self.active_document_id {
|
||||
responses.push_back(PortfolioMessage::AutoSaveDocument { document_id }.into());
|
||||
}
|
||||
}
|
||||
AutoSaveDocument { document_id } => {
|
||||
let document = self.documents.get(&document_id).unwrap();
|
||||
responses.push_back(
|
||||
@@ -156,54 +142,60 @@ impl MessageHandler<PortfolioMessage, &InputPreprocessorMessageHandler> for Port
|
||||
)
|
||||
}
|
||||
CloseActiveDocumentWithConfirmation => {
|
||||
responses.push_back(PortfolioMessage::CloseDocumentWithConfirmation { document_id: self.active_document_id }.into());
|
||||
if let Some(document_id) = self.active_document_id {
|
||||
responses.push_back(PortfolioMessage::CloseDocumentWithConfirmation { document_id }.into());
|
||||
}
|
||||
}
|
||||
CloseAllDocuments => {
|
||||
// Empty the list of internal document data
|
||||
self.documents.clear();
|
||||
self.document_ids.clear();
|
||||
if self.active_document_id.is_some() {
|
||||
responses.push_back(PropertiesPanelMessage::Deactivate.into());
|
||||
responses.push_back(BroadcastSignal::ToolAbort.into());
|
||||
responses.push_back(ToolMessage::DeactivateTools.into());
|
||||
}
|
||||
|
||||
// Clear out all documents and make a new default document
|
||||
let new_document_id = generate_uuid();
|
||||
self.documents.insert(new_document_id, DocumentMessageHandler::default());
|
||||
self.document_ids.push(new_document_id);
|
||||
self.active_document_id = new_document_id;
|
||||
for document_id in &self.document_ids {
|
||||
responses.push_back(FrontendMessage::TriggerIndexedDbRemoveDocument { document_id: *document_id }.into());
|
||||
}
|
||||
|
||||
responses.push_back(BroadcastSignal::ToolAbort.into());
|
||||
responses.push_back(PortfolioMessage::DestroyAllDocuments.into());
|
||||
responses.push_back(PortfolioMessage::UpdateOpenDocumentsList.into());
|
||||
responses.push_back(PortfolioMessage::SelectDocument { document_id: new_document_id }.into())
|
||||
}
|
||||
CloseDocument { document_id } => {
|
||||
let document_index = self.document_index(document_id);
|
||||
self.documents.remove(&document_id);
|
||||
self.document_ids.remove(document_index);
|
||||
|
||||
// Last tab was closed, so create a new blank tab
|
||||
if self.document_ids.is_empty() {
|
||||
let new_id = generate_uuid();
|
||||
self.document_ids.push(new_id);
|
||||
self.documents.insert(new_id, DocumentMessageHandler::default());
|
||||
self.active_document_id = None;
|
||||
} else if Some(document_id) == self.active_document_id {
|
||||
if document_index == self.document_ids.len() {
|
||||
// If we closed the last document take the one previous (same as last)
|
||||
responses.push_back(
|
||||
PortfolioMessage::SelectDocument {
|
||||
document_id: *self.document_ids.last().unwrap(),
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
} else {
|
||||
// Move to the next tab
|
||||
responses.push_back(
|
||||
PortfolioMessage::SelectDocument {
|
||||
document_id: self.document_ids[document_index],
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
self.active_document_id = if document_id != self.active_document_id {
|
||||
// If we are not closing the active document, stay on it
|
||||
self.active_document_id
|
||||
} else if document_index >= self.document_ids.len() {
|
||||
// If we closed the last document take the one previous (same as last)
|
||||
*self.document_ids.last().unwrap()
|
||||
} else {
|
||||
// Move to the next tab
|
||||
self.document_ids[document_index]
|
||||
};
|
||||
|
||||
// Send the new list of document tab names
|
||||
responses.push_back(UpdateOpenDocumentsList.into());
|
||||
responses.push_back(FrontendMessage::UpdateActiveDocument { document_id: self.active_document_id }.into());
|
||||
responses.push_back(FrontendMessage::TriggerIndexedDbRemoveDocument { document_id }.into());
|
||||
responses.push_back(RenderDocument.into());
|
||||
responses.push_back(DocumentMessage::DocumentStructureChanged.into());
|
||||
for layer in self.active_document().layer_metadata.keys() {
|
||||
responses.push_back(DocumentMessage::LayerChanged { affected_layer_path: layer.clone() }.into());
|
||||
if let Some(document) = self.active_document() {
|
||||
for layer in document.layer_metadata.keys() {
|
||||
responses.push_back(DocumentMessage::LayerChanged { affected_layer_path: layer.clone() }.into());
|
||||
}
|
||||
}
|
||||
}
|
||||
CloseDocumentWithConfirmation { document_id } => {
|
||||
@@ -225,36 +217,42 @@ impl MessageHandler<PortfolioMessage, &InputPreprocessorMessageHandler> for Port
|
||||
}
|
||||
Copy { clipboard } => {
|
||||
// We can't use `self.active_document()` because it counts as an immutable borrow of the entirety of `self`
|
||||
let active_document = self.documents.get(&self.active_document_id).unwrap();
|
||||
|
||||
let copy_val = |buffer: &mut Vec<CopyBufferEntry>| {
|
||||
for layer_path in active_document.selected_layers_without_children() {
|
||||
match (active_document.graphene_document.layer(layer_path).map(|t| t.clone()), *active_document.layer_metadata(layer_path)) {
|
||||
(Ok(layer), layer_metadata) => {
|
||||
buffer.push(CopyBufferEntry { layer, layer_metadata });
|
||||
if let Some(active_document) = self.active_document_id.and_then(|id| self.documents.get(&id)) {
|
||||
let copy_val = |buffer: &mut Vec<CopyBufferEntry>| {
|
||||
for layer_path in active_document.selected_layers_without_children() {
|
||||
match (active_document.graphene_document.layer(layer_path).map(|t| t.clone()), *active_document.layer_metadata(layer_path)) {
|
||||
(Ok(layer), layer_metadata) => {
|
||||
buffer.push(CopyBufferEntry { layer, layer_metadata });
|
||||
}
|
||||
(Err(e), _) => warn!("Could not access selected layer {:?}: {:?}", layer_path, e),
|
||||
}
|
||||
(Err(e), _) => warn!("Could not access selected layer {:?}: {:?}", layer_path, e),
|
||||
}
|
||||
};
|
||||
|
||||
if clipboard == Clipboard::Device {
|
||||
let mut buffer = Vec::new();
|
||||
copy_val(&mut buffer);
|
||||
let mut copy_text = String::from("graphite/layer: ");
|
||||
copy_text += &serde_json::to_string(&buffer).expect("Could not serialize paste");
|
||||
|
||||
responses.push_back(FrontendMessage::TriggerTextCopy { copy_text }.into());
|
||||
} else {
|
||||
let copy_buffer = &mut self.copy_buffer;
|
||||
copy_buffer[clipboard as usize].clear();
|
||||
copy_val(&mut copy_buffer[clipboard as usize]);
|
||||
}
|
||||
};
|
||||
|
||||
if clipboard == Clipboard::Device {
|
||||
let mut buffer = Vec::new();
|
||||
copy_val(&mut buffer);
|
||||
let mut copy_text = String::from("graphite/layer: ");
|
||||
copy_text += &serde_json::to_string(&buffer).expect("Could not serialize paste");
|
||||
|
||||
responses.push_back(FrontendMessage::TriggerTextCopy { copy_text }.into());
|
||||
} else {
|
||||
let copy_buffer = &mut self.copy_buffer;
|
||||
copy_buffer[clipboard as usize].clear();
|
||||
copy_val(&mut copy_buffer[clipboard as usize]);
|
||||
}
|
||||
}
|
||||
Cut { clipboard } => {
|
||||
responses.push_back(Copy { clipboard }.into());
|
||||
responses.push_back(DeleteSelectedLayers.into());
|
||||
}
|
||||
DestroyAllDocuments => {
|
||||
// Empty the list of internal document data
|
||||
self.documents.clear();
|
||||
self.document_ids.clear();
|
||||
self.active_document_id = None;
|
||||
}
|
||||
FontLoaded {
|
||||
font_family,
|
||||
font_style,
|
||||
@@ -263,33 +261,35 @@ impl MessageHandler<PortfolioMessage, &InputPreprocessorMessageHandler> for Port
|
||||
is_default,
|
||||
} => {
|
||||
self.font_cache.insert(Font::new(font_family, font_style), preview_url, data, is_default);
|
||||
self.active_document_mut().graphene_document.mark_all_layers_of_type_as_dirty(LayerDataTypeDiscriminant::Text);
|
||||
responses.push_back(DocumentMessage::RenderDocument.into());
|
||||
|
||||
if let Some(document) = self.active_document_mut() {
|
||||
document.graphene_document.mark_all_layers_of_type_as_dirty(LayerDataTypeDiscriminant::Text);
|
||||
responses.push_back(DocumentMessage::RenderDocument.into());
|
||||
}
|
||||
}
|
||||
LoadFont { font, is_default } => {
|
||||
if !self.font_cache.loaded_font(&font) {
|
||||
responses.push_front(FrontendMessage::TriggerFontLoad { font, is_default }.into());
|
||||
}
|
||||
}
|
||||
NewDocument => {
|
||||
let name = self.generate_new_document_name();
|
||||
let new_document = DocumentMessageHandler::with_name(name, ipp);
|
||||
let document_id = generate_uuid();
|
||||
responses.push_back(BroadcastSignal::ToolAbort.into());
|
||||
self.load_document(new_document, document_id, false, responses);
|
||||
}
|
||||
NewDocumentWithName { name } => {
|
||||
let new_document = DocumentMessageHandler::with_name(name, ipp);
|
||||
let document_id = generate_uuid();
|
||||
responses.push_back(BroadcastSignal::ToolAbort.into());
|
||||
self.load_document(new_document, document_id, false, responses);
|
||||
if self.active_document().is_some() {
|
||||
responses.push_back(BroadcastSignal::ToolAbort.into());
|
||||
responses.push_back(MovementMessage::TranslateCanvas { delta: (0., 0.).into() }.into());
|
||||
}
|
||||
|
||||
self.load_document(new_document, document_id, responses);
|
||||
}
|
||||
NextDocument => {
|
||||
let current_index = self.document_index(self.active_document_id);
|
||||
let next_index = (current_index + 1) % self.document_ids.len();
|
||||
let next_id = self.document_ids[next_index];
|
||||
if let Some(active_document_id) = self.active_document_id {
|
||||
let current_index = self.document_index(active_document_id);
|
||||
let next_index = (current_index + 1) % self.document_ids.len();
|
||||
let next_id = self.document_ids[next_index];
|
||||
|
||||
responses.push_back(PortfolioMessage::SelectDocument { document_id: next_id }.into());
|
||||
responses.push_back(PortfolioMessage::SelectDocument { document_id: next_id }.into());
|
||||
}
|
||||
}
|
||||
OpenDocument => {
|
||||
responses.push_back(FrontendMessage::TriggerFileUpload.into());
|
||||
@@ -318,7 +318,7 @@ impl MessageHandler<PortfolioMessage, &InputPreprocessorMessageHandler> for Port
|
||||
match document {
|
||||
Ok(mut document) => {
|
||||
document.set_save_state(document_is_saved);
|
||||
self.load_document(document, document_id, true, responses);
|
||||
self.load_document(document, document_id, responses);
|
||||
}
|
||||
Err(e) => responses.push_back(
|
||||
DialogMessage::DisplayDialogError {
|
||||
@@ -330,22 +330,26 @@ impl MessageHandler<PortfolioMessage, &InputPreprocessorMessageHandler> for Port
|
||||
}
|
||||
}
|
||||
Paste { clipboard } => {
|
||||
let document = self.active_document();
|
||||
let shallowest_common_folder = document
|
||||
.graphene_document
|
||||
.shallowest_common_folder(document.selected_layers())
|
||||
.expect("While pasting, the selected layers did not exist while attempting to find the appropriate folder path for insertion");
|
||||
responses.push_back(DeselectAllLayers.into());
|
||||
responses.push_back(StartTransaction.into());
|
||||
responses.push_back(
|
||||
PasteIntoFolder {
|
||||
clipboard,
|
||||
folder_path: shallowest_common_folder.to_vec(),
|
||||
insert_index: -1,
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
responses.push_back(CommitTransaction.into());
|
||||
let shallowest_common_folder = self.active_document().map(|document| {
|
||||
document
|
||||
.graphene_document
|
||||
.shallowest_common_folder(document.selected_layers())
|
||||
.expect("While pasting, the selected layers did not exist while attempting to find the appropriate folder path for insertion")
|
||||
});
|
||||
|
||||
if let Some(folder) = shallowest_common_folder {
|
||||
responses.push_back(DeselectAllLayers.into());
|
||||
responses.push_back(StartTransaction.into());
|
||||
responses.push_back(
|
||||
PasteIntoFolder {
|
||||
clipboard,
|
||||
folder_path: folder.to_vec(),
|
||||
insert_index: -1,
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
responses.push_back(CommitTransaction.into());
|
||||
}
|
||||
}
|
||||
PasteIntoFolder {
|
||||
clipboard,
|
||||
@@ -353,26 +357,27 @@ impl MessageHandler<PortfolioMessage, &InputPreprocessorMessageHandler> for Port
|
||||
insert_index,
|
||||
} => {
|
||||
let paste = |entry: &CopyBufferEntry, responses: &mut VecDeque<_>| {
|
||||
log::trace!("Pasting into folder {:?} as index: {}", &path, insert_index);
|
||||
if let Some(document) = self.active_document() {
|
||||
log::trace!("Pasting into folder {:?} as index: {}", &path, insert_index);
|
||||
let destination_path = [path.to_vec(), vec![generate_uuid()]].concat();
|
||||
|
||||
let destination_path = [path.to_vec(), vec![generate_uuid()]].concat();
|
||||
|
||||
responses.push_front(
|
||||
DocumentMessage::UpdateLayerMetadata {
|
||||
layer_path: destination_path.clone(),
|
||||
layer_metadata: entry.layer_metadata,
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
self.active_document().load_layer_resources(responses, &entry.layer.data, destination_path.clone());
|
||||
responses.push_front(
|
||||
DocumentOperation::InsertLayer {
|
||||
layer: entry.layer.clone(),
|
||||
destination_path,
|
||||
insert_index,
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
responses.push_front(
|
||||
DocumentMessage::UpdateLayerMetadata {
|
||||
layer_path: destination_path.clone(),
|
||||
layer_metadata: entry.layer_metadata,
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
document.load_layer_resources(responses, &entry.layer.data, destination_path.clone());
|
||||
responses.push_front(
|
||||
DocumentOperation::InsertLayer {
|
||||
layer: entry.layer.clone(),
|
||||
destination_path,
|
||||
insert_index,
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
if insert_index == -1 {
|
||||
@@ -386,55 +391,69 @@ impl MessageHandler<PortfolioMessage, &InputPreprocessorMessageHandler> for Port
|
||||
}
|
||||
}
|
||||
PasteSerializedData { data } => {
|
||||
if let Ok(data) = serde_json::from_str::<Vec<CopyBufferEntry>>(&data) {
|
||||
let document = self.active_document();
|
||||
let shallowest_common_folder = document
|
||||
.graphene_document
|
||||
.shallowest_common_folder(document.selected_layers())
|
||||
.expect("While pasting from serialized, the selected layers did not exist while attempting to find the appropriate folder path for insertion");
|
||||
responses.push_back(DeselectAllLayers.into());
|
||||
responses.push_back(StartTransaction.into());
|
||||
if let Some(document) = self.active_document() {
|
||||
if let Ok(data) = serde_json::from_str::<Vec<CopyBufferEntry>>(&data) {
|
||||
let shallowest_common_folder = document
|
||||
.graphene_document
|
||||
.shallowest_common_folder(document.selected_layers())
|
||||
.expect("While pasting from serialized, the selected layers did not exist while attempting to find the appropriate folder path for insertion");
|
||||
responses.push_back(DeselectAllLayers.into());
|
||||
responses.push_back(StartTransaction.into());
|
||||
|
||||
for entry in data.iter().rev() {
|
||||
let destination_path = [shallowest_common_folder.to_vec(), vec![generate_uuid()]].concat();
|
||||
for entry in data.iter().rev() {
|
||||
let destination_path = [shallowest_common_folder.to_vec(), vec![generate_uuid()]].concat();
|
||||
|
||||
responses.push_front(
|
||||
DocumentMessage::UpdateLayerMetadata {
|
||||
layer_path: destination_path.clone(),
|
||||
layer_metadata: entry.layer_metadata,
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
self.active_document().load_layer_resources(responses, &entry.layer.data, destination_path.clone());
|
||||
responses.push_front(
|
||||
DocumentOperation::InsertLayer {
|
||||
layer: entry.layer.clone(),
|
||||
destination_path,
|
||||
insert_index: -1,
|
||||
responses.push_front(
|
||||
DocumentMessage::UpdateLayerMetadata {
|
||||
layer_path: destination_path.clone(),
|
||||
layer_metadata: entry.layer_metadata,
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
document.load_layer_resources(responses, &entry.layer.data, destination_path.clone());
|
||||
responses.push_front(
|
||||
DocumentOperation::InsertLayer {
|
||||
layer: entry.layer.clone(),
|
||||
destination_path,
|
||||
insert_index: -1,
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
|
||||
responses.push_back(CommitTransaction.into());
|
||||
}
|
||||
}
|
||||
}
|
||||
PrevDocument => {
|
||||
if let Some(active_document_id) = self.active_document_id {
|
||||
let len = self.document_ids.len();
|
||||
let current_index = self.document_index(active_document_id);
|
||||
let prev_index = (current_index + len - 1) % len;
|
||||
let prev_id = self.document_ids[prev_index];
|
||||
responses.push_back(PortfolioMessage::SelectDocument { document_id: prev_id }.into());
|
||||
}
|
||||
}
|
||||
SelectDocument { document_id } => {
|
||||
if let Some(document) = self.active_document() {
|
||||
if !document.is_saved() {
|
||||
// Safe to unwrap since we know that there is an active document
|
||||
responses.push_back(
|
||||
PortfolioMessage::AutoSaveDocument {
|
||||
document_id: self.active_document_id.unwrap(),
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
|
||||
responses.push_back(CommitTransaction.into());
|
||||
}
|
||||
}
|
||||
PrevDocument => {
|
||||
let len = self.document_ids.len();
|
||||
let current_index = self.document_index(self.active_document_id);
|
||||
let prev_index = (current_index + len - 1) % len;
|
||||
let prev_id = self.document_ids[prev_index];
|
||||
responses.push_back(PortfolioMessage::SelectDocument { document_id: prev_id }.into());
|
||||
}
|
||||
|
||||
SelectDocument { document_id } => {
|
||||
let active_document = self.active_document();
|
||||
if !active_document.is_saved() {
|
||||
responses.push_back(PortfolioMessage::AutoSaveDocument { document_id: self.active_document_id }.into());
|
||||
if self.active_document().is_some() {
|
||||
responses.push_back(BroadcastSignal::ToolAbort.into());
|
||||
}
|
||||
responses.push_back(BroadcastSignal::ToolAbort.into());
|
||||
|
||||
// TODO: Remove this message in favor of having tools have specific data per document instance
|
||||
responses.push_back(SetActiveDocument { document_id }.into());
|
||||
|
||||
responses.push_back(PortfolioMessage::UpdateOpenDocumentsList.into());
|
||||
responses.push_back(FrontendMessage::UpdateActiveDocument { document_id }.into());
|
||||
responses.push_back(RenderDocument.into());
|
||||
responses.push_back(DocumentMessage::DocumentStructureChanged.into());
|
||||
@@ -444,13 +463,13 @@ impl MessageHandler<PortfolioMessage, &InputPreprocessorMessageHandler> for Port
|
||||
responses.push_back(BroadcastSignal::SelectionChanged.into());
|
||||
responses.push_back(BroadcastSignal::DocumentIsDirty.into());
|
||||
responses.push_back(PortfolioMessage::UpdateDocumentWidgets.into());
|
||||
responses.push_back(MovementMessage::TranslateCanvas { delta: (0., 0.).into() }.into());
|
||||
}
|
||||
SetActiveDocument { document_id } => {
|
||||
self.active_document_id = document_id;
|
||||
}
|
||||
SetActiveDocument { document_id } => self.active_document_id = Some(document_id),
|
||||
UpdateDocumentWidgets => {
|
||||
let active_document = self.active_document();
|
||||
active_document.update_document_widgets(responses);
|
||||
if let Some(document) = self.active_document() {
|
||||
document.update_document_widgets(responses);
|
||||
}
|
||||
}
|
||||
UpdateOpenDocumentsList => {
|
||||
// Send the list of document tab names
|
||||
@@ -472,7 +491,6 @@ impl MessageHandler<PortfolioMessage, &InputPreprocessorMessageHandler> for Port
|
||||
|
||||
fn actions(&self) -> ActionList {
|
||||
let mut common = actions!(PortfolioMessageDiscriminant;
|
||||
NewDocument,
|
||||
CloseActiveDocumentWithConfirmation,
|
||||
CloseAllDocuments,
|
||||
NextDocument,
|
||||
@@ -481,14 +499,17 @@ impl MessageHandler<PortfolioMessage, &InputPreprocessorMessageHandler> for Port
|
||||
Paste,
|
||||
);
|
||||
|
||||
if self.active_document().layer_metadata.values().any(|data| data.selected) {
|
||||
let select = actions!(PortfolioMessageDiscriminant;
|
||||
Copy,
|
||||
Cut,
|
||||
);
|
||||
common.extend(select);
|
||||
if let Some(document) = self.active_document() {
|
||||
if document.layer_metadata.values().any(|data| data.selected) {
|
||||
let select = actions!(PortfolioMessageDiscriminant;
|
||||
Copy,
|
||||
Cut,
|
||||
);
|
||||
common.extend(select);
|
||||
}
|
||||
common.extend(document.actions());
|
||||
}
|
||||
common.extend(self.active_document().actions());
|
||||
|
||||
common
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ pub enum PropertiesPanelMessage {
|
||||
CheckSelectedWasDeleted { path: Vec<LayerId> },
|
||||
CheckSelectedWasUpdated { path: Vec<LayerId> },
|
||||
ClearSelection,
|
||||
Deactivate,
|
||||
Init,
|
||||
ModifyFill { fill: Fill },
|
||||
ModifyFont { font_family: String, font_style: String, size: f64 },
|
||||
|
||||
@@ -2,8 +2,8 @@ use super::utility_types::TargetDocument;
|
||||
use crate::document::properties_panel_message::TransformOp;
|
||||
use crate::layout::layout_message::LayoutTarget;
|
||||
use crate::layout::widgets::{
|
||||
ColorInput, FontInput, IconLabel, Layout, LayoutGroup, NumberInput, PopoverButton, RadioEntryData, RadioInput, Separator, SeparatorDirection, SeparatorType, TextAreaInput, TextInput, TextLabel,
|
||||
Widget, WidgetCallback, WidgetHolder, WidgetLayout,
|
||||
ColorInput, FontInput, IconLabel, IconStyle, Layout, LayoutGroup, NumberInput, PopoverButton, RadioEntryData, RadioInput, Separator, SeparatorDirection, SeparatorType, TextAreaInput, TextInput,
|
||||
TextLabel, Widget, WidgetCallback, WidgetHolder, WidgetLayout,
|
||||
};
|
||||
use crate::message_prelude::*;
|
||||
|
||||
@@ -156,6 +156,13 @@ impl<'a> MessageHandler<PropertiesPanelMessage, PropertiesPanelMessageHandlerDat
|
||||
);
|
||||
self.active_selection = None;
|
||||
}
|
||||
Deactivate => responses.push_back(
|
||||
BroadcastMessage::UnsubscribeSignal {
|
||||
on: BroadcastSignal::SelectionChanged,
|
||||
message: Box::new(PropertiesPanelMessage::UpdateSelectedDocumentProperties.into()),
|
||||
}
|
||||
.into(),
|
||||
),
|
||||
Init => responses.push_back(
|
||||
BroadcastMessage::SubscribeSignal {
|
||||
on: BroadcastSignal::SelectionChanged,
|
||||
@@ -262,7 +269,7 @@ fn register_artboard_layer_properties(layer: &Layer, responses: &mut VecDeque<Me
|
||||
widgets: vec![
|
||||
WidgetHolder::new(Widget::IconLabel(IconLabel {
|
||||
icon: "NodeArtboard".into(),
|
||||
gap_after: true,
|
||||
icon_style: IconStyle::Node,
|
||||
})),
|
||||
WidgetHolder::new(Widget::Separator(Separator {
|
||||
separator_type: SeparatorType::Related,
|
||||
@@ -279,14 +286,16 @@ fn register_artboard_layer_properties(layer: &Layer, responses: &mut VecDeque<Me
|
||||
WidgetHolder::new(Widget::TextInput(TextInput {
|
||||
value: layer.name.clone().unwrap_or_else(|| "Untitled".to_string()),
|
||||
on_update: WidgetCallback::new(|text_input: &TextInput| PropertiesPanelMessage::ModifyName { name: text_input.value.clone() }.into()),
|
||||
..Default::default()
|
||||
})),
|
||||
WidgetHolder::new(Widget::Separator(Separator {
|
||||
separator_type: SeparatorType::Related,
|
||||
direction: SeparatorDirection::Horizontal,
|
||||
})),
|
||||
WidgetHolder::new(Widget::PopoverButton(PopoverButton {
|
||||
title: "Options Bar".into(),
|
||||
header: "Options Bar".into(),
|
||||
text: "The contents of this popover menu are coming soon".into(),
|
||||
..Default::default()
|
||||
})),
|
||||
],
|
||||
}];
|
||||
@@ -414,7 +423,8 @@ fn register_artboard_layer_properties(layer: &Layer, responses: &mut VecDeque<Me
|
||||
PropertiesPanelMessage::ModifyFill { fill: Fill::None }.into()
|
||||
}
|
||||
}),
|
||||
can_set_transparent: false,
|
||||
no_transparency: true,
|
||||
..Default::default()
|
||||
})),
|
||||
],
|
||||
},
|
||||
@@ -444,19 +454,19 @@ fn register_artwork_layer_properties(layer: &Layer, responses: &mut VecDeque<Mes
|
||||
match &layer.data {
|
||||
LayerDataType::Folder(_) => WidgetHolder::new(Widget::IconLabel(IconLabel {
|
||||
icon: "NodeFolder".into(),
|
||||
gap_after: true,
|
||||
icon_style: IconStyle::Node,
|
||||
})),
|
||||
LayerDataType::Shape(_) => WidgetHolder::new(Widget::IconLabel(IconLabel {
|
||||
icon: "NodeShape".into(),
|
||||
gap_after: true,
|
||||
icon_style: IconStyle::Node,
|
||||
})),
|
||||
LayerDataType::Text(_) => WidgetHolder::new(Widget::IconLabel(IconLabel {
|
||||
icon: "NodeText".into(),
|
||||
gap_after: true,
|
||||
icon_style: IconStyle::Node,
|
||||
})),
|
||||
LayerDataType::Image(_) => WidgetHolder::new(Widget::IconLabel(IconLabel {
|
||||
icon: "NodeImage".into(),
|
||||
gap_after: true,
|
||||
icon_style: IconStyle::Node,
|
||||
})),
|
||||
},
|
||||
WidgetHolder::new(Widget::Separator(Separator {
|
||||
@@ -474,14 +484,16 @@ fn register_artwork_layer_properties(layer: &Layer, responses: &mut VecDeque<Mes
|
||||
WidgetHolder::new(Widget::TextInput(TextInput {
|
||||
value: layer.name.clone().unwrap_or_else(|| "Untitled".to_string()),
|
||||
on_update: WidgetCallback::new(|text_input: &TextInput| PropertiesPanelMessage::ModifyName { name: text_input.value.clone() }.into()),
|
||||
..Default::default()
|
||||
})),
|
||||
WidgetHolder::new(Widget::Separator(Separator {
|
||||
separator_type: SeparatorType::Related,
|
||||
direction: SeparatorDirection::Horizontal,
|
||||
})),
|
||||
WidgetHolder::new(Widget::PopoverButton(PopoverButton {
|
||||
title: "Options Bar".into(),
|
||||
header: "Options Bar".into(),
|
||||
text: "The contents of this popover menu are coming soon".into(),
|
||||
..Default::default()
|
||||
})),
|
||||
],
|
||||
}];
|
||||
@@ -704,6 +716,7 @@ fn node_section_font(layer: &TextLayer) -> LayoutGroup {
|
||||
WidgetHolder::new(Widget::TextAreaInput(TextAreaInput {
|
||||
value: layer.text.clone(),
|
||||
on_update: WidgetCallback::new(|text_area: &TextAreaInput| PropertiesPanelMessage::ModifyText { new_text: text_area.value.clone() }.into()),
|
||||
..Default::default()
|
||||
})),
|
||||
],
|
||||
},
|
||||
@@ -729,6 +742,7 @@ fn node_section_font(layer: &TextLayer) -> LayoutGroup {
|
||||
}
|
||||
.into()
|
||||
}),
|
||||
..Default::default()
|
||||
})),
|
||||
],
|
||||
},
|
||||
@@ -754,6 +768,7 @@ fn node_section_font(layer: &TextLayer) -> LayoutGroup {
|
||||
}
|
||||
.into()
|
||||
}),
|
||||
..Default::default()
|
||||
})),
|
||||
],
|
||||
},
|
||||
@@ -997,6 +1012,7 @@ fn node_section_stroke(stroke: &Stroke) -> LayoutGroup {
|
||||
.with_dash_lengths(&text_input.value)
|
||||
.map_or(PropertiesPanelMessage::ResendActiveProperties.into(), |stroke| PropertiesPanelMessage::ModifyStroke { stroke }.into())
|
||||
}),
|
||||
..Default::default()
|
||||
})),
|
||||
],
|
||||
},
|
||||
|
||||
@@ -17,7 +17,7 @@ pub enum FrontendMessage {
|
||||
// Display prefix: make the frontend show something, like a dialog
|
||||
DisplayDialog { icon: String },
|
||||
DisplayDialogDismiss,
|
||||
DisplayDialogPanic { panic_info: String, title: String, description: String },
|
||||
DisplayDialogPanic { panic_info: String, header: String, description: String },
|
||||
DisplayEditableTextbox { text: String, line_width: Option<f64>, font_size: f64, color: Color },
|
||||
DisplayRemoveEditableTextbox,
|
||||
|
||||
@@ -30,6 +30,7 @@ pub enum FrontendMessage {
|
||||
TriggerIndexedDbWriteDocument { document: String, details: FrontendDocumentDetails, version: String },
|
||||
TriggerPaste,
|
||||
TriggerRasterDownload { document: String, name: String, mime: String, size: (f64, f64) },
|
||||
TriggerRefreshBoundsOfViewports,
|
||||
TriggerTextCommit,
|
||||
TriggerTextCopy { copy_text: String },
|
||||
TriggerViewportResize,
|
||||
@@ -58,5 +59,5 @@ pub enum FrontendMessage {
|
||||
UpdatePropertyPanelSectionsLayout { layout_target: LayoutTarget, layout: SubLayout },
|
||||
UpdateToolOptionsLayout { layout_target: LayoutTarget, layout: SubLayout },
|
||||
UpdateToolShelfLayout { layout_target: LayoutTarget, layout: SubLayout },
|
||||
UpdateWorkingColors { primary: Color, secondary: Color },
|
||||
UpdateWorkingColorsLayout { layout_target: LayoutTarget, layout: SubLayout },
|
||||
}
|
||||
|
||||
@@ -27,5 +27,9 @@ impl MessageHandler<GlobalMessage, ()> for GlobalMessageHandler {
|
||||
}
|
||||
}
|
||||
|
||||
advertise_actions!(GlobalMessageDiscriminant; LogInfo, LogDebug, LogTrace);
|
||||
advertise_actions!(GlobalMessageDiscriminant;
|
||||
LogInfo,
|
||||
LogDebug,
|
||||
LogTrace,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -266,14 +266,12 @@ impl Default for Mapping {
|
||||
|
||||
impl Mapping {
|
||||
pub fn match_message(&self, message: InputMapperMessage, keys: &KeyStates, actions: ActionList) -> Option<Message> {
|
||||
use InputMapperMessage::*;
|
||||
|
||||
let list = match message {
|
||||
KeyDown(key) => &self.key_down[key as usize],
|
||||
KeyUp(key) => &self.key_up[key as usize],
|
||||
DoubleClick => &self.double_click,
|
||||
MouseScroll => &self.mouse_scroll,
|
||||
PointerMove => &self.pointer_move,
|
||||
InputMapperMessage::KeyDown(key) => &self.key_down[key as usize],
|
||||
InputMapperMessage::KeyUp(key) => &self.key_up[key as usize],
|
||||
InputMapperMessage::DoubleClick => &self.double_click,
|
||||
InputMapperMessage::MouseScroll => &self.mouse_scroll,
|
||||
InputMapperMessage::PointerMove => &self.pointer_move,
|
||||
};
|
||||
list.match_mapping(keys, actions)
|
||||
}
|
||||
|
||||
@@ -156,7 +156,7 @@ impl InputPreprocessorMessageHandler {
|
||||
}
|
||||
|
||||
pub fn document_bounds(&self) -> [DVec2; 2] {
|
||||
// ipp bounds are relative to the entire screen
|
||||
// IPP bounds are relative to the entire application
|
||||
[(0., 0.).into(), self.viewport_bounds.bottom_right - self.viewport_bounds.top_left]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ pub enum LayoutTarget {
|
||||
PropertiesSections,
|
||||
ToolOptions,
|
||||
ToolShelf,
|
||||
WorkingColors,
|
||||
|
||||
// KEEP THIS ENUM LAST
|
||||
// This is a marker that is used to define an array that is used to hold widgets
|
||||
|
||||
@@ -55,6 +55,10 @@ impl LayoutMessageHandler {
|
||||
layout_target,
|
||||
layout: layout.clone().unwrap_widget_layout().layout,
|
||||
},
|
||||
LayoutTarget::WorkingColors => FrontendMessage::UpdateWorkingColorsLayout {
|
||||
layout_target,
|
||||
layout: layout.clone().unwrap_widget_layout().layout,
|
||||
},
|
||||
|
||||
#[remain::unsorted]
|
||||
LayoutTarget::LayoutTargetLength => panic!("`LayoutTargetLength` is not a valid Layout Target and is used for array indexing"),
|
||||
@@ -168,6 +172,7 @@ impl MessageHandler<LayoutMessage, ()> for LayoutMessageHandler {
|
||||
responses.push_back(callback_message);
|
||||
}
|
||||
Widget::Separator(_) => {}
|
||||
Widget::SwatchPairInput(_) => {}
|
||||
Widget::TextAreaInput(text_area_input) => {
|
||||
let update_value = value.as_str().expect("TextAreaInput update was not of type: string");
|
||||
text_area_input.value = update_value.into();
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use super::layout_message::LayoutTarget;
|
||||
use crate::input::keyboard::Key;
|
||||
use crate::message_prelude::*;
|
||||
use crate::Color;
|
||||
|
||||
use derivative::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
@@ -195,18 +196,18 @@ pub type SubLayout = Vec<LayoutGroup>;
|
||||
#[remain::sorted]
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub enum LayoutGroup {
|
||||
#[serde(rename = "column")]
|
||||
Column {
|
||||
#[serde(rename = "columnWidgets")]
|
||||
widgets: Vec<WidgetHolder>,
|
||||
},
|
||||
#[serde(rename = "row")]
|
||||
Row {
|
||||
#[serde(rename = "rowWidgets")]
|
||||
widgets: Vec<WidgetHolder>,
|
||||
},
|
||||
Section {
|
||||
name: String,
|
||||
layout: SubLayout,
|
||||
},
|
||||
#[serde(rename = "section")]
|
||||
Section { name: String, layout: SubLayout },
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
@@ -281,6 +282,7 @@ impl<'a> Iterator for WidgetIterMut<'a> {
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub struct WidgetHolder {
|
||||
#[serde(rename = "widgetId")]
|
||||
pub widget_id: u64,
|
||||
pub widget: Widget,
|
||||
}
|
||||
@@ -323,97 +325,265 @@ pub enum Widget {
|
||||
PopoverButton(PopoverButton),
|
||||
RadioInput(RadioInput),
|
||||
Separator(Separator),
|
||||
SwatchPairInput(SwatchPairInput),
|
||||
TextAreaInput(TextAreaInput),
|
||||
TextButton(TextButton),
|
||||
TextInput(TextInput),
|
||||
TextLabel(TextLabel),
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize, Derivative)]
|
||||
#[derivative(Debug, PartialEq, Default)]
|
||||
pub struct NumberInput {
|
||||
pub value: Option<f64>,
|
||||
#[serde(skip)]
|
||||
#[derivative(Debug = "ignore", PartialEq = "ignore")]
|
||||
pub on_update: WidgetCallback<NumberInput>,
|
||||
pub min: Option<f64>,
|
||||
pub max: Option<f64>,
|
||||
#[serde(rename = "isInteger")]
|
||||
pub is_integer: bool,
|
||||
#[serde(rename = "incrementBehavior")]
|
||||
pub increment_behavior: NumberInputIncrementBehavior,
|
||||
#[serde(rename = "incrementFactor")]
|
||||
#[derivative(Default(value = "1."))]
|
||||
pub increment_factor: f64,
|
||||
#[serde(skip)]
|
||||
#[derivative(Debug = "ignore", PartialEq = "ignore")]
|
||||
pub increment_callback_increase: WidgetCallback<NumberInput>,
|
||||
#[serde(skip)]
|
||||
#[derivative(Debug = "ignore", PartialEq = "ignore")]
|
||||
pub increment_callback_decrease: WidgetCallback<NumberInput>,
|
||||
pub label: String,
|
||||
pub unit: String,
|
||||
#[serde(rename = "displayDecimalPlaces")]
|
||||
#[derivative(Default(value = "3"))]
|
||||
pub display_decimal_places: u32,
|
||||
pub disabled: bool,
|
||||
}
|
||||
#[derive(Clone, Serialize, Deserialize, Derivative, Default)]
|
||||
#[derivative(Debug, PartialEq)]
|
||||
pub struct CheckboxInput {
|
||||
pub checked: bool,
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize, Derivative)]
|
||||
#[derivative(Debug, PartialEq, Default)]
|
||||
pub struct TextInput {
|
||||
pub value: String,
|
||||
#[serde(skip)]
|
||||
#[derivative(Debug = "ignore", PartialEq = "ignore")]
|
||||
pub on_update: WidgetCallback<TextInput>,
|
||||
}
|
||||
pub icon: String,
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize, Derivative)]
|
||||
#[derivative(Debug, PartialEq, Default)]
|
||||
pub struct TextAreaInput {
|
||||
pub value: String,
|
||||
pub tooltip: String,
|
||||
|
||||
// Callbacks
|
||||
#[serde(skip)]
|
||||
#[derivative(Debug = "ignore", PartialEq = "ignore")]
|
||||
pub on_update: WidgetCallback<TextAreaInput>,
|
||||
pub on_update: WidgetCallback<CheckboxInput>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize, Derivative)]
|
||||
#[derivative(Debug, PartialEq, Default)]
|
||||
pub struct ColorInput {
|
||||
pub value: Option<String>,
|
||||
|
||||
pub label: Option<String>,
|
||||
|
||||
#[serde(rename = "noTransparency")]
|
||||
#[derivative(Default(value = "true"))]
|
||||
pub no_transparency: bool,
|
||||
|
||||
pub disabled: bool,
|
||||
|
||||
pub tooltip: String,
|
||||
|
||||
// Callbacks
|
||||
#[serde(skip)]
|
||||
#[derivative(Debug = "ignore", PartialEq = "ignore")]
|
||||
pub on_update: WidgetCallback<ColorInput>,
|
||||
#[serde(rename = "canSetTransparent")]
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize, Derivative)]
|
||||
#[derivative(Debug, PartialEq, Default)]
|
||||
pub struct DropdownInput {
|
||||
pub entries: DropdownInputEntries,
|
||||
|
||||
// This uses `u32` instead of `usize` since it will be serialized as a normal JS number (replace this with `usize` after switching to a Rust-based GUI)
|
||||
#[serde(rename = "selectedIndex")]
|
||||
pub selected_index: Option<u32>,
|
||||
|
||||
#[serde(rename = "drawIcon")]
|
||||
pub draw_icon: bool,
|
||||
|
||||
#[derivative(Default(value = "true"))]
|
||||
pub can_set_transparent: bool,
|
||||
pub interactive: bool,
|
||||
|
||||
pub disabled: bool,
|
||||
//
|
||||
// Callbacks
|
||||
// `on_update` exists on the `DropdownEntryData`, not this parent `DropdownInput`
|
||||
}
|
||||
|
||||
pub type DropdownInputEntries = Vec<Vec<DropdownEntryData>>;
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize, Derivative, Default)]
|
||||
#[derivative(Debug, PartialEq)]
|
||||
pub struct DropdownEntryData {
|
||||
pub value: String,
|
||||
|
||||
pub label: String,
|
||||
|
||||
pub icon: String,
|
||||
|
||||
pub shortcut: Vec<String>,
|
||||
|
||||
#[serde(rename = "shortcutRequiresLock")]
|
||||
pub shortcut_requires_lock: bool,
|
||||
|
||||
pub disabled: bool,
|
||||
|
||||
pub children: DropdownInputEntries,
|
||||
|
||||
// Callbacks
|
||||
#[serde(skip)]
|
||||
#[derivative(Debug = "ignore", PartialEq = "ignore")]
|
||||
pub on_update: WidgetCallback<()>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize, Derivative)]
|
||||
#[derivative(Debug, PartialEq, Default)]
|
||||
pub struct FontInput {
|
||||
#[serde(rename = "isStyle")]
|
||||
pub is_style_picker: bool,
|
||||
#[serde(rename = "fontFamily")]
|
||||
pub font_family: String,
|
||||
|
||||
#[serde(rename = "fontStyle")]
|
||||
pub font_style: String,
|
||||
|
||||
#[serde(rename = "isStyle")]
|
||||
pub is_style_picker: bool,
|
||||
|
||||
pub disabled: bool,
|
||||
|
||||
// Callbacks
|
||||
#[serde(skip)]
|
||||
#[derivative(Debug = "ignore", PartialEq = "ignore")]
|
||||
pub on_update: WidgetCallback<FontInput>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)]
|
||||
#[derive(Clone, Serialize, Deserialize, Derivative, Default)]
|
||||
#[derivative(Debug, PartialEq)]
|
||||
pub struct IconButton {
|
||||
pub icon: String,
|
||||
|
||||
pub size: u32, // TODO: Convert to an `IconSize` enum
|
||||
|
||||
pub active: bool,
|
||||
|
||||
pub tooltip: String,
|
||||
|
||||
// Callbacks
|
||||
#[serde(skip)]
|
||||
#[derivative(Debug = "ignore", PartialEq = "ignore")]
|
||||
pub on_update: WidgetCallback<IconButton>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize, Derivative, Debug, Default, PartialEq, Eq)]
|
||||
pub struct IconLabel {
|
||||
pub icon: String,
|
||||
|
||||
#[serde(rename = "iconStyle")]
|
||||
pub icon_style: IconStyle,
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize, Derivative, Debug, Default, PartialEq, Eq)]
|
||||
pub enum IconStyle {
|
||||
#[default]
|
||||
Normal,
|
||||
Node,
|
||||
}
|
||||
|
||||
/// This widget allows for the flexible use of the layout system.
|
||||
/// In a custom layout, one can define a widget that is just used to trigger code on the backend.
|
||||
/// This is used in MenuLayout to pipe the triggering of messages from the frontend to backend.
|
||||
#[derive(Clone, Serialize, Deserialize, Derivative, Default)]
|
||||
#[derivative(Debug, PartialEq)]
|
||||
pub struct Invisible {
|
||||
#[serde(skip)]
|
||||
#[derivative(Debug = "ignore", PartialEq = "ignore")]
|
||||
pub on_update: WidgetCallback<()>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize, Derivative)]
|
||||
#[derivative(Debug, PartialEq, Default)]
|
||||
pub struct NumberInput {
|
||||
pub label: String,
|
||||
|
||||
pub value: Option<f64>,
|
||||
|
||||
pub min: Option<f64>,
|
||||
|
||||
pub max: Option<f64>,
|
||||
|
||||
#[serde(rename = "isInteger")]
|
||||
pub is_integer: bool,
|
||||
|
||||
#[serde(rename = "displayDecimalPlaces")]
|
||||
#[derivative(Default(value = "3"))]
|
||||
pub display_decimal_places: u32,
|
||||
|
||||
pub unit: String,
|
||||
|
||||
#[serde(rename = "unitIsHiddenWhenEditing")]
|
||||
#[derivative(Default(value = "true"))]
|
||||
pub unit_is_hidden_when_editing: bool,
|
||||
|
||||
#[serde(rename = "incrementBehavior")]
|
||||
pub increment_behavior: NumberInputIncrementBehavior,
|
||||
|
||||
#[serde(rename = "incrementFactor")]
|
||||
#[derivative(Default(value = "1."))]
|
||||
pub increment_factor: f64,
|
||||
|
||||
pub disabled: bool,
|
||||
|
||||
// Callbacks
|
||||
#[serde(skip)]
|
||||
#[derivative(Debug = "ignore", PartialEq = "ignore")]
|
||||
pub on_update: WidgetCallback<NumberInput>,
|
||||
|
||||
#[serde(skip)]
|
||||
#[derivative(Debug = "ignore", PartialEq = "ignore")]
|
||||
pub increment_callback_increase: WidgetCallback<NumberInput>,
|
||||
|
||||
#[serde(skip)]
|
||||
#[derivative(Debug = "ignore", PartialEq = "ignore")]
|
||||
pub increment_callback_decrease: WidgetCallback<NumberInput>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize, Debug, Default, PartialEq, Eq)]
|
||||
pub enum NumberInputIncrementBehavior {
|
||||
#[default]
|
||||
Add,
|
||||
Multiply,
|
||||
Callback,
|
||||
}
|
||||
|
||||
impl Default for NumberInputIncrementBehavior {
|
||||
fn default() -> Self {
|
||||
Self::Add
|
||||
}
|
||||
#[derive(Clone, Serialize, Deserialize, Derivative, Default)]
|
||||
#[derivative(Debug, PartialEq)]
|
||||
pub struct OptionalInput {
|
||||
pub checked: bool,
|
||||
|
||||
pub icon: String,
|
||||
|
||||
pub tooltip: String,
|
||||
|
||||
// Callbacks
|
||||
#[serde(skip)]
|
||||
#[derivative(Debug = "ignore", PartialEq = "ignore")]
|
||||
pub on_update: WidgetCallback<OptionalInput>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize, Derivative)]
|
||||
#[derivative(Debug, PartialEq, Default)]
|
||||
pub struct PopoverButton {
|
||||
pub icon: Option<String>,
|
||||
|
||||
// Body
|
||||
pub header: String,
|
||||
|
||||
pub text: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize, Derivative, Default)]
|
||||
#[derivative(Debug, PartialEq)]
|
||||
pub struct RadioInput {
|
||||
pub entries: Vec<RadioEntryData>,
|
||||
|
||||
// This uses `u32` instead of `usize` since it will be serialized as a normal JS number (replace this with `usize` after switching to a Rust-based GUI)
|
||||
#[serde(rename = "selectedIndex")]
|
||||
pub selected_index: u32,
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize, Derivative, Default)]
|
||||
#[derivative(Debug, PartialEq)]
|
||||
pub struct RadioEntryData {
|
||||
pub value: String,
|
||||
|
||||
pub label: String,
|
||||
|
||||
pub icon: String,
|
||||
|
||||
pub tooltip: String,
|
||||
|
||||
// Callbacks
|
||||
#[serde(skip)]
|
||||
#[derivative(Debug = "ignore", PartialEq = "ignore")]
|
||||
pub on_update: WidgetCallback<()>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
@@ -438,19 +608,27 @@ pub enum SeparatorType {
|
||||
List,
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize, Derivative, Default)]
|
||||
#[derivative(Debug, PartialEq)]
|
||||
pub struct IconButton {
|
||||
pub icon: String,
|
||||
#[serde(rename = "title")]
|
||||
pub tooltip: String,
|
||||
pub size: u32,
|
||||
pub active: bool,
|
||||
#[serde(rename = "gapAfter")]
|
||||
pub gap_after: bool,
|
||||
#[derive(Clone, Serialize, Deserialize, Derivative)]
|
||||
#[derivative(Debug, PartialEq, Default)]
|
||||
pub struct SwatchPairInput {
|
||||
pub primary: Color,
|
||||
|
||||
pub secondary: Color,
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize, Derivative)]
|
||||
#[derivative(Debug, PartialEq, Default)]
|
||||
pub struct TextAreaInput {
|
||||
pub value: String,
|
||||
|
||||
pub label: Option<String>,
|
||||
|
||||
pub disabled: bool,
|
||||
|
||||
// Callbacks
|
||||
#[serde(skip)]
|
||||
#[derivative(Debug = "ignore", PartialEq = "ignore")]
|
||||
pub on_update: WidgetCallback<IconButton>,
|
||||
pub on_update: WidgetCallback<TextAreaInput>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize, Derivative, Default)]
|
||||
@@ -458,125 +636,46 @@ pub struct IconButton {
|
||||
#[serde(rename_all(serialize = "camelCase", deserialize = "camelCase"))]
|
||||
pub struct TextButton {
|
||||
pub label: String,
|
||||
|
||||
pub emphasized: bool,
|
||||
|
||||
#[serde(rename = "minWidth")]
|
||||
pub min_width: u32,
|
||||
pub gap_after: bool,
|
||||
|
||||
pub disabled: bool,
|
||||
|
||||
// Callbacks
|
||||
#[serde(skip)]
|
||||
#[derivative(Debug = "ignore", PartialEq = "ignore")]
|
||||
pub on_update: WidgetCallback<TextButton>,
|
||||
pub disabled: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize, Derivative, Default)]
|
||||
#[derivative(Debug, PartialEq)]
|
||||
pub struct OptionalInput {
|
||||
pub checked: bool,
|
||||
pub icon: String,
|
||||
#[serde(rename = "title")]
|
||||
pub tooltip: String,
|
||||
#[serde(skip)]
|
||||
#[derivative(Debug = "ignore", PartialEq = "ignore")]
|
||||
pub on_update: WidgetCallback<OptionalInput>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize, Derivative, Default)]
|
||||
#[derivative(Debug, PartialEq)]
|
||||
pub struct CheckboxInput {
|
||||
pub checked: bool,
|
||||
pub icon: String,
|
||||
#[serde(rename = "title")]
|
||||
pub tooltip: String,
|
||||
#[serde(skip)]
|
||||
#[derivative(Debug = "ignore", PartialEq = "ignore")]
|
||||
pub on_update: WidgetCallback<CheckboxInput>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize, Derivative, Default)]
|
||||
#[derivative(Debug, PartialEq)]
|
||||
pub struct PopoverButton {
|
||||
pub title: String,
|
||||
pub text: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize, Derivative)]
|
||||
#[derivative(Debug, PartialEq, Default)]
|
||||
pub struct DropdownInput {
|
||||
pub entries: Vec<Vec<DropdownEntryData>>,
|
||||
// This uses `u32` instead of `usize` since it will be serialized as a normal JS number (replace with usize when we switch to a native UI)
|
||||
#[serde(rename = "selectedIndex")]
|
||||
pub selected_index: Option<u32>,
|
||||
#[serde(rename = "drawIcon")]
|
||||
pub draw_icon: bool,
|
||||
#[derivative(Default(value = "true"))]
|
||||
pub interactive: bool,
|
||||
// `on_update` exists on the `DropdownEntryData`, not this parent `DropdownInput`
|
||||
pub disabled: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize, Derivative, Default)]
|
||||
#[derivative(Debug, PartialEq)]
|
||||
pub struct DropdownEntryData {
|
||||
pub struct TextInput {
|
||||
pub value: String,
|
||||
pub label: String,
|
||||
pub icon: String,
|
||||
pub shortcut: Vec<String>,
|
||||
#[serde(rename = "shortcutRequiresLock")]
|
||||
pub shortcut_requires_lock: bool,
|
||||
pub disabled: bool,
|
||||
pub children: Vec<Vec<DropdownEntryData>>,
|
||||
|
||||
pub label: Option<String>,
|
||||
|
||||
pub disabled: bool,
|
||||
|
||||
// Callbacks
|
||||
#[serde(skip)]
|
||||
#[derivative(Debug = "ignore", PartialEq = "ignore")]
|
||||
pub on_update: WidgetCallback<()>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize, Derivative, Default)]
|
||||
#[derivative(Debug, PartialEq)]
|
||||
pub struct RadioInput {
|
||||
pub entries: Vec<RadioEntryData>,
|
||||
|
||||
// This uses `u32` instead of `usize` since it will be serialized as a normal JS number
|
||||
// TODO(mfish33): Replace with usize when using native UI
|
||||
#[serde(rename = "selectedIndex")]
|
||||
pub selected_index: u32,
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize, Derivative, Default)]
|
||||
#[derivative(Debug, PartialEq)]
|
||||
pub struct RadioEntryData {
|
||||
pub value: String,
|
||||
pub label: String,
|
||||
pub icon: String,
|
||||
pub tooltip: String,
|
||||
#[serde(skip)]
|
||||
#[derivative(Debug = "ignore", PartialEq = "ignore")]
|
||||
pub on_update: WidgetCallback<()>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize, Derivative, Debug, PartialEq, Eq)]
|
||||
pub struct IconLabel {
|
||||
pub icon: String,
|
||||
#[serde(rename = "gapAfter")]
|
||||
pub gap_after: bool,
|
||||
pub on_update: WidgetCallback<TextInput>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize, Derivative, Debug, PartialEq, Eq, Default)]
|
||||
pub struct TextLabel {
|
||||
pub value: String,
|
||||
pub bold: bool,
|
||||
|
||||
pub italic: bool,
|
||||
pub multiline: bool,
|
||||
|
||||
#[serde(rename = "tableAlign")]
|
||||
pub table_align: bool,
|
||||
}
|
||||
|
||||
// This widget allows for the flexible use of the layout system
|
||||
// In a custom layout one can define a widget that is just used to trigger code on the backend
|
||||
// This is used in MenuLayout to pipe the triggering of messages from the frontend to backend
|
||||
#[derive(Clone, Serialize, Deserialize, Derivative, Default)]
|
||||
#[derivative(Debug, PartialEq)]
|
||||
pub struct Invisible {
|
||||
#[serde(skip)]
|
||||
#[derivative(Debug = "ignore", PartialEq = "ignore")]
|
||||
pub on_update: WidgetCallback<()>,
|
||||
pub multiline: bool,
|
||||
|
||||
// Body
|
||||
pub value: String,
|
||||
}
|
||||
|
||||
@@ -13,7 +13,10 @@
|
||||
///
|
||||
/// and
|
||||
/// ```ignore
|
||||
/// actions!(DocumentMessage; Undo, Redo);
|
||||
/// actions!(DocumentMessage;
|
||||
/// Undo,
|
||||
/// Redo,
|
||||
/// );
|
||||
/// ```
|
||||
///
|
||||
/// expands to:
|
||||
|
||||
@@ -8,6 +8,8 @@ use graphene::color::Color;
|
||||
|
||||
/// A set of utility functions to make the writing of editor test more declarative
|
||||
pub trait EditorTestUtils {
|
||||
fn new_document(&mut self);
|
||||
|
||||
fn draw_rect(&mut self, x1: f64, y1: f64, x2: f64, y2: f64);
|
||||
fn draw_shape(&mut self, x1: f64, y1: f64, x2: f64, y2: f64);
|
||||
fn draw_ellipse(&mut self, x1: f64, y1: f64, x2: f64, y2: f64);
|
||||
@@ -24,6 +26,10 @@ pub trait EditorTestUtils {
|
||||
}
|
||||
|
||||
impl EditorTestUtils for Editor {
|
||||
fn new_document(&mut self) {
|
||||
self.handle_message(Message::Portfolio(PortfolioMessage::NewDocumentWithName { name: String::from("Test document") }));
|
||||
}
|
||||
|
||||
fn draw_rect(&mut self, x1: f64, y1: f64, x2: f64, y2: f64) {
|
||||
self.drag_tool(ToolType::Rectangle, x1, y1, x2, y2);
|
||||
}
|
||||
|
||||
@@ -40,6 +40,7 @@ pub struct SignalToMessageMap {
|
||||
|
||||
pub trait ToolTransition {
|
||||
fn signal_to_message_map(&self) -> SignalToMessageMap;
|
||||
|
||||
fn activate(&self, responses: &mut VecDeque<Message>) {
|
||||
let mut subscribe_message = |broadcast_to_tool_mapping: Option<ToolMessage>, signal: BroadcastSignal| {
|
||||
if let Some(mapping) = broadcast_to_tool_mapping {
|
||||
@@ -78,6 +79,7 @@ pub trait ToolTransition {
|
||||
unsubscribe_message(signal_to_tool_map.selection_changed, BroadcastSignal::SelectionChanged);
|
||||
}
|
||||
}
|
||||
|
||||
pub trait ToolMetadata {
|
||||
fn icon_name(&self) -> String;
|
||||
fn tooltip(&self) -> String;
|
||||
|
||||
@@ -75,10 +75,10 @@ pub enum ToolMessage {
|
||||
// Detail(DetailToolMessage),
|
||||
|
||||
// Messages
|
||||
#[remain::unsorted]
|
||||
ActivateTool {
|
||||
tool_type: ToolType,
|
||||
},
|
||||
DeactivateTools,
|
||||
InitTools,
|
||||
ResetColors,
|
||||
SelectPrimaryColor {
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
use super::tool::{message_to_tool_type, DocumentToolData, ToolFsmState};
|
||||
use super::tool::{message_to_tool_type, ToolFsmState};
|
||||
use crate::document::DocumentMessageHandler;
|
||||
use crate::input::InputPreprocessorMessageHandler;
|
||||
use crate::layout::layout_message::LayoutTarget;
|
||||
use crate::layout::widgets::PropertyHolder;
|
||||
use crate::layout::widgets::{IconButton, Layout, LayoutGroup, SwatchPairInput, Widget, WidgetCallback, WidgetHolder, WidgetLayout};
|
||||
use crate::message_prelude::*;
|
||||
use crate::viewport_tools::tool::DocumentToolData;
|
||||
|
||||
use graphene::color::Color;
|
||||
use graphene::layers::text_layer::FontCache;
|
||||
@@ -73,12 +75,16 @@ impl MessageHandler<ToolMessage, (&DocumentMessageHandler, &InputPreprocessorMes
|
||||
// Notify the frontend about the new active tool to be displayed
|
||||
tool_data.register_properties(responses, LayoutTarget::ToolShelf);
|
||||
}
|
||||
DeactivateTools => {
|
||||
let tool_data = &mut self.tool_state.tool_data;
|
||||
tool_data.tools.get(&tool_data.active_tool_type).unwrap().deactivate(responses);
|
||||
}
|
||||
InitTools => {
|
||||
let tool_data = &mut self.tool_state.tool_data;
|
||||
let document_data = &self.tool_state.document_tool_data;
|
||||
let active_tool = &tool_data.active_tool_type;
|
||||
|
||||
// subscribe tool to broadcast messages
|
||||
// Subscribe tool to broadcast messages
|
||||
tool_data.tools.get(active_tool).unwrap().activate(responses);
|
||||
|
||||
// Register initial properties
|
||||
@@ -87,6 +93,10 @@ impl MessageHandler<ToolMessage, (&DocumentMessageHandler, &InputPreprocessorMes
|
||||
// Notify the frontend about the initial active tool
|
||||
tool_data.register_properties(responses, LayoutTarget::ToolShelf);
|
||||
|
||||
// Notify the frontend about the initial working colors
|
||||
update_working_colors(document_data, responses);
|
||||
responses.push_back(FrontendMessage::TriggerRefreshBoundsOfViewports.into());
|
||||
|
||||
// Set initial hints and cursor
|
||||
tool_data
|
||||
.active_tool_mut()
|
||||
@@ -156,7 +166,12 @@ impl MessageHandler<ToolMessage, (&DocumentMessageHandler, &InputPreprocessorMes
|
||||
}
|
||||
|
||||
fn actions(&self) -> ActionList {
|
||||
let mut list = actions!(ToolMessageDiscriminant; SelectRandomPrimaryColor, ResetColors, SwapColors, ActivateTool);
|
||||
let mut list = actions!(ToolMessageDiscriminant;
|
||||
ActivateTool,
|
||||
SelectRandomPrimaryColor,
|
||||
ResetColors,
|
||||
SwapColors,
|
||||
);
|
||||
list.extend(self.tool_state.tool_data.active_tool().actions());
|
||||
|
||||
list
|
||||
@@ -164,10 +179,37 @@ impl MessageHandler<ToolMessage, (&DocumentMessageHandler, &InputPreprocessorMes
|
||||
}
|
||||
|
||||
fn update_working_colors(document_data: &DocumentToolData, responses: &mut VecDeque<Message>) {
|
||||
let layout = WidgetLayout::new(vec![
|
||||
LayoutGroup::Row {
|
||||
widgets: vec![WidgetHolder::new(Widget::SwatchPairInput(SwatchPairInput {
|
||||
primary: document_data.primary_color,
|
||||
secondary: document_data.secondary_color,
|
||||
}))],
|
||||
},
|
||||
LayoutGroup::Row {
|
||||
widgets: vec![
|
||||
WidgetHolder::new(Widget::IconButton(IconButton {
|
||||
size: 16,
|
||||
icon: "Swap".into(),
|
||||
tooltip: "Swap (Shift+X)".into(), // TODO: Customize this tooltip for the Mac version of the keyboard shortcut
|
||||
on_update: WidgetCallback::new(|_| ToolMessage::SwapColors.into()),
|
||||
..Default::default()
|
||||
})),
|
||||
WidgetHolder::new(Widget::IconButton(IconButton {
|
||||
size: 16,
|
||||
icon: "ResetColors".into(), // TODO: Customize this tooltip for the Mac version of the keyboard shortcut
|
||||
tooltip: "Reset (Ctrl+Shift+X)".into(),
|
||||
on_update: WidgetCallback::new(|_| ToolMessage::ResetColors.into()),
|
||||
..Default::default()
|
||||
})),
|
||||
],
|
||||
},
|
||||
]);
|
||||
|
||||
responses.push_back(
|
||||
FrontendMessage::UpdateWorkingColors {
|
||||
primary: document_data.primary_color,
|
||||
secondary: document_data.secondary_color,
|
||||
LayoutMessage::SendLayout {
|
||||
layout: Layout::WidgetLayout(layout),
|
||||
layout_target: LayoutTarget::WorkingColors,
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
|
||||
@@ -73,7 +73,13 @@ impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for ArtboardTool
|
||||
}
|
||||
}
|
||||
|
||||
advertise_actions!(ArtboardToolMessageDiscriminant; PointerDown, PointerUp, PointerMove, DeleteSelected, Abort);
|
||||
advertise_actions!(ArtboardToolMessageDiscriminant;
|
||||
PointerDown,
|
||||
PointerUp,
|
||||
PointerMove,
|
||||
DeleteSelected,
|
||||
Abort,
|
||||
);
|
||||
}
|
||||
|
||||
impl PropertyHolder for ArtboardTool {}
|
||||
|
||||
@@ -75,8 +75,14 @@ impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for EllipseTool
|
||||
use EllipseToolFsmState::*;
|
||||
|
||||
match self.fsm_state {
|
||||
Ready => actions!(EllipseToolMessageDiscriminant; DragStart),
|
||||
Drawing => actions!(EllipseToolMessageDiscriminant; DragStop, Abort, Resize),
|
||||
Ready => actions!(EllipseToolMessageDiscriminant;
|
||||
DragStart,
|
||||
),
|
||||
Drawing => actions!(EllipseToolMessageDiscriminant;
|
||||
DragStop,
|
||||
Abort,
|
||||
Resize,
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,7 +66,10 @@ impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for EyedropperTo
|
||||
}
|
||||
}
|
||||
|
||||
advertise_actions!(EyedropperToolMessageDiscriminant; LeftMouseDown, RightMouseDown);
|
||||
advertise_actions!(EyedropperToolMessageDiscriminant;
|
||||
LeftMouseDown,
|
||||
RightMouseDown,
|
||||
);
|
||||
}
|
||||
|
||||
impl ToolTransition for EyedropperTool {
|
||||
|
||||
@@ -67,7 +67,10 @@ impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for FillTool {
|
||||
}
|
||||
}
|
||||
|
||||
advertise_actions!(FillToolMessageDiscriminant; LeftMouseDown, RightMouseDown);
|
||||
advertise_actions!(FillToolMessageDiscriminant;
|
||||
LeftMouseDown,
|
||||
RightMouseDown,
|
||||
);
|
||||
}
|
||||
|
||||
impl ToolTransition for FillTool {
|
||||
|
||||
@@ -115,8 +115,16 @@ impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for FreehandTool
|
||||
use FreehandToolFsmState::*;
|
||||
|
||||
match self.fsm_state {
|
||||
Ready => actions!(FreehandToolMessageDiscriminant; DragStart, DragStop, Abort),
|
||||
Drawing => actions!(FreehandToolMessageDiscriminant; DragStop, PointerMove, Abort),
|
||||
Ready => actions!(FreehandToolMessageDiscriminant;
|
||||
DragStart,
|
||||
DragStop,
|
||||
Abort,
|
||||
),
|
||||
Drawing => actions!(FreehandToolMessageDiscriminant;
|
||||
DragStop,
|
||||
PointerMove,
|
||||
Abort,
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -98,7 +98,12 @@ impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for GradientTool
|
||||
}
|
||||
}
|
||||
|
||||
advertise_actions!(GradientToolMessageDiscriminant; PointerDown, PointerUp, PointerMove, Abort);
|
||||
advertise_actions!(GradientToolMessageDiscriminant;
|
||||
PointerDown,
|
||||
PointerUp,
|
||||
PointerMove,
|
||||
Abort,
|
||||
);
|
||||
}
|
||||
|
||||
impl PropertyHolder for GradientTool {
|
||||
|
||||
@@ -116,8 +116,14 @@ impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for LineTool {
|
||||
use LineToolFsmState::*;
|
||||
|
||||
match self.fsm_state {
|
||||
Ready => actions!(LineToolMessageDiscriminant; DragStart),
|
||||
Drawing => actions!(LineToolMessageDiscriminant; DragStop, Redraw, Abort),
|
||||
Ready => actions!(LineToolMessageDiscriminant;
|
||||
DragStart,
|
||||
),
|
||||
Drawing => actions!(LineToolMessageDiscriminant;
|
||||
DragStop,
|
||||
Redraw,
|
||||
Abort,
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,8 +75,16 @@ impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for NavigateTool
|
||||
use NavigateToolFsmState::*;
|
||||
|
||||
match self.fsm_state {
|
||||
Ready => actions!(NavigateToolMessageDiscriminant; TranslateCanvasBegin, RotateCanvasBegin, ZoomCanvasBegin),
|
||||
_ => actions!(NavigateToolMessageDiscriminant; ClickZoom, PointerMove, TransformCanvasEnd),
|
||||
Ready => actions!(NavigateToolMessageDiscriminant;
|
||||
TranslateCanvasBegin,
|
||||
RotateCanvasBegin,
|
||||
ZoomCanvasBegin,
|
||||
),
|
||||
_ => actions!(NavigateToolMessageDiscriminant;
|
||||
ClickZoom,
|
||||
PointerMove,
|
||||
TransformCanvasEnd,
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,8 +85,15 @@ impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for PathTool {
|
||||
use PathToolFsmState::*;
|
||||
|
||||
match self.fsm_state {
|
||||
Ready => actions!(PathToolMessageDiscriminant; DragStart, Delete),
|
||||
Dragging => actions!(PathToolMessageDiscriminant; DragStop, PointerMove, Delete),
|
||||
Ready => actions!(PathToolMessageDiscriminant;
|
||||
DragStart,
|
||||
Delete,
|
||||
),
|
||||
Dragging => actions!(PathToolMessageDiscriminant;
|
||||
DragStop,
|
||||
PointerMove,
|
||||
Delete,
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -131,8 +131,20 @@ impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for PenTool {
|
||||
|
||||
fn actions(&self) -> ActionList {
|
||||
match self.fsm_state {
|
||||
PenToolFsmState::Ready => actions!(PenToolMessageDiscriminant; Undo, DragStart, DragStop, Confirm, Abort),
|
||||
PenToolFsmState::DraggingHandle | PenToolFsmState::PlacingAnchor => actions!(PenToolMessageDiscriminant; DragStart, DragStop, PointerMove, Confirm, Abort),
|
||||
PenToolFsmState::Ready => actions!(PenToolMessageDiscriminant;
|
||||
Undo,
|
||||
DragStart,
|
||||
DragStop,
|
||||
Confirm,
|
||||
Abort,
|
||||
),
|
||||
PenToolFsmState::DraggingHandle | PenToolFsmState::PlacingAnchor => actions!(PenToolMessageDiscriminant;
|
||||
DragStart,
|
||||
DragStop,
|
||||
PointerMove,
|
||||
Confirm,
|
||||
Abort,
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,8 +63,14 @@ impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for RectangleToo
|
||||
use RectangleToolFsmState::*;
|
||||
|
||||
match self.fsm_state {
|
||||
Ready => actions!(RectangleToolMessageDiscriminant; DragStart),
|
||||
Drawing => actions!(RectangleToolMessageDiscriminant; DragStop, Abort, Resize),
|
||||
Ready => actions!(RectangleToolMessageDiscriminant;
|
||||
DragStart,
|
||||
),
|
||||
Drawing => actions!(RectangleToolMessageDiscriminant;
|
||||
DragStop,
|
||||
Abort,
|
||||
Resize,
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -161,8 +161,9 @@ impl PropertyHolder for SelectTool {
|
||||
separator_type: SeparatorType::Related,
|
||||
})),
|
||||
WidgetHolder::new(Widget::PopoverButton(PopoverButton {
|
||||
title: "Align".into(),
|
||||
header: "Align".into(),
|
||||
text: "The contents of this popover menu are coming soon".into(),
|
||||
..Default::default()
|
||||
})),
|
||||
WidgetHolder::new(Widget::Separator(Separator {
|
||||
direction: SeparatorDirection::Horizontal,
|
||||
@@ -187,8 +188,9 @@ impl PropertyHolder for SelectTool {
|
||||
separator_type: SeparatorType::Related,
|
||||
})),
|
||||
WidgetHolder::new(Widget::PopoverButton(PopoverButton {
|
||||
title: "Flip".into(),
|
||||
header: "Flip".into(),
|
||||
text: "The contents of this popover menu are coming soon".into(),
|
||||
..Default::default()
|
||||
})),
|
||||
WidgetHolder::new(Widget::Separator(Separator {
|
||||
direction: SeparatorDirection::Horizontal,
|
||||
@@ -234,8 +236,9 @@ impl PropertyHolder for SelectTool {
|
||||
separator_type: SeparatorType::Related,
|
||||
})),
|
||||
WidgetHolder::new(Widget::PopoverButton(PopoverButton {
|
||||
title: "Boolean".into(),
|
||||
header: "Boolean".into(),
|
||||
text: "The contents of this popover menu are coming soon".into(),
|
||||
..Default::default()
|
||||
})),
|
||||
],
|
||||
}]))
|
||||
@@ -266,8 +269,18 @@ impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for SelectTool {
|
||||
use SelectToolFsmState::*;
|
||||
|
||||
match self.fsm_state {
|
||||
Ready => actions!(SelectToolMessageDiscriminant; DragStart, PointerMove, Abort, EditLayer),
|
||||
_ => actions!(SelectToolMessageDiscriminant; DragStop, PointerMove, Abort, EditLayer),
|
||||
Ready => actions!(SelectToolMessageDiscriminant;
|
||||
DragStart,
|
||||
PointerMove,
|
||||
Abort,
|
||||
EditLayer,
|
||||
),
|
||||
_ => actions!(SelectToolMessageDiscriminant;
|
||||
DragStop,
|
||||
PointerMove,
|
||||
Abort,
|
||||
EditLayer,
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -114,8 +114,14 @@ impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for ShapeTool {
|
||||
use ShapeToolFsmState::*;
|
||||
|
||||
match self.fsm_state {
|
||||
Ready => actions!(ShapeToolMessageDiscriminant; DragStart),
|
||||
Drawing => actions!(ShapeToolMessageDiscriminant; DragStop, Abort, Resize),
|
||||
Ready => actions!(ShapeToolMessageDiscriminant;
|
||||
DragStart,
|
||||
),
|
||||
Drawing => actions!(ShapeToolMessageDiscriminant;
|
||||
DragStop,
|
||||
Abort,
|
||||
Resize,
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -119,8 +119,19 @@ impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for SplineTool {
|
||||
use SplineToolFsmState::*;
|
||||
|
||||
match self.fsm_state {
|
||||
Ready => actions!(SplineToolMessageDiscriminant; Undo, DragStart, DragStop, Confirm, Abort),
|
||||
Drawing => actions!(SplineToolMessageDiscriminant; DragStop, PointerMove, Confirm, Abort),
|
||||
Ready => actions!(SplineToolMessageDiscriminant;
|
||||
Undo,
|
||||
DragStart,
|
||||
DragStop,
|
||||
Confirm,
|
||||
Abort,
|
||||
),
|
||||
Drawing => actions!(SplineToolMessageDiscriminant;
|
||||
DragStop,
|
||||
PointerMove,
|
||||
Confirm,
|
||||
Abort,
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,6 +96,7 @@ impl PropertyHolder for TextTool {
|
||||
})
|
||||
.into()
|
||||
}),
|
||||
..Default::default()
|
||||
})),
|
||||
WidgetHolder::new(Widget::Separator(Separator {
|
||||
direction: SeparatorDirection::Horizontal,
|
||||
@@ -112,6 +113,7 @@ impl PropertyHolder for TextTool {
|
||||
})
|
||||
.into()
|
||||
}),
|
||||
..Default::default()
|
||||
})),
|
||||
WidgetHolder::new(Widget::Separator(Separator {
|
||||
direction: SeparatorDirection::Horizontal,
|
||||
@@ -169,8 +171,14 @@ impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for TextTool {
|
||||
use TextToolFsmState::*;
|
||||
|
||||
match self.fsm_state {
|
||||
Ready => actions!(TextMessageDiscriminant; Interact),
|
||||
Editing => actions!(TextMessageDiscriminant; Interact, Abort, CommitText),
|
||||
Ready => actions!(TextMessageDiscriminant;
|
||||
Interact,
|
||||
),
|
||||
Editing => actions!(TextMessageDiscriminant;
|
||||
Interact,
|
||||
Abort,
|
||||
CommitText,
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user