Delete selected layers (#173)

* Delete Layers
* Fix backend selection
* Fix shift selection
* Add desired shape add selection behavior
* Add X and Backspace as keybinds for layer deletion
* Change key storage to u128
* Remove unhelpful trace logging
* Reverse display direction for layers
This commit is contained in:
TrueDoctor 2021-06-10 09:24:59 +02:00 committed by Keavon Chambers
parent 4f40a1f291
commit e4b5aa3933
10 changed files with 81 additions and 28 deletions

View File

@ -144,9 +144,9 @@ export default defineComponent({
async handleShiftClick(clickedLayer: LayerPanelEntry) { async handleShiftClick(clickedLayer: LayerPanelEntry) {
// The two paths of the range are stored in selectionRangeStartLayer and selectionRangeEndLayer // The two paths of the range are stored in selectionRangeStartLayer and selectionRangeEndLayer
// So for a new Shift+Click, select all layers between selectionRangeStartLayer and selectionRangeEndLayer(stored in prev Sft+C) // So for a new Shift+Click, select all layers between selectionRangeStartLayer and selectionRangeEndLayer(stored in prev Sft+C)
this.selectionRangeEndLayer = clickedLayer;
this.selectionRangeStartLayer = (this.selectionRangeStartLayer as LayerPanelEntry) || clickedLayer;
this.clearSelection(); this.clearSelection();
this.selectionRangeEndLayer = clickedLayer;
if (!this.selectionRangeStartLayer) this.selectionRangeStartLayer = clickedLayer;
this.fillSelectionRange(this.selectionRangeStartLayer, this.selectionRangeEndLayer, true); this.fillSelectionRange(this.selectionRangeStartLayer, this.selectionRangeEndLayer, true);
this.updateSelection(); this.updateSelection();
}, },
@ -159,11 +159,13 @@ export default defineComponent({
this.updateSelection(); this.updateSelection();
}, },
async fillSelectionRange(start: LayerPanelEntry, end: LayerPanelEntry, selected = true) { async fillSelectionRange(start: LayerPanelEntry, end: LayerPanelEntry, selected = true) {
const startIndex = this.layers.indexOf(start); const startIndex = this.layers.findIndex((layer) => layer.path.join() === start.path.join());
const endIndex = this.layers.indexOf(end); const endIndex = this.layers.findIndex((layer) => layer.path.join() === end.path.join());
const [min, max] = [startIndex, endIndex].sort(); const [min, max] = [startIndex, endIndex].sort();
for (let i = min; i <= max; i += 1) { if (min !== -1) {
this.layers[i].layer_data.selected = selected; for (let i = min; i <= max; i += 1) {
this.layers[i].layer_data.selected = selected;
}
} }
}, },
async clearSelection() { async clearSelection() {
@ -173,6 +175,7 @@ export default defineComponent({
}, },
async updateSelection() { async updateSelection() {
const paths = this.layers.filter((layer) => layer.layer_data.selected).map((layer) => layer.path); const paths = this.layers.filter((layer) => layer.layer_data.selected).map((layer) => layer.path);
if (paths.length === 0) return;
const length = paths.reduce((acc, cur) => acc + cur.length, 0) + paths.length - 1; const length = paths.reduce((acc, cur) => acc + cur.length, 0) + paths.length - 1;
const output = new BigUint64Array(length); const output = new BigUint64Array(length);
let i = 0; let i = 0;

View File

@ -119,6 +119,8 @@ pub fn translate_key(name: &str) -> Key {
// When using linux + chrome + the neo keyboard layout, the shift key is recognized as caps // When using linux + chrome + the neo keyboard layout, the shift key is recognized as caps
"capslock" => KeyShift, "capslock" => KeyShift,
"control" => KeyControl, "control" => KeyControl,
"delete" => KeyDelete,
"backspace" => KeyBackspace,
"alt" => KeyAlt, "alt" => KeyAlt,
"escape" => KeyEscape, "escape" => KeyEscape,
_ => UnknownKey, _ => UnknownKey,

View File

@ -172,9 +172,10 @@ impl Document {
pub fn handle_operation(&mut self, operation: Operation) -> Result<Option<Vec<DocumentResponse>>, DocumentError> { pub fn handle_operation(&mut self, operation: Operation) -> Result<Option<Vec<DocumentResponse>>, DocumentError> {
let responses = match &operation { let responses = match &operation {
Operation::AddCircle { path, insert_index, cx, cy, r, style } => { Operation::AddCircle { path, insert_index, cx, cy, r, style } => {
self.add_layer(&path, Layer::new(LayerDataTypes::Circle(layers::Circle::new((*cx, *cy), *r, *style))), *insert_index)?; let id = self.add_layer(&path, Layer::new(LayerDataTypes::Circle(layers::Circle::new((*cx, *cy), *r, *style))), *insert_index)?;
let path = [path.clone(), vec![id]].concat();
Some(vec![DocumentResponse::DocumentChanged]) Some(vec![DocumentResponse::DocumentChanged, DocumentResponse::SelectLayer { path }])
} }
Operation::AddEllipse { Operation::AddEllipse {
path, path,
@ -186,9 +187,10 @@ impl Document {
rot, rot,
style, style,
} => { } => {
self.add_layer(&path, Layer::new(LayerDataTypes::Ellipse(layers::Ellipse::new((*cx, *cy), (*rx, *ry), *rot, *style))), *insert_index)?; let id = self.add_layer(&path, Layer::new(LayerDataTypes::Ellipse(layers::Ellipse::new((*cx, *cy), (*rx, *ry), *rot, *style))), *insert_index)?;
let path = [path.clone(), vec![id]].concat();
Some(vec![DocumentResponse::DocumentChanged]) Some(vec![DocumentResponse::DocumentChanged, DocumentResponse::SelectLayer { path }])
} }
Operation::AddRect { Operation::AddRect {
path, path,
@ -199,9 +201,10 @@ impl Document {
y1, y1,
style, style,
} => { } => {
self.add_layer(&path, Layer::new(LayerDataTypes::Rect(Rect::new((*x0, *y0), (*x1, *y1), *style))), *insert_index)?; let id = self.add_layer(&path, Layer::new(LayerDataTypes::Rect(Rect::new((*x0, *y0), (*x1, *y1), *style))), *insert_index)?;
let path = [path.clone(), vec![id]].concat();
Some(vec![DocumentResponse::DocumentChanged]) Some(vec![DocumentResponse::DocumentChanged, DocumentResponse::SelectLayer { path }])
} }
Operation::AddLine { Operation::AddLine {
path, path,
@ -212,9 +215,10 @@ impl Document {
y1, y1,
style, style,
} => { } => {
self.add_layer(&path, Layer::new(LayerDataTypes::Line(Line::new((*x0, *y0), (*x1, *y1), *style))), *insert_index)?; let id = self.add_layer(&path, Layer::new(LayerDataTypes::Line(Line::new((*x0, *y0), (*x1, *y1), *style))), *insert_index)?;
let path = [path.clone(), vec![id]].concat();
Some(vec![DocumentResponse::DocumentChanged]) Some(vec![DocumentResponse::DocumentChanged, DocumentResponse::SelectLayer { path }])
} }
Operation::AddPen { path, insert_index, points, style } => { Operation::AddPen { path, insert_index, points, style } => {
let points: Vec<kurbo::Point> = points.iter().map(|&it| it.into()).collect(); let points: Vec<kurbo::Point> = points.iter().map(|&it| it.into()).collect();
@ -233,9 +237,10 @@ impl Document {
style, style,
} => { } => {
let s = Shape::new((*x0, *y0), (*x1, *y1), *sides, *style); let s = Shape::new((*x0, *y0), (*x1, *y1), *sides, *style);
self.add_layer(&path, Layer::new(LayerDataTypes::Shape(s)), *insert_index)?; let id = self.add_layer(&path, Layer::new(LayerDataTypes::Shape(s)), *insert_index)?;
let path = [path.clone(), vec![id]].concat();
Some(vec![DocumentResponse::DocumentChanged]) Some(vec![DocumentResponse::DocumentChanged, DocumentResponse::SelectLayer { path }])
} }
Operation::DeleteLayer { path } => { Operation::DeleteLayer { path } => {
self.delete(&path)?; self.delete(&path)?;
@ -281,8 +286,9 @@ impl Document {
responses.append(&mut op_responses); responses.append(&mut op_responses);
} }
} }
responses.extend(vec![DocumentResponse::DocumentChanged, DocumentResponse::FolderChanged { path }]);
Some(vec![DocumentResponse::DocumentChanged, DocumentResponse::FolderChanged { path }]) Some(responses)
} }
Operation::ToggleVisibility { path } => { Operation::ToggleVisibility { path } => {
let _ = self.layer_mut(&path).map(|layer| { let _ = self.layer_mut(&path).map(|layer| {

View File

@ -7,6 +7,7 @@ use std::fmt;
pub enum DocumentResponse { pub enum DocumentResponse {
DocumentChanged, DocumentChanged,
FolderChanged { path: Vec<LayerId> }, FolderChanged { path: Vec<LayerId> },
SelectLayer { path: Vec<LayerId> },
} }
impl fmt::Display for DocumentResponse { impl fmt::Display for DocumentResponse {
@ -14,6 +15,7 @@ impl fmt::Display for DocumentResponse {
let name = match self { let name = match self {
DocumentResponse::DocumentChanged { .. } => "DocumentChanged", DocumentResponse::DocumentChanged { .. } => "DocumentChanged",
DocumentResponse::FolderChanged { .. } => "FolderChanged", DocumentResponse::FolderChanged { .. } => "FolderChanged",
DocumentResponse::SelectLayer { .. } => "SelectLayer",
}; };
formatter.write_str(name) formatter.write_str(name)

View File

@ -21,11 +21,16 @@ impl Dispatcher {
pub fn handle_message<T: Into<Message>>(&mut self, message: T) -> Result<(), EditorError> { pub fn handle_message<T: Into<Message>>(&mut self, message: T) -> Result<(), EditorError> {
let message = message.into(); let message = message.into();
use Message::*; use Message::*;
if !matches!( if !(matches!(
message, message,
Message::InputPreprocessor(_) | Message::InputMapper(_) | Message::Tool(ToolMessage::Rectangle(RectangleMessage::MouseMove)) Message::InputPreprocessor(_)
) { | Message::InputMapper(_)
log::trace!("Message: {}", message.to_discriminant().global_name()); | Message::Document(DocumentMessage::RenderDocument)
| Message::Frontend(FrontendMessage::UpdateCanvas { .. })
| Message::Document(DocumentMessage::DispatchOperation { .. })
) || MessageDiscriminant::from(&message).local_name().ends_with("MouseMove"))
{
log::trace!("Message: {}", message.to_discriminant().local_name());
} }
match message { match message {
NoOp => (), NoOp => (),

View File

@ -53,6 +53,7 @@ impl Document {
.layers() .layers()
.iter() .iter()
.zip(folder.layer_ids.iter()) .zip(folder.layer_ids.iter())
.rev()
.map(|(layer, id)| { .map(|(layer, id)| {
let path = [path, &[*id]].concat(); let path = [path, &[*id]].concat();
layer_panel_entry(layer_data(self_layer_data, &path), layer, path) layer_panel_entry(layer_data(self_layer_data, &path), layer, path)

View File

@ -10,6 +10,7 @@ pub enum DocumentMessage {
DispatchOperation(DocumentOperation), DispatchOperation(DocumentOperation),
SelectLayers(Vec<Vec<LayerId>>), SelectLayers(Vec<Vec<LayerId>>),
DeleteLayer(Vec<LayerId>), DeleteLayer(Vec<LayerId>),
DeleteSelectedLayers,
AddFolder(Vec<LayerId>), AddFolder(Vec<LayerId>),
RenameLayer(Vec<LayerId>, String), RenameLayer(Vec<LayerId>, String),
ToggleLayerVisibility(Vec<LayerId>), ToggleLayerVisibility(Vec<LayerId>),
@ -56,6 +57,14 @@ impl DocumentMessageHandler {
FrontendMessage::ExpandFolder { path, children }.into() FrontendMessage::ExpandFolder { path, children }.into()
}) })
} }
fn clear_selection(&mut self) {
self.active_document_mut().layer_data.values_mut().for_each(|layer_data| layer_data.selected = false);
}
fn select_layer(&mut self, path: &[LayerId]) -> Option<Message> {
self.active_document_mut().layer_data(&path).selected = true;
// TODO: Add deduplication
(!path.is_empty()).then(|| self.handle_folder_changed(path[..path.len() - 1].to_vec())).flatten()
}
} }
impl Default for DocumentMessageHandler { impl Default for DocumentMessageHandler {
@ -95,11 +104,18 @@ impl MessageHandler<DocumentMessage, ()> for DocumentMessageHandler {
self.active_document_mut().layer_data(&path).expanded ^= true; self.active_document_mut().layer_data(&path).expanded ^= true;
responses.extend(self.handle_folder_changed(path)); responses.extend(self.handle_folder_changed(path));
} }
SelectLayers(paths) => { DeleteSelectedLayers => {
// TODO: Replace with drain_filter https://github.com/rust-lang/rust/issues/59618
let paths: Vec<Vec<LayerId>> = self.active_document().layer_data.iter().filter_map(|(path, data)| data.selected.then(|| path.clone())).collect();
for path in paths { for path in paths {
self.active_document_mut().layer_data(&path).selected ^= true; self.active_document_mut().layer_data.remove(&path);
responses.extend(self.handle_folder_changed(path)); responses.push_back(DocumentOperation::DeleteLayer { path }.into())
// TODO: Add deduplication }
}
SelectLayers(paths) => {
self.clear_selection();
for path in paths {
responses.extend(self.select_layer(&path));
} }
} }
Undo => { Undo => {
@ -116,6 +132,14 @@ impl MessageHandler<DocumentMessage, ()> for DocumentMessageHandler {
.into_iter() .into_iter()
.map(|response| match response { .map(|response| match response {
DocumentResponse::FolderChanged { path } => self.handle_folder_changed(path), DocumentResponse::FolderChanged { path } => self.handle_folder_changed(path),
DocumentResponse::SelectLayer { path } => {
if !self.active_document().document.work_mounted {
self.clear_selection();
self.select_layer(&path)
} else {
None
}
}
DocumentResponse::DocumentChanged => unreachable!(), DocumentResponse::DocumentChanged => unreachable!(),
}) })
.flatten(), .flatten(),
@ -134,5 +158,11 @@ impl MessageHandler<DocumentMessage, ()> for DocumentMessageHandler {
message => todo!("document_action_handler does not implement: {}", message.to_discriminant().global_name()), message => todo!("document_action_handler does not implement: {}", message.to_discriminant().global_name()),
} }
} }
advertise_actions!(DocumentMessageDiscriminant; Undo, RenderDocument, ExportDocument); fn actions(&self) -> ActionList {
if self.active_document().layer_data.values().any(|data| data.selected) {
actions!(DocumentMessageDiscriminant; Undo, DeleteSelectedLayers, RenderDocument, ExportDocument)
} else {
actions!(DocumentMessageDiscriminant; Undo, RenderDocument, ExportDocument)
}
}
} }

View File

@ -28,7 +28,6 @@ impl FrontendMessageHandler {
impl MessageHandler<FrontendMessage, ()> for FrontendMessageHandler { impl MessageHandler<FrontendMessage, ()> for FrontendMessageHandler {
fn process_action(&mut self, message: FrontendMessage, _data: (), _responses: &mut VecDeque<Message>) { fn process_action(&mut self, message: FrontendMessage, _data: (), _responses: &mut VecDeque<Message>) {
log::trace!("Sending {} Response", message.to_discriminant().global_name());
(self.callback)(message) (self.callback)(message)
} }
advertise_actions!( advertise_actions!(

View File

@ -147,6 +147,9 @@ impl Default for Mapping {
entry! {action=PenMessage::Confirm, key_down=KeyEnter}, entry! {action=PenMessage::Confirm, key_down=KeyEnter},
// Document Actions // Document Actions
entry! {action=DocumentMessage::Undo, key_down=KeyZ, modifiers=[KeyControl]}, entry! {action=DocumentMessage::Undo, key_down=KeyZ, modifiers=[KeyControl]},
entry! {action=DocumentMessage::DeleteSelectedLayers, key_down=KeyDelete},
entry! {action=DocumentMessage::DeleteSelectedLayers, key_down=KeyX},
entry! {action=DocumentMessage::DeleteSelectedLayers, key_down=KeyBackspace},
entry! {action=DocumentMessage::ExportDocument, key_down=KeyS, modifiers=[KeyControl, KeyShift]}, entry! {action=DocumentMessage::ExportDocument, key_down=KeyS, modifiers=[KeyControl, KeyShift]},
entry! {action=DocumentMessage::ExportDocument, key_down=KeyE, modifiers=[KeyControl]}, entry! {action=DocumentMessage::ExportDocument, key_down=KeyE, modifiers=[KeyControl]},
// Tool Actions // Tool Actions

View File

@ -1,7 +1,7 @@
pub const NUMBER_OF_KEYS: usize = Key::NumKeys as usize; pub const NUMBER_OF_KEYS: usize = Key::NumKeys as usize;
// Edit this to specify the storage type used // Edit this to specify the storage type used
// TODO: Increase size of type // TODO: Increase size of type
pub type StorageType = u8; pub type StorageType = u128;
const STORAGE_SIZE: u32 = std::mem::size_of::<usize>() as u32 * 8 + 2 - std::mem::size_of::<StorageType>().leading_zeros(); const STORAGE_SIZE: u32 = std::mem::size_of::<usize>() as u32 * 8 + 2 - std::mem::size_of::<StorageType>().leading_zeros();
const STORAGE_SIZE_BITS: usize = 1 << STORAGE_SIZE; const STORAGE_SIZE_BITS: usize = 1 << STORAGE_SIZE;
const KEY_MASK_STORAGE_LENGTH: usize = (NUMBER_OF_KEYS + STORAGE_SIZE_BITS - 1) >> STORAGE_SIZE; const KEY_MASK_STORAGE_LENGTH: usize = (NUMBER_OF_KEYS + STORAGE_SIZE_BITS - 1) >> STORAGE_SIZE;
@ -55,6 +55,8 @@ pub enum Key {
KeyEnter, KeyEnter,
KeyShift, KeyShift,
KeyControl, KeyControl,
KeyDelete,
KeyBackspace,
KeyAlt, KeyAlt,
KeyEscape, KeyEscape,