Skip to content

Commit

Permalink
Implement bounding box for selected layers
Browse files Browse the repository at this point in the history
  • Loading branch information
TrueDoctor committed Aug 14, 2021
1 parent c15ab1a commit 28aa6a7
Show file tree
Hide file tree
Showing 5 changed files with 130 additions and 50 deletions.
23 changes: 15 additions & 8 deletions editor/src/document/document_file.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ pub enum DocumentMessage {
DispatchOperation(Box<DocumentOperation>),
SelectLayers(Vec<Vec<LayerId>>),
SelectAllLayers,
SelectionChanged,
DeselectAllLayers,
DeleteLayer(Vec<LayerId>),
DeleteSelectedLayers,
Expand Down Expand Up @@ -135,6 +136,10 @@ impl DocumentMessageHandler {
// TODO: Add deduplication
(!path.is_empty()).then(|| self.handle_folder_changed(path[..path.len() - 1].to_vec())).flatten()
}
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)
}
pub fn layerdata(&self, path: &[LayerId]) -> &LayerData {
self.layer_data.get(path).expect("Layerdata does not exist")
}
Expand Down Expand Up @@ -253,6 +258,7 @@ impl DocumentMessageHandler {
.iter()
.zip(paths.iter().zip(data))
.rev()
.filter(|(layer, _)| !layer.overlay)
.map(|(layer, (path, data))| {
layer_panel_entry(
&data,
Expand Down Expand Up @@ -340,10 +346,12 @@ impl MessageHandler<DocumentMessage, &InputPreprocessor> for DocumentMessageHand
self.layer_data(&path).expanded ^= true;
responses.extend(self.handle_folder_changed(path));
}
SelectionChanged => responses.push_back(SelectMessage::UpdateSelectionBoundingBox.into()),
DeleteSelectedLayers => {
for path in self.selected_layers().cloned() {
responses.push_back(DocumentOperation::DeleteLayer { path }.into())
}
responses.push_back(SelectMessage::UpdateSelectionBoundingBox.into());
}
DuplicateSelectedLayers => {
for path in self.selected_layers_sorted() {
Expand All @@ -357,17 +365,14 @@ 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(SelectMessage::UpdateSelectionBoundingBox.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(SelectLayers(all_layer_paths).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(SelectLayers(vec![]).into());
}
Undo => {
// this is a temporary fix and will be addressed by #123
Expand All @@ -386,7 +391,8 @@ impl MessageHandler<DocumentMessage, &InputPreprocessor> for DocumentMessageHand
DocumentResponse::FolderChanged { path } => self.handle_folder_changed(path),
DocumentResponse::DeletedLayer { path } => {
self.layer_data.remove(&path);
None

Some(SelectMessage::UpdateSelectionBoundingBox.into())
}
DocumentResponse::LayerChanged { path } => Some(
FrontendMessage::UpdateLayer {
Expand All @@ -395,7 +401,7 @@ impl MessageHandler<DocumentMessage, &InputPreprocessor> for DocumentMessageHand
}
.into(),
),
DocumentResponse::CreatedLayer { path } => self.select_layer(&path),
DocumentResponse::CreatedLayer { path } => (!self.document.layer(&path).unwrap().overlay).then(|| SelectLayers(vec![path]).into()),
DocumentResponse::DocumentChanged => unreachable!(),
})
.flatten(),
Expand All @@ -421,6 +427,7 @@ impl MessageHandler<DocumentMessage, &InputPreprocessor> for DocumentMessageHand
};
responses.push_back(operation.into());
}
responses.push_back(SelectMessage::UpdateSelectionBoundingBox.into());
}
MoveSelectedLayersTo { path, insert_index } => {
responses.push_back(DocumentsMessage::CopySelectedLayers.into());
Expand Down
67 changes: 43 additions & 24 deletions editor/src/tool/tool_message_handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ pub enum ToolMessage {
SelectSecondaryColor(Color),
SwapColors,
ResetColors,
NoOp,
SetToolOptions(ToolType, ToolOptions),
#[child]
Fill(FillMessage),
Expand Down Expand Up @@ -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())
Expand All @@ -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);
}
}
}
}
Expand All @@ -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 {
Expand Down
67 changes: 51 additions & 16 deletions editor/src/tool/tools/select.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ pub enum SelectMessage {
DragStop,
MouseMove,
Abort,
UpdateSelectionBoundingBox,

Align(AlignAxis, AlignAggregate),
FlipHorizontal,
Expand Down Expand Up @@ -67,7 +68,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 {
Expand All @@ -86,6 +88,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;

Expand All @@ -102,6 +122,20 @@ impl Fsm for SelectToolFsmState {
use SelectToolFsmState::*;
if let ToolMessage::Select(event) = event {
match (self, event) {
(_, UpdateSelectionBoundingBox) => {
let response = match (document.selected_layers_bounding_box(), data.bounding_box_id.clone()) {
(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) => {
data.drag_start = input.mouse.position;
data.drag_current = input.mouse.position;
Expand All @@ -118,19 +152,12 @@ impl Fsm for SelectToolFsmState {
// If the user clicks on a layer that is in their current selection, go into the dragging mode.
// Otherwise enter the box select mode
if selected.iter().any(|path| intersection.contains(path)) {
responses.push_back(SelectMessage::UpdateSelectionBoundingBox.into());
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(),
);
data.drag_box_id = Some(add_boundnig_box(responses));
DrawingBox
}
}
Expand All @@ -144,6 +171,7 @@ impl Fsm for SelectToolFsmState {
.into(),
);
}
responses.push_back(SelectMessage::UpdateSelectionBoundingBox.into());
data.drag_current = input.mouse.position;
Dragging
}
Expand All @@ -154,22 +182,29 @@ impl Fsm for SelectToolFsmState {

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(),
);
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.drag_box_id.take().unwrap(),
}
.into(),
);
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)) => {
Expand Down
6 changes: 4 additions & 2 deletions graphene/src/document.rs
Original file line number Diff line number Diff line change
Expand Up @@ -289,8 +289,10 @@ impl Document {
Operation::AddBoundingBox { path, transform, style } => {
let mut rect = Shape::rectangle(*style);
rect.render_index = -1;
self.set_layer(path, Layer::new(LayerDataType::Shape(rect), *transform), -1)?;
Some(vec![DocumentResponse::DocumentChanged])
let mut layer = Layer::new(LayerDataType::Shape(rect), *transform);
layer.overlay = true;
self.set_layer(path, layer, -1)?;
Some(vec![DocumentResponse::DocumentChanged, DocumentResponse::CreatedLayer { path: path.clone() }])
}
Operation::AddShape {
path,
Expand Down
17 changes: 17 additions & 0 deletions graphene/src/layers/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ pub struct Layer {
pub cache_dirty: bool,
pub blend_mode: BlendMode,
pub opacity: f64,
pub overlay: bool,
}

impl Layer {
Expand All @@ -97,6 +98,21 @@ impl Layer {
cache_dirty: true,
blend_mode: BlendMode::Normal,
opacity: 1.,
overlay: false,
}
}
pub fn new_overlay(data: LayerDataType, transform: [f64; 6]) -> Self {
Self {
visible: true,
name: None,
data,
transform: glam::DAffine2::from_cols_array(&transform),
cache: String::new(),
thumbnail_cache: String::new(),
cache_dirty: true,
blend_mode: BlendMode::Normal,
opacity: 1.,
overlay: true,
}
}

Expand Down Expand Up @@ -166,6 +182,7 @@ impl Clone for Layer {
cache_dirty: true,
blend_mode: self.blend_mode,
opacity: self.opacity,
overlay: self.overlay,
}
}
}

0 comments on commit 28aa6a7

Please sign in to comment.