mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2025-08-03 21:08:18 +00:00
Implement bounding box for selected layers (#349)
* Implement bounding box for selected layers * Add shift modifier for multi selection
This commit is contained in:
parent
6deecad9c0
commit
9e47b27b59
10 changed files with 159 additions and 72 deletions
|
@ -30,7 +30,6 @@ impl Dispatcher {
|
|||
| Message::Frontend(FrontendMessage::UpdateScrollbars { .. })
|
||||
| Message::Frontend(FrontendMessage::SetCanvasZoom { .. })
|
||||
| Message::Frontend(FrontendMessage::SetCanvasRotation { .. })
|
||||
| Message::Documents(DocumentsMessage::Document(DocumentMessage::DispatchOperation { .. }))
|
||||
) || MessageDiscriminant::from(&message).local_name().ends_with("MouseMove"))
|
||||
{
|
||||
log::trace!("Message: {:?}", message);
|
||||
|
@ -152,7 +151,7 @@ mod test {
|
|||
let document_before_copy = editor.dispatcher.documents_message_handler.active_document().document.clone();
|
||||
let shape_id = document_before_copy.root.as_folder().unwrap().layer_ids[1];
|
||||
|
||||
editor.handle_message(DocumentMessage::SelectLayers(vec![vec![shape_id]])).unwrap();
|
||||
editor.handle_message(DocumentMessage::SetSelectedLayers(vec![vec![shape_id]])).unwrap();
|
||||
editor.handle_message(DocumentsMessage::CopySelectedLayers).unwrap();
|
||||
editor.handle_message(DocumentsMessage::PasteLayers { path: vec![], insert_index: -1 }).unwrap();
|
||||
|
||||
|
@ -212,7 +211,7 @@ mod test {
|
|||
})
|
||||
.unwrap();
|
||||
|
||||
editor.handle_message(DocumentMessage::SelectLayers(vec![vec![folder_id]])).unwrap();
|
||||
editor.handle_message(DocumentMessage::SetSelectedLayers(vec![vec![folder_id]])).unwrap();
|
||||
|
||||
let document_before_copy = editor.dispatcher.documents_message_handler.active_document().document.clone();
|
||||
|
||||
|
@ -276,7 +275,7 @@ mod test {
|
|||
let rect_id = document_before_copy.root.as_folder().unwrap().layer_ids[RECT_INDEX];
|
||||
let ellipse_id = document_before_copy.root.as_folder().unwrap().layer_ids[ELLIPSE_INDEX];
|
||||
|
||||
editor.handle_message(DocumentMessage::SelectLayers(vec![vec![rect_id], vec![ellipse_id]])).unwrap();
|
||||
editor.handle_message(DocumentMessage::SetSelectedLayers(vec![vec![rect_id], vec![ellipse_id]])).unwrap();
|
||||
editor.handle_message(DocumentsMessage::CopySelectedLayers).unwrap();
|
||||
editor.handle_message(DocumentMessage::DeleteSelectedLayers).unwrap();
|
||||
editor.draw_rect(0., 800., 12., 200.);
|
||||
|
@ -310,7 +309,7 @@ mod test {
|
|||
|
||||
let verify_order = |handler: &mut DocumentMessageHandler| (handler.all_layers_sorted(), handler.non_selected_layers_sorted(), handler.selected_layers_sorted());
|
||||
|
||||
editor.handle_message(DocumentMessage::SelectLayers(vec![vec![0], vec![2]])).unwrap();
|
||||
editor.handle_message(DocumentMessage::SetSelectedLayers(vec![vec![0], vec![2]])).unwrap();
|
||||
|
||||
editor.handle_message(DocumentMessage::ReorderSelectedLayers(1)).unwrap();
|
||||
let (all, non_selected, selected) = verify_order(&mut editor.dispatcher.documents_message_handler.active_document_mut());
|
||||
|
|
|
@ -66,8 +66,10 @@ pub enum DocumentMessage {
|
|||
#[child]
|
||||
Movement(MovementMessage),
|
||||
DispatchOperation(Box<DocumentOperation>),
|
||||
SelectLayers(Vec<Vec<LayerId>>),
|
||||
SetSelectedLayers(Vec<Vec<LayerId>>),
|
||||
AddSelectedLayers(Vec<Vec<LayerId>>),
|
||||
SelectAllLayers,
|
||||
SelectionChanged,
|
||||
DeselectAllLayers,
|
||||
DeleteLayer(Vec<LayerId>),
|
||||
DeleteSelectedLayers,
|
||||
|
@ -125,9 +127,17 @@ impl DocumentMessageHandler {
|
|||
self.layer_data.values_mut().for_each(|layer_data| layer_data.selected = false);
|
||||
}
|
||||
fn select_layer(&mut self, path: &[LayerId]) -> Option<Message> {
|
||||
if self.document.layer(path).ok()?.overlay {
|
||||
return None;
|
||||
}
|
||||
self.layer_data(path).selected = true;
|
||||
let data = self.layer_panel_entry(path.to_vec()).ok()?;
|
||||
// TODO: Add deduplication
|
||||
(!path.is_empty()).then(|| self.handle_folder_changed(path[..path.len() - 1].to_vec())).flatten()
|
||||
(!path.is_empty()).then(|| FrontendMessage::UpdateLayer { path: path.to_vec(), data }.into())
|
||||
}
|
||||
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")
|
||||
|
@ -247,6 +257,7 @@ impl DocumentMessageHandler {
|
|||
.iter()
|
||||
.zip(paths.iter().zip(data))
|
||||
.rev()
|
||||
.filter(|(layer, _)| !layer.overlay)
|
||||
.map(|(layer, (path, data))| {
|
||||
layer_panel_entry(
|
||||
&data,
|
||||
|
@ -332,34 +343,41 @@ 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() {
|
||||
responses.push_back(DocumentOperation::DuplicateLayer { path }.into())
|
||||
}
|
||||
}
|
||||
SelectLayers(paths) => {
|
||||
SetSelectedLayers(paths) => {
|
||||
self.clear_selection();
|
||||
responses.push_front(AddSelectedLayers(paths).into());
|
||||
}
|
||||
AddSelectedLayers(paths) => {
|
||||
for path in paths {
|
||||
responses.extend(self.select_layer(&path));
|
||||
}
|
||||
// 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));
|
||||
}
|
||||
let all_layer_paths = self
|
||||
.layer_data
|
||||
.keys()
|
||||
.filter(|path| !path.is_empty() && !self.document.layer(path).unwrap().overlay)
|
||||
.cloned()
|
||||
.collect::<Vec<_>>();
|
||||
responses.push_back(SetSelectedLayers(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(SetSelectedLayers(vec![]).into());
|
||||
}
|
||||
Undo => {
|
||||
// this is a temporary fix and will be addressed by #123
|
||||
|
@ -378,7 +396,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 {
|
||||
|
@ -387,7 +406,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(|| SetSelectedLayers(vec![path]).into()),
|
||||
DocumentResponse::DocumentChanged => unreachable!(),
|
||||
})
|
||||
.flatten(),
|
||||
|
@ -434,6 +453,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());
|
||||
|
@ -488,6 +508,7 @@ impl MessageHandler<DocumentMessage, &InputPreprocessor> for DocumentMessageHand
|
|||
.into(),
|
||||
);
|
||||
}
|
||||
responses.push_back(SelectMessage::UpdateSelectionBoundingBox.into());
|
||||
}
|
||||
}
|
||||
AlignSelectedLayers(axis, aggregate) => {
|
||||
|
@ -520,6 +541,7 @@ impl MessageHandler<DocumentMessage, &InputPreprocessor> for DocumentMessageHand
|
|||
.into(),
|
||||
);
|
||||
}
|
||||
responses.push_back(SelectMessage::UpdateSelectionBoundingBox.into());
|
||||
}
|
||||
}
|
||||
RenameLayer(path, name) => responses.push_back(DocumentOperation::RenameLayer { path, name }.into()),
|
||||
|
|
|
@ -105,6 +105,7 @@ impl MessageHandler<MovementMessage, (&mut LayerData, &Document, &InputPreproces
|
|||
|
||||
layerdata.rotation += rotation;
|
||||
layerdata.snap_rotate = snapping;
|
||||
responses.push_back(SelectMessage::UpdateSelectionBoundingBox.into());
|
||||
responses.push_back(
|
||||
FrontendMessage::SetCanvasRotation {
|
||||
new_radians: layerdata.snapped_angle(),
|
||||
|
@ -172,6 +173,7 @@ impl MessageHandler<MovementMessage, (&mut LayerData, &Document, &InputPreproces
|
|||
layerdata.rotation = new;
|
||||
self.create_document_transform_from_layerdata(layerdata, &ipp.viewport_bounds, responses);
|
||||
responses.push_back(FrontendMessage::SetCanvasRotation { new_radians: new }.into());
|
||||
responses.push_back(SelectMessage::UpdateSelectionBoundingBox.into());
|
||||
}
|
||||
ZoomCanvasToFitAll => {
|
||||
if let Some([pos1, pos2]) = document.visible_layers_bounding_box() {
|
||||
|
|
|
@ -131,7 +131,7 @@ impl Default for Mapping {
|
|||
entry! {action=MovementMessage::DisableSnapping, key_up=KeyShift},
|
||||
// Select
|
||||
entry! {action=SelectMessage::MouseMove, message=InputMapperMessage::PointerMove},
|
||||
entry! {action=SelectMessage::DragStart, key_down=Lmb},
|
||||
entry! {action=SelectMessage::DragStart{add_to_selection: KeyShift}, key_down=Lmb},
|
||||
entry! {action=SelectMessage::DragStop, key_up=Lmb},
|
||||
entry! {action=SelectMessage::Abort, key_down=Rmb},
|
||||
entry! {action=SelectMessage::Abort, key_down=KeyEscape},
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
use crate::message_prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
pub const NUMBER_OF_KEYS: usize = Key::NumKeys as usize;
|
||||
// Edit this to specify the storage type used
|
||||
|
@ -12,7 +13,7 @@ const KEY_MASK_STORAGE_LENGTH: usize = (NUMBER_OF_KEYS + STORAGE_SIZE_BITS - 1)
|
|||
pub type KeyStates = BitVector<KEY_MASK_STORAGE_LENGTH>;
|
||||
|
||||
#[impl_message(Message, InputMapperMessage, KeyDown)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
pub enum Key {
|
||||
UnknownKey,
|
||||
// MouseKeys
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)) => {
|
||||
|
|
|
@ -109,7 +109,7 @@ pub fn close_all_documents_with_confirmation() -> Result<(), JsValue> {
|
|||
#[wasm_bindgen]
|
||||
pub fn bounds_of_viewports(bounds_of_viewports: &[f64]) -> Result<(), JsValue> {
|
||||
let chunked: Vec<_> = bounds_of_viewports.chunks(4).map(ViewportBounds::from_slice).collect();
|
||||
let ev = InputPreprocessorMessage::BoundsOfViewports((chunked).into());
|
||||
let ev = InputPreprocessorMessage::BoundsOfViewports(chunked);
|
||||
EDITOR_STATE.with(|editor| editor.borrow_mut().handle_message(ev)).map_err(convert_error)
|
||||
}
|
||||
|
||||
|
@ -324,7 +324,7 @@ pub fn translate_canvas_by_fraction(delta_x: f64, delta_y: f64) -> Result<(), Js
|
|||
pub fn select_layers(paths: Vec<LayerId>) -> Result<(), JsValue> {
|
||||
let paths = paths.split(|id| *id == LayerId::MAX).map(|path| path.to_vec()).collect();
|
||||
EDITOR_STATE
|
||||
.with(|editor| editor.borrow_mut().handle_message(DocumentMessage::SelectLayers(paths)))
|
||||
.with(|editor| editor.borrow_mut().handle_message(DocumentMessage::SetSelectedLayers(paths)))
|
||||
.map_err(convert_error)
|
||||
}
|
||||
|
||||
|
|
|
@ -33,7 +33,7 @@ fn split_path(path: &[LayerId]) -> Result<(&[LayerId], LayerId), DocumentError>
|
|||
}
|
||||
|
||||
impl Document {
|
||||
pub fn with_content(serialized_content: &String) -> Result<Self, DocumentError> {
|
||||
pub fn with_content(serialized_content: &str) -> Result<Self, DocumentError> {
|
||||
serde_json::from_str(serialized_content).map_err(|e| DocumentError::InvalidFile(e.to_string()))
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
|
|
|
@ -83,6 +83,7 @@ pub struct Layer {
|
|||
pub cache_dirty: bool,
|
||||
pub blend_mode: BlendMode,
|
||||
pub opacity: f64,
|
||||
pub overlay: bool,
|
||||
}
|
||||
|
||||
impl Layer {
|
||||
|
@ -97,6 +98,7 @@ impl Layer {
|
|||
cache_dirty: true,
|
||||
blend_mode: BlendMode::Normal,
|
||||
opacity: 1.,
|
||||
overlay: false,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -128,7 +130,7 @@ impl Layer {
|
|||
}
|
||||
|
||||
pub fn intersects_quad(&self, quad: Quad, path: &mut Vec<LayerId>, intersections: &mut Vec<Vec<LayerId>>) {
|
||||
if !self.visible {
|
||||
if !self.visible || self.overlay {
|
||||
return;
|
||||
}
|
||||
let transformed_quad = self.transform.inverse() * quad;
|
||||
|
@ -166,6 +168,7 @@ impl Clone for Layer {
|
|||
cache_dirty: true,
|
||||
blend_mode: self.blend_mode,
|
||||
opacity: self.opacity,
|
||||
overlay: self.overlay,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue