diff --git a/client/web/src/components/panels/LayerTree.vue b/client/web/src/components/panels/LayerTree.vue index 7641f3586..03e129cb9 100644 --- a/client/web/src/components/panels/LayerTree.vue +++ b/client/web/src/components/panels/LayerTree.vue @@ -144,9 +144,9 @@ export default defineComponent({ async handleShiftClick(clickedLayer: LayerPanelEntry) { // The two paths of the range are stored in selectionRangeStartLayer and selectionRangeEndLayer // So for a new Shift+Click, select all layers between selectionRangeStartLayer and selectionRangeEndLayer(stored in prev Sft+C) - this.selectionRangeEndLayer = clickedLayer; - this.selectionRangeStartLayer = (this.selectionRangeStartLayer as LayerPanelEntry) || clickedLayer; this.clearSelection(); + this.selectionRangeEndLayer = clickedLayer; + if (!this.selectionRangeStartLayer) this.selectionRangeStartLayer = clickedLayer; this.fillSelectionRange(this.selectionRangeStartLayer, this.selectionRangeEndLayer, true); this.updateSelection(); }, @@ -159,11 +159,13 @@ export default defineComponent({ this.updateSelection(); }, async fillSelectionRange(start: LayerPanelEntry, end: LayerPanelEntry, selected = true) { - const startIndex = this.layers.indexOf(start); - const endIndex = this.layers.indexOf(end); + const startIndex = this.layers.findIndex((layer) => layer.path.join() === start.path.join()); + const endIndex = this.layers.findIndex((layer) => layer.path.join() === end.path.join()); const [min, max] = [startIndex, endIndex].sort(); - for (let i = min; i <= max; i += 1) { - this.layers[i].layer_data.selected = selected; + if (min !== -1) { + for (let i = min; i <= max; i += 1) { + this.layers[i].layer_data.selected = selected; + } } }, async clearSelection() { @@ -173,6 +175,7 @@ export default defineComponent({ }, async updateSelection() { const paths = this.layers.filter((layer) => layer.layer_data.selected).map((layer) => layer.path); + if (paths.length === 0) return; const length = paths.reduce((acc, cur) => acc + cur.length, 0) + paths.length - 1; const output = new BigUint64Array(length); let i = 0; diff --git a/client/web/wasm/src/wrappers.rs b/client/web/wasm/src/wrappers.rs index 1fb50f8fe..20e1807d7 100644 --- a/client/web/wasm/src/wrappers.rs +++ b/client/web/wasm/src/wrappers.rs @@ -119,6 +119,8 @@ pub fn translate_key(name: &str) -> Key { // When using linux + chrome + the neo keyboard layout, the shift key is recognized as caps "capslock" => KeyShift, "control" => KeyControl, + "delete" => KeyDelete, + "backspace" => KeyBackspace, "alt" => KeyAlt, "escape" => KeyEscape, _ => UnknownKey, diff --git a/core/document/src/document.rs b/core/document/src/document.rs index fb0338d69..63e9e0978 100644 --- a/core/document/src/document.rs +++ b/core/document/src/document.rs @@ -172,9 +172,10 @@ impl Document { pub fn handle_operation(&mut self, operation: Operation) -> Result>, DocumentError> { let responses = match &operation { Operation::AddCircle { path, insert_index, cx, cy, r, style } => { - self.add_layer(&path, Layer::new(LayerDataTypes::Circle(layers::Circle::new((*cx, *cy), *r, *style))), *insert_index)?; + let id = self.add_layer(&path, Layer::new(LayerDataTypes::Circle(layers::Circle::new((*cx, *cy), *r, *style))), *insert_index)?; + let path = [path.clone(), vec![id]].concat(); - Some(vec![DocumentResponse::DocumentChanged]) + Some(vec![DocumentResponse::DocumentChanged, DocumentResponse::SelectLayer { path }]) } Operation::AddEllipse { path, @@ -186,9 +187,10 @@ impl Document { rot, style, } => { - self.add_layer(&path, Layer::new(LayerDataTypes::Ellipse(layers::Ellipse::new((*cx, *cy), (*rx, *ry), *rot, *style))), *insert_index)?; + let id = self.add_layer(&path, Layer::new(LayerDataTypes::Ellipse(layers::Ellipse::new((*cx, *cy), (*rx, *ry), *rot, *style))), *insert_index)?; + let path = [path.clone(), vec![id]].concat(); - Some(vec![DocumentResponse::DocumentChanged]) + Some(vec![DocumentResponse::DocumentChanged, DocumentResponse::SelectLayer { path }]) } Operation::AddRect { path, @@ -199,9 +201,10 @@ impl Document { y1, style, } => { - self.add_layer(&path, Layer::new(LayerDataTypes::Rect(Rect::new((*x0, *y0), (*x1, *y1), *style))), *insert_index)?; + let id = self.add_layer(&path, Layer::new(LayerDataTypes::Rect(Rect::new((*x0, *y0), (*x1, *y1), *style))), *insert_index)?; + let path = [path.clone(), vec![id]].concat(); - Some(vec![DocumentResponse::DocumentChanged]) + Some(vec![DocumentResponse::DocumentChanged, DocumentResponse::SelectLayer { path }]) } Operation::AddLine { path, @@ -212,9 +215,10 @@ impl Document { y1, style, } => { - self.add_layer(&path, Layer::new(LayerDataTypes::Line(Line::new((*x0, *y0), (*x1, *y1), *style))), *insert_index)?; + let id = self.add_layer(&path, Layer::new(LayerDataTypes::Line(Line::new((*x0, *y0), (*x1, *y1), *style))), *insert_index)?; + let path = [path.clone(), vec![id]].concat(); - Some(vec![DocumentResponse::DocumentChanged]) + Some(vec![DocumentResponse::DocumentChanged, DocumentResponse::SelectLayer { path }]) } Operation::AddPen { path, insert_index, points, style } => { let points: Vec = points.iter().map(|&it| it.into()).collect(); @@ -233,9 +237,10 @@ impl Document { style, } => { let s = Shape::new((*x0, *y0), (*x1, *y1), *sides, *style); - self.add_layer(&path, Layer::new(LayerDataTypes::Shape(s)), *insert_index)?; + let id = self.add_layer(&path, Layer::new(LayerDataTypes::Shape(s)), *insert_index)?; + let path = [path.clone(), vec![id]].concat(); - Some(vec![DocumentResponse::DocumentChanged]) + Some(vec![DocumentResponse::DocumentChanged, DocumentResponse::SelectLayer { path }]) } Operation::DeleteLayer { path } => { self.delete(&path)?; @@ -281,8 +286,9 @@ impl Document { responses.append(&mut op_responses); } } + responses.extend(vec![DocumentResponse::DocumentChanged, DocumentResponse::FolderChanged { path }]); - Some(vec![DocumentResponse::DocumentChanged, DocumentResponse::FolderChanged { path }]) + Some(responses) } Operation::ToggleVisibility { path } => { let _ = self.layer_mut(&path).map(|layer| { diff --git a/core/document/src/response.rs b/core/document/src/response.rs index d86c1844c..c4e74bc96 100644 --- a/core/document/src/response.rs +++ b/core/document/src/response.rs @@ -7,6 +7,7 @@ use std::fmt; pub enum DocumentResponse { DocumentChanged, FolderChanged { path: Vec }, + SelectLayer { path: Vec }, } impl fmt::Display for DocumentResponse { @@ -14,6 +15,7 @@ impl fmt::Display for DocumentResponse { let name = match self { DocumentResponse::DocumentChanged { .. } => "DocumentChanged", DocumentResponse::FolderChanged { .. } => "FolderChanged", + DocumentResponse::SelectLayer { .. } => "SelectLayer", }; formatter.write_str(name) diff --git a/core/editor/src/communication/dispatcher.rs b/core/editor/src/communication/dispatcher.rs index abe9e376d..644bf9389 100644 --- a/core/editor/src/communication/dispatcher.rs +++ b/core/editor/src/communication/dispatcher.rs @@ -21,11 +21,16 @@ impl Dispatcher { pub fn handle_message>(&mut self, message: T) -> Result<(), EditorError> { let message = message.into(); use Message::*; - if !matches!( + if !(matches!( message, - Message::InputPreprocessor(_) | Message::InputMapper(_) | Message::Tool(ToolMessage::Rectangle(RectangleMessage::MouseMove)) - ) { - log::trace!("Message: {}", message.to_discriminant().global_name()); + Message::InputPreprocessor(_) + | Message::InputMapper(_) + | Message::Document(DocumentMessage::RenderDocument) + | Message::Frontend(FrontendMessage::UpdateCanvas { .. }) + | Message::Document(DocumentMessage::DispatchOperation { .. }) + ) || MessageDiscriminant::from(&message).local_name().ends_with("MouseMove")) + { + log::trace!("Message: {}", message.to_discriminant().local_name()); } match message { NoOp => (), diff --git a/core/editor/src/document/document_file.rs b/core/editor/src/document/document_file.rs index 65d3e96cb..008c0cdaa 100644 --- a/core/editor/src/document/document_file.rs +++ b/core/editor/src/document/document_file.rs @@ -53,6 +53,7 @@ impl Document { .layers() .iter() .zip(folder.layer_ids.iter()) + .rev() .map(|(layer, id)| { let path = [path, &[*id]].concat(); layer_panel_entry(layer_data(self_layer_data, &path), layer, path) diff --git a/core/editor/src/document/document_message_handler.rs b/core/editor/src/document/document_message_handler.rs index b61eadfc9..70ccc600f 100644 --- a/core/editor/src/document/document_message_handler.rs +++ b/core/editor/src/document/document_message_handler.rs @@ -10,6 +10,7 @@ pub enum DocumentMessage { DispatchOperation(DocumentOperation), SelectLayers(Vec>), DeleteLayer(Vec), + DeleteSelectedLayers, AddFolder(Vec), RenameLayer(Vec, String), ToggleLayerVisibility(Vec), @@ -56,6 +57,14 @@ impl DocumentMessageHandler { FrontendMessage::ExpandFolder { path, children }.into() }) } + fn clear_selection(&mut self) { + self.active_document_mut().layer_data.values_mut().for_each(|layer_data| layer_data.selected = false); + } + fn select_layer(&mut self, path: &[LayerId]) -> Option { + self.active_document_mut().layer_data(&path).selected = true; + // TODO: Add deduplication + (!path.is_empty()).then(|| self.handle_folder_changed(path[..path.len() - 1].to_vec())).flatten() + } } impl Default for DocumentMessageHandler { @@ -95,11 +104,18 @@ impl MessageHandler for DocumentMessageHandler { self.active_document_mut().layer_data(&path).expanded ^= true; responses.extend(self.handle_folder_changed(path)); } - SelectLayers(paths) => { + DeleteSelectedLayers => { + // TODO: Replace with drain_filter https://github.com/rust-lang/rust/issues/59618 + let paths: Vec> = self.active_document().layer_data.iter().filter_map(|(path, data)| data.selected.then(|| path.clone())).collect(); for path in paths { - self.active_document_mut().layer_data(&path).selected ^= true; - responses.extend(self.handle_folder_changed(path)); - // TODO: Add deduplication + self.active_document_mut().layer_data.remove(&path); + responses.push_back(DocumentOperation::DeleteLayer { path }.into()) + } + } + SelectLayers(paths) => { + self.clear_selection(); + for path in paths { + responses.extend(self.select_layer(&path)); } } Undo => { @@ -116,6 +132,14 @@ impl MessageHandler for DocumentMessageHandler { .into_iter() .map(|response| match response { DocumentResponse::FolderChanged { path } => self.handle_folder_changed(path), + DocumentResponse::SelectLayer { path } => { + if !self.active_document().document.work_mounted { + self.clear_selection(); + self.select_layer(&path) + } else { + None + } + } DocumentResponse::DocumentChanged => unreachable!(), }) .flatten(), @@ -134,5 +158,11 @@ impl MessageHandler for DocumentMessageHandler { message => todo!("document_action_handler does not implement: {}", message.to_discriminant().global_name()), } } - advertise_actions!(DocumentMessageDiscriminant; Undo, RenderDocument, ExportDocument); + fn actions(&self) -> ActionList { + if self.active_document().layer_data.values().any(|data| data.selected) { + actions!(DocumentMessageDiscriminant; Undo, DeleteSelectedLayers, RenderDocument, ExportDocument) + } else { + actions!(DocumentMessageDiscriminant; Undo, RenderDocument, ExportDocument) + } + } } diff --git a/core/editor/src/frontend/frontend_message_handler.rs b/core/editor/src/frontend/frontend_message_handler.rs index 9d2264b0c..883c7f16f 100644 --- a/core/editor/src/frontend/frontend_message_handler.rs +++ b/core/editor/src/frontend/frontend_message_handler.rs @@ -28,7 +28,6 @@ impl FrontendMessageHandler { impl MessageHandler for FrontendMessageHandler { fn process_action(&mut self, message: FrontendMessage, _data: (), _responses: &mut VecDeque) { - log::trace!("Sending {} Response", message.to_discriminant().global_name()); (self.callback)(message) } advertise_actions!( diff --git a/core/editor/src/input/input_mapper.rs b/core/editor/src/input/input_mapper.rs index 3557129a3..8b1fa345c 100644 --- a/core/editor/src/input/input_mapper.rs +++ b/core/editor/src/input/input_mapper.rs @@ -147,6 +147,9 @@ impl Default for Mapping { entry! {action=PenMessage::Confirm, key_down=KeyEnter}, // Document Actions entry! {action=DocumentMessage::Undo, key_down=KeyZ, modifiers=[KeyControl]}, + entry! {action=DocumentMessage::DeleteSelectedLayers, key_down=KeyDelete}, + entry! {action=DocumentMessage::DeleteSelectedLayers, key_down=KeyX}, + entry! {action=DocumentMessage::DeleteSelectedLayers, key_down=KeyBackspace}, entry! {action=DocumentMessage::ExportDocument, key_down=KeyS, modifiers=[KeyControl, KeyShift]}, entry! {action=DocumentMessage::ExportDocument, key_down=KeyE, modifiers=[KeyControl]}, // Tool Actions diff --git a/core/editor/src/input/keyboard.rs b/core/editor/src/input/keyboard.rs index e77468fae..87fc83a3f 100644 --- a/core/editor/src/input/keyboard.rs +++ b/core/editor/src/input/keyboard.rs @@ -1,7 +1,7 @@ pub const NUMBER_OF_KEYS: usize = Key::NumKeys as usize; // Edit this to specify the storage type used // TODO: Increase size of type -pub type StorageType = u8; +pub type StorageType = u128; const STORAGE_SIZE: u32 = std::mem::size_of::() as u32 * 8 + 2 - std::mem::size_of::().leading_zeros(); const STORAGE_SIZE_BITS: usize = 1 << STORAGE_SIZE; const KEY_MASK_STORAGE_LENGTH: usize = (NUMBER_OF_KEYS + STORAGE_SIZE_BITS - 1) >> STORAGE_SIZE; @@ -55,6 +55,8 @@ pub enum Key { KeyEnter, KeyShift, KeyControl, + KeyDelete, + KeyBackspace, KeyAlt, KeyEscape,