From 2fb13656bea2d37e31593d3886a9d02f8e367e47 Mon Sep 17 00:00:00 2001 From: mfish33 Date: Sun, 26 Jun 2022 18:01:07 -0600 Subject: [PATCH 01/21] unfinished implementation --- editor/src/communication/dispatcher.rs | 48 +-- editor/src/dialog/dialog_message_handler.rs | 58 +-- .../src/document/portfolio_message_handler.rs | 362 +++++++++--------- .../widgets/inputs/SwatchPairInput.vue | 4 +- frontend/src/io-managers/input.ts | 2 +- frontend/wasm/src/editor_api.rs | 12 +- 6 files changed, 246 insertions(+), 240 deletions(-) diff --git a/editor/src/communication/dispatcher.rs b/editor/src/communication/dispatcher.rs index ec75305a6e..d1cf627696 100644 --- a/editor/src/communication/dispatcher.rs +++ b/editor/src/communication/dispatcher.rs @@ -104,15 +104,19 @@ impl Dispatcher { .process_action(message, &self.message_handlers.input_preprocessor_message_handler, &mut self.message_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 self.message_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 self.message_queue, + ); + } else { + log::warn!("Called ToolMessage without an active document.\nGot {:?}", message); + } } Workspace(message) => { self.message_handlers @@ -188,14 +192,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(); @@ -222,7 +226,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 { @@ -235,7 +239,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(); @@ -267,7 +271,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. @@ -291,7 +295,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); @@ -306,7 +310,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(); @@ -357,7 +361,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]; @@ -378,7 +382,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(); @@ -408,7 +412,7 @@ mod test { fn map_to_vec(paths: Vec<&[LayerId]>) -> Vec> { paths.iter().map(|layer| layer.to_vec()).collect::>() } - 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| { @@ -424,15 +428,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::>()); 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::>()); 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::>()); } diff --git a/editor/src/dialog/dialog_message_handler.rs b/editor/src/dialog/dialog_message_handler.rs index f3b13e413a..24336e85ad 100644 --- a/editor/src/dialog/dialog_message_handler.rs +++ b/editor/src/dialog/dialog_message_handler.rs @@ -54,35 +54,37 @@ impl MessageHandler 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, - ..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, + ..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 { diff --git a/editor/src/document/portfolio_message_handler.rs b/editor/src/document/portfolio_message_handler.rs index 88ca085f5b..632dfb0cb7 100644 --- a/editor/src/document/portfolio_message_handler.rs +++ b/editor/src/document/portfolio_message_handler.rs @@ -13,23 +13,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, document_ids: Vec, - active_document_id: u64, + active_document_id: Option, copy_buffer: [Vec; 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 { @@ -55,21 +55,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) { - // Special case when loading a document on an empty page - if replace_first_empty && self.active_document().is_unmodified_default() { - responses.push_back(ToolMessage::AbortCurrentTool.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) { + self.document_ids.push(document_id); responses.extend( new_document @@ -117,25 +104,6 @@ impl PortfolioMessageHandler { } } -impl Default for PortfolioMessageHandler { - fn default() -> Self { - let mut documents_map: HashMap = HashMap::with_capacity(1); - let starting_key = generate_uuid(); - documents_map.insert(starting_key, DocumentMessageHandler::default()); - - const EMPTY_VEC: Vec = 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 for PortfolioMessageHandler { #[remain::check] fn process_action(&mut self, message: PortfolioMessage, ipp: &InputPreprocessorMessageHandler, responses: &mut VecDeque) { @@ -146,12 +114,20 @@ impl MessageHandler 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( @@ -168,46 +144,46 @@ impl MessageHandler 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(); - // 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; - responses.push_back(ToolMessage::AbortCurrentTool.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; + responses.push_back(PropertiesPanelMessage::ClearSelection.into()); + } 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 let open_documents = self .document_ids @@ -222,12 +198,16 @@ impl MessageHandler for Port .collect::>(); responses.push_back(FrontendMessage::UpdateOpenDocumentsList { open_documents }.into()); - responses.push_back(FrontendMessage::UpdateActiveDocument { document_id: self.active_document_id }.into()); + if let Some(document_id) = self.active_document_id { + responses.push_back(FrontendMessage::UpdateActiveDocument { 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 } => { @@ -249,30 +229,30 @@ impl MessageHandler 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| { - 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| { + 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 } => { @@ -299,20 +279,23 @@ impl MessageHandler for Port let new_document = DocumentMessageHandler::with_name(name, ipp); let document_id = generate_uuid(); responses.push_back(ToolMessage::AbortCurrentTool.into()); - self.load_document(new_document, document_id, false, responses); + + self.load_document(new_document, document_id, responses); } NewDocumentWithName { name } => { let new_document = DocumentMessageHandler::with_name(name, ipp); let document_id = generate_uuid(); responses.push_back(ToolMessage::AbortCurrentTool.into()); - self.load_document(new_document, document_id, false, responses); + 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()); @@ -341,7 +324,7 @@ impl MessageHandler 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 { @@ -353,22 +336,26 @@ impl MessageHandler 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, @@ -376,50 +363,9 @@ impl MessageHandler for Port insert_index, } => { let paste = |entry: &CopyBufferEntry, responses: &mut VecDeque<_>| { - log::trace!("Pasting into folder {:?} as index: {}", &path, insert_index); - - 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_image_data(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 { - for entry in self.copy_buffer[clipboard as usize].iter().rev() { - paste(entry, responses) - } - } else { - for entry in self.copy_buffer[clipboard as usize].iter() { - paste(entry, responses) - } - } - } - PasteSerializedData { data } => { - if let Ok(data) = serde_json::from_str::>(&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()); - - for entry in data.iter().rev() { - let destination_path = [shallowest_common_folder.to_vec(), vec![generate_uuid()]].concat(); + 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(); responses.push_front( DocumentMessage::UpdateLayerMetadata { @@ -428,36 +374,88 @@ impl MessageHandler for Port } .into(), ); - self.active_document().load_image_data(responses, &entry.layer.data, destination_path.clone()); + document.load_image_data(responses, &entry.layer.data, destination_path.clone()); responses.push_front( DocumentOperation::InsertLayer { layer: entry.layer.clone(), destination_path, - insert_index: -1, + insert_index, } .into(), ); } + }; - responses.push_back(CommitTransaction.into()); + if insert_index == -1 { + for entry in self.copy_buffer[clipboard as usize].iter().rev() { + paste(entry, responses) + } + } else { + for entry in self.copy_buffer[clipboard as usize].iter() { + paste(entry, responses) + } + } + } + PasteSerializedData { data } => { + if let Some(document) = self.active_document() { + if let Ok(data) = serde_json::from_str::>(&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(); + + responses.push_front( + DocumentMessage::UpdateLayerMetadata { + layer_path: destination_path.clone(), + layer_metadata: entry.layer_metadata, + } + .into(), + ); + document.load_image_data(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 => { - 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()); + 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 } => { - let active_document = self.active_document(); - if !active_document.is_saved() { - responses.push_back(PortfolioMessage::AutoSaveDocument { document_id: self.active_document_id }.into()); + 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(ToolMessage::AbortCurrentTool.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(FrontendMessage::UpdateActiveDocument { document_id }.into()); responses.push_back(RenderDocument.into()); responses.push_back(DocumentMessage::DocumentStructureChanged.into()); @@ -467,12 +465,11 @@ impl MessageHandler for Port responses.push_back(ToolMessage::DocumentIsDirty.into()); responses.push_back(PortfolioMessage::UpdateDocumentWidgets.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 @@ -503,14 +500,17 @@ impl MessageHandler 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 } } diff --git a/frontend/src/components/widgets/inputs/SwatchPairInput.vue b/frontend/src/components/widgets/inputs/SwatchPairInput.vue index f2b054ddfa..9f67716a30 100644 --- a/frontend/src/components/widgets/inputs/SwatchPairInput.vue +++ b/frontend/src/components/widgets/inputs/SwatchPairInput.vue @@ -138,8 +138,8 @@ export default defineComponent({ secondaryButton.style.setProperty("--swatch-color", updateWorkingColors.secondary.toRgbaCSS()); }); - this.updatePrimaryColor(); - this.updateSecondaryColor(); + // this.updatePrimaryColor(); + // this.updateSecondaryColor(); }, }); diff --git a/frontend/src/io-managers/input.ts b/frontend/src/io-managers/input.ts index 2fe03a9c9a..817f45162b 100644 --- a/frontend/src/io-managers/input.ts +++ b/frontend/src/io-managers/input.ts @@ -354,7 +354,7 @@ export function createInputManager(editor: Editor, container: HTMLElement, dialo // Bind the event listeners bindListeners(); // Resize on creation - onWindowResize(container); + setTimeout(() => onWindowResize(container), 30); // Return the destructor return unbindListeners; diff --git a/frontend/wasm/src/editor_api.rs b/frontend/wasm/src/editor_api.rs index eee3e50874..55fd78f8a7 100644 --- a/frontend/wasm/src/editor_api.rs +++ b/frontend/wasm/src/editor_api.rs @@ -106,11 +106,11 @@ impl JsEditorHandle { // ======================================================================== pub fn init_app(&self) { - let message = PortfolioMessage::UpdateOpenDocumentsList; - self.dispatch(message); + // let message = PortfolioMessage::NewDocument; + // self.dispatch(message); - let message = PortfolioMessage::UpdateDocumentWidgets; - self.dispatch(message); + // let message = PortfolioMessage::UpdateDocumentWidgets; + // self.dispatch(message); let message = ToolMessage::InitTools; self.dispatch(message); @@ -120,8 +120,8 @@ impl JsEditorHandle { let message = FrontendMessage::TriggerFontLoad { font, is_default: true }; self.dispatch(message); - let message = MovementMessage::TranslateCanvas { delta: (0., 0.).into() }; - self.dispatch(message); + // let message = MovementMessage::TranslateCanvas { delta: (0., 0.).into() }; + // self.dispatch(message); let message = MenuBarMessage::SendLayout; self.dispatch(message); From 5bac4c5fd5220fdd73ee6845ba36781bb30cf161 Mon Sep 17 00:00:00 2001 From: Keavon Chambers Date: Tue, 28 Jun 2022 23:23:09 -0700 Subject: [PATCH 02/21] Add frontend for the empty panel screen --- .../graphics/graphite-logotype-solid.svg | 11 +++ .../src/components/window/workspace/Panel.vue | 89 ++++++++++++++++++- .../components/window/workspace/Workspace.vue | 2 +- frontend/src/utility-functions/icons.ts | 10 ++- frontend/wasm/src/editor_api.rs | 10 +++ 5 files changed, 118 insertions(+), 4 deletions(-) create mode 100644 frontend/assets/graphics/graphite-logotype-solid.svg diff --git a/frontend/assets/graphics/graphite-logotype-solid.svg b/frontend/assets/graphics/graphite-logotype-solid.svg new file mode 100644 index 0000000000..466bb496c8 --- /dev/null +++ b/frontend/assets/graphics/graphite-logotype-solid.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/frontend/src/components/window/workspace/Panel.vue b/frontend/src/components/window/workspace/Panel.vue index 0faa9ad4dc..d108f5a655 100644 --- a/frontend/src/components/window/workspace/Panel.vue +++ b/frontend/src/components/window/workspace/Panel.vue @@ -21,7 +21,36 @@ - + + + + + + + + + + + + + + + + + New Document: + Open Document: + + + + + + + + + + + + @@ -141,6 +170,45 @@ flex: 1 1 100%; flex-direction: column; min-height: 0; + + .empty-panel { + background: var(--color-2-mildblack); + margin: 4px; + border-radius: 2px; + justify-content: center; + + .content { + flex: 0 0 auto; + align-items: center; + + .logotype { + margin-bottom: 40px; + + svg { + width: auto; + height: 120px; + } + } + + .actions { + > div { + gap: 8px; + + > * { + height: 24px; + } + + .text-label { + line-height: 24px; + } + + .user-input-label { + margin: 0; + } + } + } + } + } } } @@ -156,6 +224,10 @@ import NodeGraph from "@/components/panels/NodeGraph.vue"; import Properties from "@/components/panels/Properties.vue"; import IconButton from "@/components/widgets/buttons/IconButton.vue"; import PopoverButton from "@/components/widgets/buttons/PopoverButton.vue"; +import IconLabel from "@/components/widgets/labels/IconLabel.vue"; +import Separator from "@/components/widgets/labels/Separator.vue"; +import TextLabel from "@/components/widgets/labels/TextLabel.vue"; +import UserInputLabel from "@/components/widgets/labels/UserInputLabel.vue"; const panelComponents = { Document, @@ -168,18 +240,31 @@ const panelComponents = { type PanelTypes = keyof typeof panelComponents; export default defineComponent({ + inject: ["editor"], props: { tabMinWidths: { type: Boolean as PropType, default: false }, tabCloseButtons: { type: Boolean as PropType, default: false }, tabLabels: { type: Array as PropType, required: true }, tabActiveIndex: { type: Number as PropType, required: true }, - panelType: { type: String as PropType, required: true }, + panelType: { type: String as PropType, required: false }, clickAction: { type: Function as PropType<(index: number) => void>, required: false }, closeAction: { type: Function as PropType<(index: number) => void>, required: false }, }, + methods: { + newDocument() { + this.editor.instance.new_document_dialog(); + }, + openDocument() { + this.editor.instance.open_file_upload(); + }, + }, components: { LayoutCol, LayoutRow, + IconLabel, + TextLabel, + UserInputLabel, + Separator, ...panelComponents, }, }); diff --git a/frontend/src/components/window/workspace/Workspace.vue b/frontend/src/components/window/workspace/Workspace.vue index bd6cd791d2..28a02186ae 100644 --- a/frontend/src/components/window/workspace/Workspace.vue +++ b/frontend/src/components/window/workspace/Workspace.vue @@ -4,7 +4,7 @@ = ICON_LIST; export const iconComponents = Object.fromEntries(Object.entries(icons).map(([name, data]) => [name, data.component])); export type IconName = keyof typeof icons; -export type IconSize = 12 | 16 | 24 | 32; +export type IconSize = null | 12 | 16 | 24 | 32; export type IconStyle = "node" | ""; // The following helper type declarations allow us to avoid manually maintaining the `IconName` type declaration as a string union paralleling the keys of the diff --git a/frontend/wasm/src/editor_api.rs b/frontend/wasm/src/editor_api.rs index 55fd78f8a7..a61544364e 100644 --- a/frontend/wasm/src/editor_api.rs +++ b/frontend/wasm/src/editor_api.rs @@ -167,6 +167,16 @@ impl JsEditorHandle { self.dispatch(message); } + pub fn new_document_dialog(&self) { + let message = DialogMessage::RequestNewDocumentDialog; + self.dispatch(message); + } + + pub fn open_file_upload(&self) { + let message = FrontendMessage::TriggerFileUpload; + self.dispatch(message); + } + pub fn open_document_file(&self, document_name: String, document_serialized_content: String) { let message = PortfolioMessage::OpenDocumentFile { document_name, From b5720a62db8754d859abba1d116ef0b86c8fae81 Mon Sep 17 00:00:00 2001 From: Keavon Chambers Date: Wed, 29 Jun 2022 17:04:59 -0700 Subject: [PATCH 03/21] Add an icon for Folder based on NodeFolder --- frontend/assets/icon-16px-solid/folder.svg | 4 ++++ frontend/src/components/window/workspace/Panel.vue | 2 +- frontend/src/utility-functions/icons.ts | 2 ++ 3 files changed, 7 insertions(+), 1 deletion(-) create mode 100644 frontend/assets/icon-16px-solid/folder.svg diff --git a/frontend/assets/icon-16px-solid/folder.svg b/frontend/assets/icon-16px-solid/folder.svg new file mode 100644 index 0000000000..0f9cb44015 --- /dev/null +++ b/frontend/assets/icon-16px-solid/folder.svg @@ -0,0 +1,4 @@ + + + + diff --git a/frontend/src/components/window/workspace/Panel.vue b/frontend/src/components/window/workspace/Panel.vue index d108f5a655..1d2c2384a8 100644 --- a/frontend/src/components/window/workspace/Panel.vue +++ b/frontend/src/components/window/workspace/Panel.vue @@ -30,7 +30,7 @@ - + diff --git a/frontend/src/utility-functions/icons.ts b/frontend/src/utility-functions/icons.ts index 61d2d0e3e8..2a453d632e 100644 --- a/frontend/src/utility-functions/icons.ts +++ b/frontend/src/utility-functions/icons.ts @@ -90,6 +90,7 @@ import EyeVisible from "@/../assets/icon-16px-solid/eye-visible.svg"; import File from "@/../assets/icon-16px-solid/file.svg"; import FlipHorizontal from "@/../assets/icon-16px-solid/flip-horizontal.svg"; import FlipVertical from "@/../assets/icon-16px-solid/flip-vertical.svg"; +import Folder from "@/../assets/icon-16px-solid/folder.svg"; import GraphiteLogo from "@/../assets/icon-16px-solid/graphite-logo.svg"; import NodeArtboard from "@/../assets/icon-16px-solid/node-artboard.svg"; import NodeBlur from "@/../assets/icon-16px-solid/node-blur.svg"; @@ -137,6 +138,7 @@ const SOLID_16PX = { File: { component: File, size: 16 }, FlipHorizontal: { component: FlipHorizontal, size: 16 }, FlipVertical: { component: FlipVertical, size: 16 }, + Folder: { component: Folder, size: 16 }, GraphiteLogo: { component: GraphiteLogo, size: 16 }, NodeArtboard: { component: NodeArtboard, size: 16 }, NodeBlur: { component: NodeBlur, size: 16 }, From dd7c73f40ee194e88bf30b4c92f82aa0cc118af7 Mon Sep 17 00:00:00 2001 From: mfish33 Date: Thu, 30 Jun 2022 00:03:21 -0600 Subject: [PATCH 04/21] fixed messages causing peicees of ui not to render on new document --- editor/src/document/portfolio_message.rs | 1 - .../src/document/portfolio_message_handler.rs | 19 ++++++++----------- frontend/src/components/panels/Document.vue | 3 +++ frontend/src/io-managers/input.ts | 2 +- frontend/wasm/src/editor_api.rs | 15 --------------- 5 files changed, 12 insertions(+), 28 deletions(-) diff --git a/editor/src/document/portfolio_message.rs b/editor/src/document/portfolio_message.rs index 5cf987cf48..0372f1b3cb 100644 --- a/editor/src/document/portfolio_message.rs +++ b/editor/src/document/portfolio_message.rs @@ -48,7 +48,6 @@ pub enum PortfolioMessage { font: Font, is_default: bool, }, - NewDocument, NewDocumentWithName { name: String, }, diff --git a/editor/src/document/portfolio_message_handler.rs b/editor/src/document/portfolio_message_handler.rs index 632dfb0cb7..359d58a964 100644 --- a/editor/src/document/portfolio_message_handler.rs +++ b/editor/src/document/portfolio_message_handler.rs @@ -85,9 +85,13 @@ impl PortfolioMessageHandler { }) .collect::>(); - responses.push_back(FrontendMessage::UpdateOpenDocumentsList { open_documents }.into()); + // use push front to avoid messages accidentally targeting old document + responses.push_front(PortfolioMessage::SelectDocument { document_id }.into()); - responses.push_back(PortfolioMessage::SelectDocument { document_id }.into()); + responses.push_back(FrontendMessage::UpdateOpenDocumentsList { open_documents }.into()); + responses.push_back(PortfolioMessage::UpdateDocumentWidgets.into()); + responses.push_back(ToolMessage::InitTools.into()); + responses.push_back(MenuBarMessage::SendLayout.into()); } /// Returns an iterator over the open documents in order. @@ -274,18 +278,12 @@ impl MessageHandler for Port 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(ToolMessage::AbortCurrentTool.into()); - - self.load_document(new_document, document_id, responses); - } NewDocumentWithName { name } => { let new_document = DocumentMessageHandler::with_name(name, ipp); let document_id = generate_uuid(); responses.push_back(ToolMessage::AbortCurrentTool.into()); + responses.push_back(MovementMessage::TranslateCanvas { delta: (0., 0.).into() }.into()); + self.load_document(new_document, document_id, responses); } NextDocument => { @@ -491,7 +489,6 @@ impl MessageHandler for Port fn actions(&self) -> ActionList { let mut common = actions!(PortfolioMessageDiscriminant; - NewDocument, CloseActiveDocumentWithConfirmation, CloseAllDocuments, NextDocument, diff --git a/frontend/src/components/panels/Document.vue b/frontend/src/components/panels/Document.vue index 01f072ecd5..218c08de8a 100644 --- a/frontend/src/components/panels/Document.vue +++ b/frontend/src/components/panels/Document.vue @@ -442,6 +442,9 @@ export default defineComponent({ this.editor.instance.set_image_blob_url(element.path, url, image.width, image.height); }); }); + + // trigger resize on mount to send document bounds to the backend + window.dispatchEvent(new Event("resize")); }, data() { return { diff --git a/frontend/src/io-managers/input.ts b/frontend/src/io-managers/input.ts index 817f45162b..2fe03a9c9a 100644 --- a/frontend/src/io-managers/input.ts +++ b/frontend/src/io-managers/input.ts @@ -354,7 +354,7 @@ export function createInputManager(editor: Editor, container: HTMLElement, dialo // Bind the event listeners bindListeners(); // Resize on creation - setTimeout(() => onWindowResize(container), 30); + onWindowResize(container); // Return the destructor return unbindListeners; diff --git a/frontend/wasm/src/editor_api.rs b/frontend/wasm/src/editor_api.rs index a61544364e..2203139840 100644 --- a/frontend/wasm/src/editor_api.rs +++ b/frontend/wasm/src/editor_api.rs @@ -106,25 +106,10 @@ impl JsEditorHandle { // ======================================================================== pub fn init_app(&self) { - // let message = PortfolioMessage::NewDocument; - // self.dispatch(message); - - // let message = PortfolioMessage::UpdateDocumentWidgets; - // self.dispatch(message); - - let message = ToolMessage::InitTools; - self.dispatch(message); - // A default font let font = graphene::layers::text_layer::Font::new(DEFAULT_FONT_FAMILY.into(), DEFAULT_FONT_STYLE.into()); let message = FrontendMessage::TriggerFontLoad { font, is_default: true }; self.dispatch(message); - - // let message = MovementMessage::TranslateCanvas { delta: (0., 0.).into() }; - // self.dispatch(message); - - let message = MenuBarMessage::SendLayout; - self.dispatch(message); } /// Displays a dialog with an error message From 370e7c1250787000383fc3a38bc7372d66e56f1c Mon Sep 17 00:00:00 2001 From: Keavon Chambers Date: Thu, 30 Jun 2022 20:10:05 -0700 Subject: [PATCH 05/21] Standardize nextTick syntax --- .../floating-menus/FloatingMenu.vue | 29 ++++++----- frontend/src/components/panels/Document.vue | 50 +++++++++---------- frontend/src/components/panels/LayerTree.vue | 18 +++---- .../components/widgets/inputs/FontInput.vue | 1 + .../components/window/workspace/Workspace.vue | 14 +++--- 5 files changed, 58 insertions(+), 54 deletions(-) diff --git a/frontend/src/components/floating-menus/FloatingMenu.vue b/frontend/src/components/floating-menus/FloatingMenu.vue index ba9852296f..9fab5007d9 100644 --- a/frontend/src/components/floating-menus/FloatingMenu.vue +++ b/frontend/src/components/floating-menus/FloatingMenu.vue @@ -175,7 +175,7 @@ diff --git a/frontend/src/state-providers/panels.ts b/frontend/src/state-providers/panels.ts index a34d55475b..eb9a0e64f7 100644 --- a/frontend/src/state-providers/panels.ts +++ b/frontend/src/state-providers/panels.ts @@ -16,6 +16,7 @@ import { UpdateMouseCursor, UpdateToolOptionsLayout, UpdateToolShelfLayout, + UpdateWorkingColorsLayout, } from "@/wasm-communication/messages"; import DocumentComponent from "@/components/panels/Document.vue"; @@ -95,6 +96,10 @@ export function createPanelsState(editor: Editor) { await nextTick(); state.documentPanel.updateToolShelfLayout(updateToolShelfLayout); }); + editor.subscriptions.subscribeJsMessage(UpdateWorkingColorsLayout, async (updateWorkingColorsLayout) => { + await nextTick(); + state.documentPanel.updateWorkingColorsLayout(updateWorkingColorsLayout); + }); // Resize elements to render the new viewport size editor.subscriptions.subscribeJsMessage(TriggerViewportResize, async () => { diff --git a/frontend/src/wasm-communication/messages.ts b/frontend/src/wasm-communication/messages.ts index d00c60d602..2747d2918b 100644 --- a/frontend/src/wasm-communication/messages.ts +++ b/frontend/src/wasm-communication/messages.ts @@ -461,6 +461,13 @@ export class UpdateToolShelfLayout extends JsMessage implements WidgetLayout { layout!: LayoutGroup[]; } +export class UpdateWorkingColorsLayout extends JsMessage implements WidgetLayout { + layout_target!: unknown; + + @Transform(({ value }) => createWidgetLayout(value)) + layout!: LayoutGroup[]; +} + export class UpdatePropertyPanelOptionsLayout extends JsMessage implements WidgetLayout { layout_target!: unknown; @@ -615,6 +622,7 @@ export const messageMakers: Record = { UpdateDocumentModeLayout, UpdateToolOptionsLayout, UpdateWorkingColors, + UpdateWorkingColorsLayout, UpdateMenuBarLayout, } as const; export type JsMessageType = keyof typeof messageMakers; diff --git a/frontend/wasm/src/editor_api.rs b/frontend/wasm/src/editor_api.rs index 2203139840..bde5b32908 100644 --- a/frontend/wasm/src/editor_api.rs +++ b/frontend/wasm/src/editor_api.rs @@ -337,18 +337,6 @@ impl JsEditorHandle { Ok(()) } - /// Swap primary and secondary color - pub fn swap_colors(&self) { - let message = ToolMessage::SwapColors; - self.dispatch(message); - } - - /// Reset primary and secondary colors to their defaults - pub fn reset_colors(&self) { - let message = ToolMessage::ResetColors; - self.dispatch(message); - } - /// Paste layers from a serialized json representation pub fn paste_serialized_data(&self, data: String) { let message = PortfolioMessage::PasteSerializedData { data }; From b917e93ffbafbd506153387f23b87a3bf5575e73 Mon Sep 17 00:00:00 2001 From: Keavon Chambers Date: Thu, 21 Jul 2022 00:40:33 -0700 Subject: [PATCH 15/21] Standardize action macro formatting --- editor/src/dialog/dialog_message_handler.rs | 6 +++++- editor/src/document/overlays_message_handler.rs | 4 +++- editor/src/global/global_message_handler.rs | 6 +++++- editor/src/misc/macros.rs | 5 ++++- editor/src/viewport_tools/tool_message.rs | 1 - .../src/viewport_tools/tool_message_handler.rs | 7 ++++++- editor/src/viewport_tools/tools/artboard_tool.rs | 8 +++++++- editor/src/viewport_tools/tools/ellipse_tool.rs | 10 ++++++++-- .../src/viewport_tools/tools/eyedropper_tool.rs | 5 ++++- editor/src/viewport_tools/tools/fill_tool.rs | 5 ++++- editor/src/viewport_tools/tools/freehand_tool.rs | 12 ++++++++++-- editor/src/viewport_tools/tools/gradient_tool.rs | 7 ++++++- editor/src/viewport_tools/tools/line_tool.rs | 10 ++++++++-- editor/src/viewport_tools/tools/navigate_tool.rs | 12 ++++++++++-- editor/src/viewport_tools/tools/path_tool.rs | 11 +++++++++-- editor/src/viewport_tools/tools/pen_tool.rs | 16 ++++++++++++++-- .../src/viewport_tools/tools/rectangle_tool.rs | 10 ++++++++-- editor/src/viewport_tools/tools/select_tool.rs | 14 ++++++++++++-- editor/src/viewport_tools/tools/shape_tool.rs | 10 ++++++++-- editor/src/viewport_tools/tools/spline_tool.rs | 15 +++++++++++++-- editor/src/viewport_tools/tools/text_tool.rs | 10 ++++++++-- 21 files changed, 152 insertions(+), 32 deletions(-) diff --git a/editor/src/dialog/dialog_message_handler.rs b/editor/src/dialog/dialog_message_handler.rs index ea6c363290..c2512302a4 100644 --- a/editor/src/dialog/dialog_message_handler.rs +++ b/editor/src/dialog/dialog_message_handler.rs @@ -101,5 +101,9 @@ impl MessageHandler for DialogMessageHa } } - advertise_actions!(DialogMessageDiscriminant;RequestNewDocumentDialog,RequestExportDialog,CloseAllDocumentsWithConfirmation); + advertise_actions!(DialogMessageDiscriminant; + RequestNewDocumentDialog, + RequestExportDialog, + CloseAllDocumentsWithConfirmation, + ); } diff --git a/editor/src/document/overlays_message_handler.rs b/editor/src/document/overlays_message_handler.rs index b0a33d61c5..a6f12fd406 100644 --- a/editor/src/document/overlays_message_handler.rs +++ b/editor/src/document/overlays_message_handler.rs @@ -45,6 +45,8 @@ impl MessageHandler ActionList { - actions!(OverlaysMessageDiscriminant; ClearAllOverlays) + actions!(OverlaysMessageDiscriminant; + ClearAllOverlays, + ) } } diff --git a/editor/src/global/global_message_handler.rs b/editor/src/global/global_message_handler.rs index 6e6b88df43..6f0a4df1d9 100644 --- a/editor/src/global/global_message_handler.rs +++ b/editor/src/global/global_message_handler.rs @@ -27,5 +27,9 @@ impl MessageHandler for GlobalMessageHandler { } } - advertise_actions!(GlobalMessageDiscriminant; LogInfo, LogDebug, LogTrace); + advertise_actions!(GlobalMessageDiscriminant; + LogInfo, + LogDebug, + LogTrace, + ); } diff --git a/editor/src/misc/macros.rs b/editor/src/misc/macros.rs index 702efffcff..221c149ced 100644 --- a/editor/src/misc/macros.rs +++ b/editor/src/misc/macros.rs @@ -13,7 +13,10 @@ /// /// and /// ```ignore -/// actions!(DocumentMessage; Undo, Redo); +/// actions!(DocumentMessage; +/// Undo, +/// Redo, +/// ); /// ``` /// /// expands to: diff --git a/editor/src/viewport_tools/tool_message.rs b/editor/src/viewport_tools/tool_message.rs index 0924d0efce..93a51300dd 100644 --- a/editor/src/viewport_tools/tool_message.rs +++ b/editor/src/viewport_tools/tool_message.rs @@ -75,7 +75,6 @@ pub enum ToolMessage { // Detail(DetailToolMessage), // Messages - #[remain::unsorted] ActivateTool { tool_type: ToolType, }, diff --git a/editor/src/viewport_tools/tool_message_handler.rs b/editor/src/viewport_tools/tool_message_handler.rs index bf38b3c59e..e85438b068 100644 --- a/editor/src/viewport_tools/tool_message_handler.rs +++ b/editor/src/viewport_tools/tool_message_handler.rs @@ -160,7 +160,12 @@ impl MessageHandler 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 diff --git a/editor/src/viewport_tools/tools/artboard_tool.rs b/editor/src/viewport_tools/tools/artboard_tool.rs index 71fc038da6..6e039c1d3b 100644 --- a/editor/src/viewport_tools/tools/artboard_tool.rs +++ b/editor/src/viewport_tools/tools/artboard_tool.rs @@ -73,7 +73,13 @@ impl<'a> MessageHandler> for ArtboardTool } } - advertise_actions!(ArtboardToolMessageDiscriminant; PointerDown, PointerUp, PointerMove, DeleteSelected, Abort); + advertise_actions!(ArtboardToolMessageDiscriminant; + PointerDown, + PointerUp, + PointerMove, + DeleteSelected, + Abort, + ); } impl PropertyHolder for ArtboardTool {} diff --git a/editor/src/viewport_tools/tools/ellipse_tool.rs b/editor/src/viewport_tools/tools/ellipse_tool.rs index eb4db7b311..1118f6b402 100644 --- a/editor/src/viewport_tools/tools/ellipse_tool.rs +++ b/editor/src/viewport_tools/tools/ellipse_tool.rs @@ -75,8 +75,14 @@ impl<'a> MessageHandler> 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, + ), } } } diff --git a/editor/src/viewport_tools/tools/eyedropper_tool.rs b/editor/src/viewport_tools/tools/eyedropper_tool.rs index b903055b87..1c62f5dcb4 100644 --- a/editor/src/viewport_tools/tools/eyedropper_tool.rs +++ b/editor/src/viewport_tools/tools/eyedropper_tool.rs @@ -66,7 +66,10 @@ impl<'a> MessageHandler> for EyedropperTo } } - advertise_actions!(EyedropperToolMessageDiscriminant; LeftMouseDown, RightMouseDown); + advertise_actions!(EyedropperToolMessageDiscriminant; + LeftMouseDown, + RightMouseDown, + ); } impl ToolTransition for EyedropperTool { diff --git a/editor/src/viewport_tools/tools/fill_tool.rs b/editor/src/viewport_tools/tools/fill_tool.rs index 100cae7fc8..9394b935fa 100644 --- a/editor/src/viewport_tools/tools/fill_tool.rs +++ b/editor/src/viewport_tools/tools/fill_tool.rs @@ -67,7 +67,10 @@ impl<'a> MessageHandler> for FillTool { } } - advertise_actions!(FillToolMessageDiscriminant; LeftMouseDown, RightMouseDown); + advertise_actions!(FillToolMessageDiscriminant; + LeftMouseDown, + RightMouseDown, + ); } impl ToolTransition for FillTool { diff --git a/editor/src/viewport_tools/tools/freehand_tool.rs b/editor/src/viewport_tools/tools/freehand_tool.rs index 17578c0de5..5cc4605752 100644 --- a/editor/src/viewport_tools/tools/freehand_tool.rs +++ b/editor/src/viewport_tools/tools/freehand_tool.rs @@ -115,8 +115,16 @@ impl<'a> MessageHandler> 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, + ), } } } diff --git a/editor/src/viewport_tools/tools/gradient_tool.rs b/editor/src/viewport_tools/tools/gradient_tool.rs index 9521602f84..a0a380bb59 100644 --- a/editor/src/viewport_tools/tools/gradient_tool.rs +++ b/editor/src/viewport_tools/tools/gradient_tool.rs @@ -98,7 +98,12 @@ impl<'a> MessageHandler> for GradientTool } } - advertise_actions!(GradientToolMessageDiscriminant; PointerDown, PointerUp, PointerMove, Abort); + advertise_actions!(GradientToolMessageDiscriminant; + PointerDown, + PointerUp, + PointerMove, + Abort, + ); } impl PropertyHolder for GradientTool { diff --git a/editor/src/viewport_tools/tools/line_tool.rs b/editor/src/viewport_tools/tools/line_tool.rs index 901cf841f9..a12c103ce0 100644 --- a/editor/src/viewport_tools/tools/line_tool.rs +++ b/editor/src/viewport_tools/tools/line_tool.rs @@ -116,8 +116,14 @@ impl<'a> MessageHandler> 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, + ), } } } diff --git a/editor/src/viewport_tools/tools/navigate_tool.rs b/editor/src/viewport_tools/tools/navigate_tool.rs index 20d970de95..f4ec3ba8d2 100644 --- a/editor/src/viewport_tools/tools/navigate_tool.rs +++ b/editor/src/viewport_tools/tools/navigate_tool.rs @@ -75,8 +75,16 @@ impl<'a> MessageHandler> 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, + ), } } } diff --git a/editor/src/viewport_tools/tools/path_tool.rs b/editor/src/viewport_tools/tools/path_tool.rs index cc3259dae4..6c91285145 100644 --- a/editor/src/viewport_tools/tools/path_tool.rs +++ b/editor/src/viewport_tools/tools/path_tool.rs @@ -85,8 +85,15 @@ impl<'a> MessageHandler> 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, + ), } } } diff --git a/editor/src/viewport_tools/tools/pen_tool.rs b/editor/src/viewport_tools/tools/pen_tool.rs index e833144e43..4539de2735 100644 --- a/editor/src/viewport_tools/tools/pen_tool.rs +++ b/editor/src/viewport_tools/tools/pen_tool.rs @@ -131,8 +131,20 @@ impl<'a> MessageHandler> 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, + ), } } } diff --git a/editor/src/viewport_tools/tools/rectangle_tool.rs b/editor/src/viewport_tools/tools/rectangle_tool.rs index 4de0cbbb06..55c76a8d58 100644 --- a/editor/src/viewport_tools/tools/rectangle_tool.rs +++ b/editor/src/viewport_tools/tools/rectangle_tool.rs @@ -63,8 +63,14 @@ impl<'a> MessageHandler> 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, + ), } } } diff --git a/editor/src/viewport_tools/tools/select_tool.rs b/editor/src/viewport_tools/tools/select_tool.rs index 617749c704..64c32c1e42 100644 --- a/editor/src/viewport_tools/tools/select_tool.rs +++ b/editor/src/viewport_tools/tools/select_tool.rs @@ -266,8 +266,18 @@ impl<'a> MessageHandler> 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, + ), } } } diff --git a/editor/src/viewport_tools/tools/shape_tool.rs b/editor/src/viewport_tools/tools/shape_tool.rs index 0ac1f4d6d7..2c56e51975 100644 --- a/editor/src/viewport_tools/tools/shape_tool.rs +++ b/editor/src/viewport_tools/tools/shape_tool.rs @@ -114,8 +114,14 @@ impl<'a> MessageHandler> 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, + ), } } } diff --git a/editor/src/viewport_tools/tools/spline_tool.rs b/editor/src/viewport_tools/tools/spline_tool.rs index aa5f929e13..b75a2f3bc4 100644 --- a/editor/src/viewport_tools/tools/spline_tool.rs +++ b/editor/src/viewport_tools/tools/spline_tool.rs @@ -119,8 +119,19 @@ impl<'a> MessageHandler> 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, + ), } } } diff --git a/editor/src/viewport_tools/tools/text_tool.rs b/editor/src/viewport_tools/tools/text_tool.rs index e4773b5919..f5d465d0e3 100644 --- a/editor/src/viewport_tools/tools/text_tool.rs +++ b/editor/src/viewport_tools/tools/text_tool.rs @@ -169,8 +169,14 @@ impl<'a> MessageHandler> 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, + ), } } } From f28f2ab619614dbeeb2418acb035ece1c0955586 Mon Sep 17 00:00:00 2001 From: Keavon Chambers Date: Thu, 21 Jul 2022 00:41:29 -0700 Subject: [PATCH 16/21] Provide typing for widgets in TS/Vue and associated cleanup --- editor/src/dialog/dialogs/export_dialog.rs | 1 + .../src/dialog/dialogs/new_document_dialog.rs | 1 + .../src/document/document_message_handler.rs | 12 +- .../properties_panel_message_handler.rs | 29 +- editor/src/frontend/frontend_message.rs | 3 +- editor/src/layout/widgets.rs | 409 ++++++++------ .../viewport_tools/tool_message_handler.rs | 19 +- .../src/viewport_tools/tools/select_tool.rs | 9 +- editor/src/viewport_tools/tools/text_tool.rs | 2 + .../components/floating-menus/MenuList.vue | 19 +- frontend/src/components/panels/LayerTree.vue | 8 +- frontend/src/components/panels/NodeGraph.vue | 16 +- frontend/src/components/widgets/WidgetRow.vue | 47 +- .../components/widgets/buttons/IconButton.vue | 8 +- .../widgets/buttons/PopoverButton.vue | 4 +- .../components/widgets/buttons/TextButton.ts | 9 +- .../components/widgets/buttons/TextButton.vue | 7 +- .../widgets/inputs/CheckboxInput.vue | 12 +- .../components/widgets/inputs/ColorInput.vue | 14 +- .../widgets/inputs/DropdownInput.vue | 4 +- .../components/widgets/inputs/FontInput.vue | 7 +- .../widgets/inputs/MenuBarInput.vue | 6 +- .../components/widgets/inputs/NumberInput.vue | 19 +- .../widgets/inputs/OptionalInput.vue | 3 +- .../components/widgets/inputs/RadioInput.vue | 12 +- .../widgets/inputs/SwatchPairInput.vue | 57 +- .../components/widgets/labels/IconLabel.vue | 13 +- .../components/widgets/labels/Separator.vue | 3 +- frontend/src/io-managers/panic.ts | 37 +- frontend/src/state-providers/panels.ts | 2 + frontend/src/utility-functions/icons.ts | 2 +- frontend/src/wasm-communication/messages.ts | 523 +++++++++++++----- frontend/wasm/src/helpers.rs | 13 +- 33 files changed, 804 insertions(+), 526 deletions(-) diff --git a/editor/src/dialog/dialogs/export_dialog.rs b/editor/src/dialog/dialogs/export_dialog.rs index ed8090edd8..657eabc5c1 100644 --- a/editor/src/dialog/dialogs/export_dialog.rs +++ b/editor/src/dialog/dialogs/export_dialog.rs @@ -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() })), ]; diff --git a/editor/src/dialog/dialogs/new_document_dialog.rs b/editor/src/dialog/dialogs/new_document_dialog.rs index dd86d563af..c1841841a3 100644 --- a/editor/src/dialog/dialogs/new_document_dialog.rs +++ b/editor/src/dialog/dialogs/new_document_dialog.rs @@ -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() })), ]; diff --git a/editor/src/document/document_message_handler.rs b/editor/src/document/document_message_handler.rs index 7501ea4c4a..9e176a9a21 100644 --- a/editor/src/document/document_message_handler.rs +++ b/editor/src/document/document_message_handler.rs @@ -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, diff --git a/editor/src/document/properties_panel_message_handler.rs b/editor/src/document/properties_panel_message_handler.rs index c7cf34a884..aac4016522 100644 --- a/editor/src/document/properties_panel_message_handler.rs +++ b/editor/src/document/properties_panel_message_handler.rs @@ -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::*; @@ -269,7 +269,7 @@ fn register_artboard_layer_properties(layer: &Layer, responses: &mut VecDeque 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 { @@ -481,14 +484,16 @@ fn register_artwork_layer_properties(layer: &Layer, responses: &mut VecDeque 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() })), ], }, @@ -736,6 +742,7 @@ fn node_section_font(layer: &TextLayer) -> LayoutGroup { } .into() }), + ..Default::default() })), ], }, @@ -761,6 +768,7 @@ fn node_section_font(layer: &TextLayer) -> LayoutGroup { } .into() }), + ..Default::default() })), ], }, @@ -1004,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() })), ], }, diff --git a/editor/src/frontend/frontend_message.rs b/editor/src/frontend/frontend_message.rs index 67bb443809..1616489aa8 100644 --- a/editor/src/frontend/frontend_message.rs +++ b/editor/src/frontend/frontend_message.rs @@ -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, font_size: f64, color: Color }, DisplayRemoveEditableTextbox, @@ -58,6 +58,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 }, } diff --git a/editor/src/layout/widgets.rs b/editor/src/layout/widgets.rs index 37f6caece2..36bed1cb61 100644 --- a/editor/src/layout/widgets.rs +++ b/editor/src/layout/widgets.rs @@ -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; #[remain::sorted] #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub enum LayoutGroup { + #[serde(rename = "column")] Column { #[serde(rename = "columnWidgets")] widgets: Vec, }, + #[serde(rename = "row")] Row { #[serde(rename = "rowWidgets")] widgets: Vec, }, - 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, } @@ -330,257 +332,350 @@ pub enum Widget { TextLabel(TextLabel), } -#[derive(Clone, Serialize, Deserialize, Derivative)] -#[derivative(Debug, PartialEq, Default)] -pub struct NumberInput { - pub value: Option, - #[serde(skip)] - #[derivative(Debug = "ignore", PartialEq = "ignore")] - pub on_update: WidgetCallback, - pub min: Option, - pub max: Option, - #[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, +#[derive(Clone, Serialize, Deserialize, Derivative, Default)] +#[derivative(Debug, PartialEq)] +pub struct CheckboxInput { + pub checked: bool, + + pub icon: String, + + pub tooltip: String, + + // Callbacks #[serde(skip)] #[derivative(Debug = "ignore", PartialEq = "ignore")] - pub increment_callback_decrease: WidgetCallback, - pub label: String, - pub unit: String, - #[serde(rename = "displayDecimalPlaces")] - #[derivative(Default(value = "3"))] - pub display_decimal_places: u32, - pub disabled: bool, + pub on_update: WidgetCallback, } #[derive(Clone, Serialize, Deserialize, Derivative)] #[derivative(Debug, PartialEq, Default)] -pub struct TextInput { - pub value: String, +pub struct ColorInput { + pub value: Option, + + pub label: Option, + + #[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, + pub on_update: WidgetCallback, } #[derive(Clone, Serialize, Deserialize, Derivative)] #[derivative(Debug, PartialEq, Default)] -pub struct TextAreaInput { - pub value: String, - #[serde(skip)] - #[derivative(Debug = "ignore", PartialEq = "ignore")] - pub on_update: WidgetCallback, +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, + + #[serde(rename = "drawIcon")] + pub draw_icon: bool, + + #[derivative(Default(value = "true"))] + pub interactive: bool, + + pub disabled: bool, + // + // Callbacks + // `on_update` exists on the `DropdownEntryData`, not this parent `DropdownInput` } -#[derive(Clone, Serialize, Deserialize, Derivative)] -#[derivative(Debug, PartialEq, Default)] -pub struct ColorInput { - pub value: Option, +pub type DropdownInputEntries = Vec>; + +#[derive(Clone, Serialize, Deserialize, Derivative, Default)] +#[derivative(Debug, PartialEq)] +pub struct DropdownEntryData { + pub value: String, + + pub label: String, + + pub icon: String, + + pub shortcut: Vec, + + #[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, - #[serde(rename = "canSetTransparent")] - #[derivative(Default(value = "true"))] - pub can_set_transparent: bool, + 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, } -#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)] -pub enum NumberInputIncrementBehavior { - Add, - Multiply, - Callback, -} +#[derive(Clone, Serialize, Deserialize, Derivative, Default)] +#[derivative(Debug, PartialEq)] +pub struct IconButton { + pub icon: String, -impl Default for NumberInputIncrementBehavior { - fn default() -> Self { - Self::Add - } -} + pub size: u32, // TODO: Convert to an `IconSize` enum -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub struct Separator { - pub direction: SeparatorDirection, + pub active: bool, - #[serde(rename = "type")] - pub separator_type: SeparatorType, + pub tooltip: String, + + // Callbacks + #[serde(skip)] + #[derivative(Debug = "ignore", PartialEq = "ignore")] + pub on_update: WidgetCallback, } -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub struct SwatchPairInput; +#[derive(Clone, Serialize, Deserialize, Derivative, Debug, Default, PartialEq, Eq)] +pub struct IconLabel { + pub icon: String, -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub enum SeparatorDirection { - Horizontal, - Vertical, + #[serde(rename = "iconStyle")] + pub icon_style: IconStyle, } -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub enum SeparatorType { - Related, - Unrelated, - Section, - List, +#[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 IconButton { - pub icon: String, - #[serde(rename = "title")] - pub tooltip: String, - pub size: u32, - pub active: bool, - #[serde(rename = "gapAfter")] - pub gap_after: bool, +pub struct Invisible { #[serde(skip)] #[derivative(Debug = "ignore", PartialEq = "ignore")] - pub on_update: WidgetCallback, + pub on_update: WidgetCallback<()>, } -#[derive(Clone, Serialize, Deserialize, Derivative, Default)] -#[derivative(Debug, PartialEq)] -#[serde(rename_all(serialize = "camelCase", deserialize = "camelCase"))] -pub struct TextButton { +#[derive(Clone, Serialize, Deserialize, Derivative)] +#[derivative(Debug, PartialEq, Default)] +pub struct NumberInput { pub label: String, - pub emphasized: bool, - pub min_width: u32, - pub gap_after: bool, + + pub value: Option, + + pub min: Option, + + pub max: Option, + + #[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, - pub disabled: bool, -} + pub on_update: WidgetCallback, -#[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, + pub increment_callback_increase: WidgetCallback, + + #[serde(skip)] + #[derivative(Debug = "ignore", PartialEq = "ignore")] + pub increment_callback_decrease: WidgetCallback, +} + +#[derive(Clone, Serialize, Deserialize, Debug, Default, PartialEq, Eq)] +pub enum NumberInputIncrementBehavior { + #[default] + Add, + Multiply, + Callback, } #[derive(Clone, Serialize, Deserialize, Derivative, Default)] #[derivative(Debug, PartialEq)] -pub struct CheckboxInput { +pub struct OptionalInput { pub checked: bool, + pub icon: String, - #[serde(rename = "title")] + pub tooltip: String, + + // Callbacks #[serde(skip)] #[derivative(Debug = "ignore", PartialEq = "ignore")] - pub on_update: WidgetCallback, + pub on_update: WidgetCallback, } -#[derive(Clone, Serialize, Deserialize, Derivative, Default)] -#[derivative(Debug, PartialEq)] +#[derive(Clone, Serialize, Deserialize, Derivative)] +#[derivative(Debug, PartialEq, Default)] pub struct PopoverButton { - pub title: String, + pub icon: Option, + + // Body + pub header: String, + pub text: String, } -#[derive(Clone, Serialize, Deserialize, Derivative)] -#[derivative(Debug, PartialEq, Default)] -pub struct DropdownInput { - pub entries: Vec>, - // 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) +#[derive(Clone, Serialize, Deserialize, Derivative, Default)] +#[derivative(Debug, PartialEq)] +pub struct RadioInput { + pub entries: Vec, + + // 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, - #[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, + pub selected_index: u32, } #[derive(Clone, Serialize, Deserialize, Derivative, Default)] #[derivative(Debug, PartialEq)] -pub struct DropdownEntryData { +pub struct RadioEntryData { pub value: String, + pub label: String, + pub icon: String, - pub shortcut: Vec, - #[serde(rename = "shortcutRequiresLock")] - pub shortcut_requires_lock: bool, - pub disabled: bool, - pub children: Vec>, + pub tooltip: String, + + // 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, +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct Separator { + pub direction: SeparatorDirection, - // 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, + #[serde(rename = "type")] + pub separator_type: SeparatorType, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub enum SeparatorDirection { + Horizontal, + Vertical, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub enum SeparatorType { + Related, + Unrelated, + Section, + List, +} + +#[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, + + 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 RadioEntryData { - pub value: String, +#[serde(rename_all(serialize = "camelCase", deserialize = "camelCase"))] +pub struct TextButton { pub label: String, - pub icon: String, - pub tooltip: String, + + pub emphasized: bool, + + #[serde(rename = "minWidth")] + pub min_width: u32, + + pub disabled: bool, + + // Callbacks #[serde(skip)] #[derivative(Debug = "ignore", PartialEq = "ignore")] - pub on_update: WidgetCallback<()>, + 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, +#[derive(Clone, Serialize, Deserialize, Derivative)] +#[derivative(Debug, PartialEq, Default)] +pub struct TextInput { + pub value: String, + + pub label: Option, + + pub disabled: bool, + + // Callbacks + #[serde(skip)] + #[derivative(Debug = "ignore", PartialEq = "ignore")] + pub on_update: WidgetCallback, } #[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, } diff --git a/editor/src/viewport_tools/tool_message_handler.rs b/editor/src/viewport_tools/tool_message_handler.rs index e85438b068..84f30e799d 100644 --- a/editor/src/viewport_tools/tool_message_handler.rs +++ b/editor/src/viewport_tools/tool_message_handler.rs @@ -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::{IconButton, Layout, LayoutGroup, PropertyHolder, SwatchPairInput, Widget, WidgetCallback, WidgetHolder, WidgetLayout}; +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; @@ -175,7 +177,10 @@ impl MessageHandler) { let layout = WidgetLayout::new(vec![ LayoutGroup::Row { - widgets: vec![WidgetHolder::new(Widget::SwatchPairInput(SwatchPairInput))], + widgets: vec![WidgetHolder::new(Widget::SwatchPairInput(SwatchPairInput { + primary: document_data.primary_color, + secondary: document_data.secondary_color, + }))], }, LayoutGroup::Row { widgets: vec![ @@ -204,12 +209,4 @@ fn update_working_colors(document_data: &DocumentToolData, responses: &mut VecDe } .into(), ); - - responses.push_back( - FrontendMessage::UpdateWorkingColors { - primary: document_data.primary_color, - secondary: document_data.secondary_color, - } - .into(), - ); } diff --git a/editor/src/viewport_tools/tools/select_tool.rs b/editor/src/viewport_tools/tools/select_tool.rs index 64c32c1e42..c3ac6f42f5 100644 --- a/editor/src/viewport_tools/tools/select_tool.rs +++ b/editor/src/viewport_tools/tools/select_tool.rs @@ -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() })), ], }])) diff --git a/editor/src/viewport_tools/tools/text_tool.rs b/editor/src/viewport_tools/tools/text_tool.rs index f5d465d0e3..14787334a9 100644 --- a/editor/src/viewport_tools/tools/text_tool.rs +++ b/editor/src/viewport_tools/tools/text_tool.rs @@ -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, diff --git a/frontend/src/components/floating-menus/MenuList.vue b/frontend/src/components/floating-menus/MenuList.vue index 2ad8b42606..cc168d3946 100644 --- a/frontend/src/components/floating-menus/MenuList.vue +++ b/frontend/src/components/floating-menus/MenuList.vue @@ -163,7 +163,7 @@ diff --git a/frontend/src/components/widgets/labels/IconLabel.vue b/frontend/src/components/widgets/labels/IconLabel.vue index 11f1052c9b..c5a9609894 100644 --- a/frontend/src/components/widgets/labels/IconLabel.vue +++ b/frontend/src/components/widgets/labels/IconLabel.vue @@ -1,5 +1,5 @@ @@ -42,16 +42,15 @@ import LayoutRow from "@/components/layout/LayoutRow.vue"; export default defineComponent({ props: { icon: { type: String as PropType, required: true }, - gapAfter: { type: Boolean as PropType, default: false }, - style: { type: String as PropType, default: "" }, + iconStyle: { type: String as PropType, required: false }, }, computed: { - iconSize(): string { + iconSizeClass(): string { return `size-${icons[this.icon].size}`; }, - iconStyle(): string { - if (!this.style) return ""; - return `${this.style}-style`; + iconStyleClass(): string { + if (!this.iconStyle || this.iconStyle === "Normal") return ""; + return `${this.iconStyle.toLowerCase()}-style`; }, }, components: { diff --git a/frontend/src/components/widgets/labels/Separator.vue b/frontend/src/components/widgets/labels/Separator.vue index 8f39d5a34e..c1b03303f0 100644 --- a/frontend/src/components/widgets/labels/Separator.vue +++ b/frontend/src/components/widgets/labels/Separator.vue @@ -75,8 +75,7 @@