Implement bounding box for selected layers (#349)

* Implement bounding box for selected layers

* Add shift modifier for multi selection
This commit is contained in:
TrueDoctor
2021-08-20 09:57:00 +02:00
committed by Keavon Chambers
parent 7c64e1a816
commit 68d8ae4804
10 changed files with 159 additions and 72 deletions
+43 -24
View File
@@ -16,6 +16,7 @@ pub enum ToolMessage {
SelectSecondaryColor(Color),
SwapColors,
ResetColors,
NoOp,
SetToolOptions(ToolType, ToolOptions),
#[child]
Fill(FillMessage),
@@ -59,16 +60,27 @@ impl MessageHandler<ToolMessage, (&DocumentMessageHandler, &InputPreprocessor)>
update_working_colors(&self.tool_state.document_tool_data, responses);
}
SelectTool(tool) => {
let mut reset = |tool| match tool {
ToolType::Ellipse => responses.push_back(EllipseMessage::Abort.into()),
ToolType::Rectangle => responses.push_back(RectangleMessage::Abort.into()),
ToolType::Shape => responses.push_back(ShapeMessage::Abort.into()),
ToolType::Line => responses.push_back(LineMessage::Abort.into()),
ToolType::Pen => responses.push_back(PenMessage::Abort.into()),
_ => (),
let old_tool = self.tool_state.tool_data.active_tool_type;
let reset = |tool| match tool {
ToolType::Ellipse => EllipseMessage::Abort.into(),
ToolType::Rectangle => RectangleMessage::Abort.into(),
ToolType::Shape => ShapeMessage::Abort.into(),
ToolType::Line => LineMessage::Abort.into(),
ToolType::Pen => PenMessage::Abort.into(),
ToolType::Select => SelectMessage::Abort.into(),
_ => ToolMessage::NoOp,
};
reset(tool);
reset(self.tool_state.tool_data.active_tool_type);
let (new, old) = (reset(tool), reset(old_tool));
let mut send_to_tool = |tool_type, message: ToolMessage| {
if let Some(tool) = self.tool_state.tool_data.tools.get_mut(&tool_type) {
tool.process_action(message, (document, &self.tool_state.document_tool_data, input), responses);
}
};
send_to_tool(tool, new);
send_to_tool(old_tool, old);
if tool == ToolType::Select {
responses.push_back(SelectMessage::UpdateSelectionBoundingBox.into());
}
self.tool_state.tool_data.active_tool_type = tool;
responses.push_back(FrontendMessage::SetActiveTool { tool_name: tool.to_string() }.into())
@@ -88,22 +100,11 @@ impl MessageHandler<ToolMessage, (&DocumentMessageHandler, &InputPreprocessor)>
self.tool_state.document_tool_data.tool_options.insert(tool_type, tool_options);
}
message => {
let tool_type = match message {
Fill(_) => ToolType::Fill,
Rectangle(_) => ToolType::Rectangle,
Ellipse(_) => ToolType::Ellipse,
Shape(_) => ToolType::Shape,
Line(_) => ToolType::Line,
Pen(_) => ToolType::Pen,
Select(_) => ToolType::Select,
Crop(_) => ToolType::Crop,
Eyedropper(_) => ToolType::Eyedropper,
Navigate(_) => ToolType::Navigate,
Path(_) => ToolType::Path,
_ => unreachable!(),
};
let tool_type = message_to_tool_type(&message);
if let Some(tool) = self.tool_state.tool_data.tools.get_mut(&tool_type) {
tool.process_action(message, (document, &self.tool_state.document_tool_data, input), responses);
if tool_type == self.tool_state.tool_data.active_tool_type {
tool.process_action(message, (document, &self.tool_state.document_tool_data, input), responses);
}
}
}
}
@@ -115,6 +116,24 @@ impl MessageHandler<ToolMessage, (&DocumentMessageHandler, &InputPreprocessor)>
}
}
fn message_to_tool_type(message: &ToolMessage) -> ToolType {
use ToolMessage::*;
match message {
Fill(_) => ToolType::Fill,
Rectangle(_) => ToolType::Rectangle,
Ellipse(_) => ToolType::Ellipse,
Shape(_) => ToolType::Shape,
Line(_) => ToolType::Line,
Pen(_) => ToolType::Pen,
Select(_) => ToolType::Select,
Crop(_) => ToolType::Crop,
Eyedropper(_) => ToolType::Eyedropper,
Navigate(_) => ToolType::Navigate,
Path(_) => ToolType::Path,
_ => unreachable!(),
}
}
fn update_working_colors(doc_data: &DocumentToolData, responses: &mut VecDeque<Message>) {
responses.push_back(
FrontendMessage::UpdateWorkingColors {
+62 -23
View File
@@ -8,6 +8,7 @@ use graphene::Quad;
use glam::{DAffine2, DVec2};
use serde::{Deserialize, Serialize};
use crate::input::keyboard::Key;
use crate::input::{mouse::ViewportPosition, InputPreprocessor};
use crate::tool::{DocumentToolData, Fsm, ToolActionHandlerData};
use crate::{
@@ -25,10 +26,11 @@ pub struct Select {
#[impl_message(Message, ToolMessage, Select)]
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize, Hash)]
pub enum SelectMessage {
DragStart,
DragStart { add_to_selection: Key },
DragStop,
MouseMove,
Abort,
UpdateSelectionBoundingBox,
Align(AlignAxis, AlignAggregate),
FlipHorizontal,
@@ -67,7 +69,8 @@ struct SelectToolData {
drag_start: ViewportPosition,
drag_current: ViewportPosition,
layers_dragging: Vec<Vec<LayerId>>, // Paths and offsets
box_id: Option<Vec<LayerId>>,
drag_box_id: Option<Vec<LayerId>>,
bounding_box_id: Option<Vec<LayerId>>,
}
impl SelectToolData {
@@ -86,6 +89,24 @@ impl SelectToolData {
}
}
fn add_boundnig_box(responses: &mut VecDeque<Message>) -> Vec<LayerId> {
let path = vec![generate_uuid()];
responses.push_back(
Operation::AddBoundingBox {
path: path.clone(),
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(),
);
path
}
fn transform_from_box(pos1: DVec2, pos2: DVec2) -> [f64; 6] {
DAffine2::from_scale_angle_translation(pos2 - pos1, 0., pos1).to_cols_array()
}
impl Fsm for SelectToolFsmState {
type ToolData = SelectToolData;
@@ -102,7 +123,21 @@ impl Fsm for SelectToolFsmState {
use SelectToolFsmState::*;
if let ToolMessage::Select(event) = event {
match (self, event) {
(Ready, DragStart) => {
(_, UpdateSelectionBoundingBox) => {
let response = match (document.selected_layers_bounding_box(), data.bounding_box_id.take()) {
(None, Some(path)) => Operation::DeleteLayer { path }.into(),
(Some([pos1, pos2]), path) => {
let path = path.unwrap_or_else(|| add_boundnig_box(responses));
data.bounding_box_id = Some(path.clone());
let transform = transform_from_box(pos1, pos2);
Operation::SetLayerTransformInViewport { path, transform }.into()
}
(_, _) => Message::NoOp,
};
responses.push_back(response);
self
}
(Ready, DragStart { add_to_selection }) => {
data.drag_start = input.mouse.position;
data.drag_current = input.mouse.position;
let mut selected: Vec<_> = document.selected_layers().cloned().collect();
@@ -112,7 +147,7 @@ impl Fsm for SelectToolFsmState {
if selected.is_empty() {
if let Some(layer) = intersection.last() {
selected.push(layer.clone());
responses.push_back(DocumentMessage::SelectLayers(selected.clone()).into());
responses.push_back(DocumentMessage::SetSelectedLayers(selected.clone()).into());
}
}
// If the user clicks on a layer that is in their current selection, go into the dragging mode.
@@ -121,16 +156,10 @@ impl Fsm for SelectToolFsmState {
data.layers_dragging = selected;
Dragging
} else {
responses.push_back(DocumentMessage::DeselectAllLayers.into());
data.box_id = Some(vec![generate_uuid()]);
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(),
);
if !input.keyboard.get(add_to_selection as usize) {
responses.push_back(DocumentMessage::DeselectAllLayers.into());
}
data.drag_box_id = Some(add_boundnig_box(responses));
DrawingBox
}
}
@@ -144,17 +173,19 @@ impl Fsm for SelectToolFsmState {
.into(),
);
}
responses.push_back(SelectMessage::UpdateSelectionBoundingBox.into());
data.drag_current = input.mouse.position;
Dragging
}
(DrawingBox, MouseMove) => {
data.drag_current = input.mouse.position;
let start = data.drag_start;
let size = data.drag_current - start;
let half_pixel_offset = DVec2::new(0.5, 0.5);
let start = data.drag_start + half_pixel_offset;
let size = data.drag_current - start + half_pixel_offset;
responses.push_back(
Operation::SetLayerTransformInViewport {
path: data.box_id.clone().unwrap(),
path: data.drag_box_id.clone().unwrap(),
transform: DAffine2::from_scale_angle_translation(size, 0., start).to_cols_array(),
}
.into(),
@@ -162,14 +193,22 @@ 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(DocumentMessage::AddSelectedLayers(document.document.intersects_quad_root(quad)).into());
responses.push_back(
Operation::DeleteLayer {
path: data.drag_box_id.take().unwrap(),
}
.into(),
);
data.drag_box_id = None;
Ready
}
(_, Abort) => {
let mut delete = |path: &mut Option<Vec<LayerId>>| path.take().map(|path| responses.push_back(Operation::DeleteLayer { path }.into()));
delete(&mut data.drag_box_id);
delete(&mut data.bounding_box_id);
Ready
}
(_, Align(axis, aggregate)) => {