Skip to content

Select tool bounding box #346

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions editor/src/document/document_file.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<bool>) -> Vec<Vec<LayerId>> {
// Compute the indices for each layer to be able to sort them
Expand Down Expand Up @@ -309,6 +314,7 @@ impl MessageHandler<DocumentMessage, &InputPreprocessor> 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() {
Expand All @@ -322,17 +328,20 @@ impl MessageHandler<DocumentMessage, &InputPreprocessor> 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::<Vec<_>>();
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
Expand Down
3 changes: 3 additions & 0 deletions editor/src/document/movement_handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,8 @@ impl MessageHandler<MovementMessage, (&mut LayerData, &Document, &InputPreproces

let snapping = self.snapping;

responses.push_back(ToolMessage::CanvasRotated.into());

layerdata.rotation += rotation;
layerdata.snap_rotate = snapping;
responses.push_back(
Expand Down Expand Up @@ -170,6 +172,7 @@ impl MessageHandler<MovementMessage, (&mut LayerData, &Document, &InputPreproces
SetCanvasRotation(new) => {
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 => {
Expand Down
12 changes: 12 additions & 0 deletions editor/src/tool/tool_message_handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ pub enum ToolMessage {
SelectSecondaryColor(Color),
SwapColors,
ResetColors,
CanvasRotated,
SelectionUpdated,
SetToolOptions(ToolType, ToolOptions),
#[child]
Fill(FillMessage),
Expand Down Expand Up @@ -65,12 +67,17 @@ impl MessageHandler<ToolMessage, (&DocumentMessageHandler, &InputPreprocessor)>
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 => {
Expand All @@ -87,6 +94,11 @@ impl MessageHandler<ToolMessage, (&DocumentMessageHandler, &InputPreprocessor)>
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,
Expand Down
102 changes: 79 additions & 23 deletions editor/src/tool/tools/select.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -100,14 +103,45 @@ 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;
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() {
Expand All @@ -122,15 +156,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
}
}
Expand All @@ -145,12 +170,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 {
Expand All @@ -162,14 +189,26 @@ 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());
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) => {
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)) => {
Expand All @@ -188,9 +227,26 @@ impl Fsm for SelectToolFsmState {
self
}
_ => self,
}
} else {
self
},
_ => self,
}
}
}

fn place_bounding_box_around_selection(box_id: &Option<Vec<u64>>, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>) {
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(),
);
}