From 42fb19ca3960466c770f5d769ab3f9e4b193a17c Mon Sep 17 00:00:00 2001 From: Simon Desloges Date: Sat, 14 Aug 2021 01:07:31 -0400 Subject: [PATCH 1/2] Show bounding box around selection, albeit with a bug --- editor/src/document/document_file.rs | 9 +++ editor/src/document/movement_handler.rs | 3 + editor/src/tool/tool_message_handler.rs | 12 ++++ editor/src/tool/tools/select.rs | 86 +++++++++++++++++++------ 4 files changed, 89 insertions(+), 21 deletions(-) diff --git a/editor/src/document/document_file.rs b/editor/src/document/document_file.rs index 8e613ca59f..66dfda5e38 100644 --- a/editor/src/document/document_file.rs +++ b/editor/src/document/document_file.rs @@ -141,6 +141,11 @@ impl DocumentMessageHandler { self.layer_data.iter().filter_map(|(path, data)| data.selected.then(|| path)) } + pub fn selected_layers_bounding_box(&self) -> Option<[DVec2; 2]> { + let paths = self.selected_layers().map(|vec| &vec[..]); + self.document.combined_viewport_bounding_box(paths) + } + /// Returns the paths to all layers in order, optionally including only selected or non-selected layers. fn layers_sorted(&self, selected: Option) -> Vec> { // Compute the indices for each layer to be able to sort them @@ -309,6 +314,7 @@ impl MessageHandler for DocumentMessageHand for path in self.selected_layers().cloned() { responses.push_back(DocumentOperation::DeleteLayer { path }.into()) } + responses.push_back(ToolMessage::SelectionUpdated.into()); } DuplicateSelectedLayers => { for path in self.selected_layers_sorted() { @@ -322,17 +328,20 @@ impl MessageHandler for DocumentMessageHand } // TODO: Correctly update layer panel in clear_selection instead of here responses.extend(self.handle_folder_changed(Vec::new())); + responses.push_back(ToolMessage::SelectionUpdated.into()); } SelectAllLayers => { let all_layer_paths = self.layer_data.keys().filter(|path| !path.is_empty()).cloned().collect::>(); for path in all_layer_paths { responses.extend(self.select_layer(&path)); } + responses.push_back(ToolMessage::SelectionUpdated.into()); } DeselectAllLayers => { self.clear_selection(); let children = self.layer_panel(&[]).expect("The provided Path was not valid"); responses.push_back(FrontendMessage::ExpandFolder { path: vec![], children }.into()); + responses.push_back(ToolMessage::SelectionUpdated.into()); } Undo => { // this is a temporary fix and will be addressed by #123 diff --git a/editor/src/document/movement_handler.rs b/editor/src/document/movement_handler.rs index d28d604e7e..318b9cde3b 100644 --- a/editor/src/document/movement_handler.rs +++ b/editor/src/document/movement_handler.rs @@ -102,6 +102,8 @@ impl MessageHandler { layerdata.rotation = new; self.create_document_transform_from_layerdata(layerdata, &ipp.viewport_size, responses); + responses.push_back(ToolMessage::CanvasRotated.into()); responses.push_back(FrontendMessage::SetCanvasRotation { new_radians: new }.into()); } ZoomCanvasToFitAll => { diff --git a/editor/src/tool/tool_message_handler.rs b/editor/src/tool/tool_message_handler.rs index ed90623c11..45a34b715c 100644 --- a/editor/src/tool/tool_message_handler.rs +++ b/editor/src/tool/tool_message_handler.rs @@ -16,6 +16,8 @@ pub enum ToolMessage { SelectSecondaryColor(Color), SwapColors, ResetColors, + CanvasRotated, + SelectionUpdated, SetToolOptions(ToolType, ToolOptions), #[child] Fill(FillMessage), @@ -65,12 +67,17 @@ impl MessageHandler ToolType::Shape => responses.push_back(ShapeMessage::Abort.into()), ToolType::Line => responses.push_back(LineMessage::Abort.into()), ToolType::Pen => responses.push_back(PenMessage::Abort.into()), + ToolType::Select => responses.push_back(SelectMessage::Abort.into()), _ => (), }; reset(tool); reset(self.tool_state.tool_data.active_tool_type); self.tool_state.tool_data.active_tool_type = tool; + if tool == ToolType::Select { + responses.push_back(SelectMessage::Selected.into()); + } + responses.push_back(FrontendMessage::SetActiveTool { tool_name: tool.to_string() }.into()) } SwapColors => { @@ -87,6 +94,11 @@ impl MessageHandler SetToolOptions(tool_type, tool_options) => { self.tool_state.document_tool_data.tool_options.insert(tool_type, tool_options); } + CanvasRotated | SelectionUpdated => self + .tool_state + .tool_data + .active_tool_mut() + .process_action(message, (&document, &self.tool_state.document_tool_data, input), responses), message => { let tool_type = match message { Fill(_) => ToolType::Fill, diff --git a/editor/src/tool/tools/select.rs b/editor/src/tool/tools/select.rs index 4ad62dc549..08d1123991 100644 --- a/editor/src/tool/tools/select.rs +++ b/editor/src/tool/tools/select.rs @@ -25,11 +25,14 @@ pub struct Select { #[impl_message(Message, ToolMessage, Select)] #[derive(PartialEq, Clone, Debug, Serialize, Deserialize, Hash)] pub enum SelectMessage { + Selected, DragStart, DragStop, MouseMove, Abort, + UpdateBoundingBox, + Align(AlignAxis, AlignAggregate), FlipHorizontal, FlipVertical, @@ -100,8 +103,33 @@ impl Fsm for SelectToolFsmState { ) -> Self { use SelectMessage::*; use SelectToolFsmState::*; - if let ToolMessage::Select(event) = event { - match (self, event) { + match event { + ToolMessage::CanvasRotated | ToolMessage::SelectionUpdated => { + responses.push_back(SelectMessage::UpdateBoundingBox.into()); + self + } + ToolMessage::Select(event) => match (self, event) { + (_, UpdateBoundingBox) => { + if data.box_id.is_some() { + place_bounding_box_around_selection(&data.box_id, document, responses); + } + self + } + (Ready, Selected) => { + if data.box_id.is_none() { + data.box_id = Some(vec![generate_hash(&*responses, input, document.document.hash())]); + responses.push_back( + Operation::AddBoundingBox { + path: data.box_id.clone().unwrap(), + transform: DAffine2::ZERO.to_cols_array(), + style: style::PathStyle::new(Some(Stroke::new(Color::from_rgb8(0x00, 0xA8, 0xFF), 1.0)), Some(Fill::none())), + } + .into(), + ); + place_bounding_box_around_selection(&data.box_id, document, responses); + } + self + } (Ready, DragStart) => { data.drag_start = input.mouse.position; data.drag_current = input.mouse.position; @@ -122,15 +150,6 @@ impl Fsm for SelectToolFsmState { Dragging } else { responses.push_back(DocumentMessage::DeselectAllLayers.into()); - data.box_id = Some(vec![generate_hash(&*responses, input, document.document.hash())]); - responses.push_back( - Operation::AddBoundingBox { - path: data.box_id.clone().unwrap(), - transform: DAffine2::ZERO.to_cols_array(), - style: style::PathStyle::new(Some(Stroke::new(Color::from_rgb8(0x00, 0xA8, 0xFF), 1.0)), Some(Fill::none())), - } - .into(), - ); DrawingBox } } @@ -145,12 +164,14 @@ impl Fsm for SelectToolFsmState { ); } data.drag_current = input.mouse.position; + responses.push_back(SelectMessage::UpdateBoundingBox.into()); Dragging } (DrawingBox, MouseMove) => { data.drag_current = input.mouse.position; - let start = data.drag_start.as_f64(); - let size = data.drag_current.as_f64() - start; + let half_pixel_offset = DVec2::new(0.5, 0.5); + let start = data.drag_start.as_f64() + half_pixel_offset; + let size = data.drag_current.as_f64() - start + half_pixel_offset; responses.push_back( Operation::SetLayerTransformInViewport { @@ -162,14 +183,20 @@ impl Fsm for SelectToolFsmState { DrawingBox } (Dragging, DragStop) => Ready, - (DrawingBox, Abort) => { - responses.push_back(Operation::DeleteLayer { path: data.box_id.take().unwrap() }.into()); - Ready - } (DrawingBox, DragStop) => { let quad = data.selection_quad(); responses.push_back(DocumentMessage::SelectLayers(document.document.intersects_quad_root(quad)).into()); - responses.push_back(Operation::DeleteLayer { path: data.box_id.take().unwrap() }.into()); + // // responses.push_back(Operation::DeleteLayer { path: data.box_id.take().unwrap() }.into()); + Ready + } + (Ready, Abort) => { + if data.box_id.is_some() { + responses.push_back(Operation::DeleteLayer { path: data.box_id.take().unwrap() }.into()); + } + Ready + } + (_, Abort) => { + place_bounding_box_around_selection(&data.box_id, document, responses); Ready } (_, Align(axis, aggregate)) => { @@ -188,9 +215,26 @@ impl Fsm for SelectToolFsmState { self } _ => self, - } - } else { - self + }, + _ => self, } } } + +fn place_bounding_box_around_selection(box_id: &Option>, document: &DocumentMessageHandler, responses: &mut VecDeque) { + let maybe_bounding_box = document.selected_layers_bounding_box(); + let transform = if let Some(bounding_box) = maybe_bounding_box { + let start = bounding_box[0]; + let size = bounding_box[1] - start; + DAffine2::from_scale_angle_translation(size, 0., start) + } else { + DAffine2::ZERO + }; + responses.push_back( + Operation::SetLayerTransformInViewport { + path: box_id.clone().unwrap(), + transform: transform.to_cols_array(), + } + .into(), + ); +} From f1f76c1c9d073fdd8df1f5ff0459be1991668489 Mon Sep 17 00:00:00 2001 From: Simon Desloges Date: Sat, 14 Aug 2021 01:23:00 -0400 Subject: [PATCH 2/2] Ignore bounding box in selection quad intersection --- editor/src/tool/tools/select.rs | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/editor/src/tool/tools/select.rs b/editor/src/tool/tools/select.rs index 08d1123991..0167efe723 100644 --- a/editor/src/tool/tools/select.rs +++ b/editor/src/tool/tools/select.rs @@ -135,7 +135,13 @@ impl Fsm for SelectToolFsmState { data.drag_current = input.mouse.position; let mut selected: Vec<_> = document.selected_layers().cloned().collect(); let quad = data.selection_quad(); - let intersection = document.document.intersects_quad_root(quad); + let mut intersection = document.document.intersects_quad_root(quad); + + // Ignore the bounding box overlay if it's in the intersection. + if let Some(box_id) = &data.box_id { + intersection.retain(|path| path != box_id); + } + // If no layer is currently selected and the user clicks on a shape, select that. if selected.is_empty() { if let Some(layer) = intersection.last() { @@ -185,8 +191,14 @@ impl Fsm for SelectToolFsmState { (Dragging, DragStop) => Ready, (DrawingBox, DragStop) => { let quad = data.selection_quad(); - responses.push_back(DocumentMessage::SelectLayers(document.document.intersects_quad_root(quad)).into()); - // // responses.push_back(Operation::DeleteLayer { path: data.box_id.take().unwrap() }.into()); + let mut intersection = document.document.intersects_quad_root(quad); + + // Ignore the bounding box overlay if it's in the intersection. + if let Some(box_id) = &data.box_id { + intersection.retain(|path| path != box_id); + } + + responses.push_back(DocumentMessage::SelectLayers(intersection).into()); Ready } (Ready, Abort) => {