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 GitHub
parent 981f138812
commit 0e488d99ff
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 81 additions and 28 deletions

View file

@ -144,9 +144,9 @@ export default defineComponent({
async handleShiftClick(clickedLayer: LayerPanelEntry) {
// 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)
this.selectionRangeEndLayer = clickedLayer;
this.selectionRangeStartLayer = (this.selectionRangeStartLayer as LayerPanelEntry) || clickedLayer;
this.clearSelection();
this.selectionRangeEndLayer = clickedLayer;
if (!this.selectionRangeStartLayer) this.selectionRangeStartLayer = clickedLayer;
this.fillSelectionRange(this.selectionRangeStartLayer, this.selectionRangeEndLayer, true);
this.updateSelection();
},
@ -159,11 +159,13 @@ export default defineComponent({
this.updateSelection();
},
async fillSelectionRange(start: LayerPanelEntry, end: LayerPanelEntry, selected = true) {
const startIndex = this.layers.indexOf(start);
const endIndex = this.layers.indexOf(end);
const startIndex = this.layers.findIndex((layer) => layer.path.join() === start.path.join());
const endIndex = this.layers.findIndex((layer) => layer.path.join() === end.path.join());
const [min, max] = [startIndex, endIndex].sort();
for (let i = min; i <= max; i += 1) {
this.layers[i].layer_data.selected = selected;
if (min !== -1) {
for (let i = min; i <= max; i += 1) {
this.layers[i].layer_data.selected = selected;
}
}
},
async clearSelection() {
@ -173,6 +175,7 @@ export default defineComponent({
},
async updateSelection() {
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 output = new BigUint64Array(length);
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
"capslock" => KeyShift,
"control" => KeyControl,
"delete" => KeyDelete,
"backspace" => KeyBackspace,
"alt" => KeyAlt,
"escape" => KeyEscape,
_ => UnknownKey,

View file

@ -172,9 +172,10 @@ impl Document {
pub fn handle_operation(&mut self, operation: Operation) -> Result<Option<Vec<DocumentResponse>>, DocumentError> {
let responses = match &operation {
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 {
path,
@ -186,9 +187,10 @@ impl Document {
rot,
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 {
path,
@ -199,9 +201,10 @@ impl Document {
y1,
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 {
path,
@ -212,9 +215,10 @@ impl Document {
y1,
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 } => {
let points: Vec<kurbo::Point> = points.iter().map(|&it| it.into()).collect();
@ -233,9 +237,10 @@ impl Document {
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 } => {
self.delete(&path)?;
@ -281,8 +286,9 @@ impl Document {
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 } => {
let _ = self.layer_mut(&path).map(|layer| {

View file

@ -7,6 +7,7 @@ use std::fmt;
pub enum DocumentResponse {
DocumentChanged,
FolderChanged { path: Vec<LayerId> },
SelectLayer { path: Vec<LayerId> },
}
impl fmt::Display for DocumentResponse {
@ -14,6 +15,7 @@ impl fmt::Display for DocumentResponse {
let name = match self {
DocumentResponse::DocumentChanged { .. } => "DocumentChanged",
DocumentResponse::FolderChanged { .. } => "FolderChanged",
DocumentResponse::SelectLayer { .. } => "SelectLayer",
};
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> {
let message = message.into();
use Message::*;
if !matches!(
if !(matches!(
message,
Message::InputPreprocessor(_) | Message::InputMapper(_) | Message::Tool(ToolMessage::Rectangle(RectangleMessage::MouseMove))
) {
log::trace!("Message: {}", message.to_discriminant().global_name());
Message::InputPreprocessor(_)
| Message::InputMapper(_)
| 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 {
NoOp => (),

View file

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

View file

@ -10,6 +10,7 @@ pub enum DocumentMessage {
DispatchOperation(DocumentOperation),
SelectLayers(Vec<Vec<LayerId>>),
DeleteLayer(Vec<LayerId>),
DeleteSelectedLayers,
AddFolder(Vec<LayerId>),
RenameLayer(Vec<LayerId>, String),
ToggleLayerVisibility(Vec<LayerId>),
@ -56,6 +57,14 @@ impl DocumentMessageHandler {
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 {
@ -95,11 +104,18 @@ impl MessageHandler<DocumentMessage, ()> for DocumentMessageHandler {
self.active_document_mut().layer_data(&path).expanded ^= true;
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 {
self.active_document_mut().layer_data(&path).selected ^= true;
responses.extend(self.handle_folder_changed(path));
// TODO: Add deduplication
self.active_document_mut().layer_data.remove(&path);
responses.push_back(DocumentOperation::DeleteLayer { path }.into())
}
}
SelectLayers(paths) => {
self.clear_selection();
for path in paths {
responses.extend(self.select_layer(&path));
}
}
Undo => {
@ -116,6 +132,14 @@ impl MessageHandler<DocumentMessage, ()> for DocumentMessageHandler {
.into_iter()
.map(|response| match response {
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!(),
})
.flatten(),
@ -134,5 +158,11 @@ impl MessageHandler<DocumentMessage, ()> for DocumentMessageHandler {
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 {
fn process_action(&mut self, message: FrontendMessage, _data: (), _responses: &mut VecDeque<Message>) {
log::trace!("Sending {} Response", message.to_discriminant().global_name());
(self.callback)(message)
}
advertise_actions!(

View file

@ -147,6 +147,9 @@ impl Default for Mapping {
entry! {action=PenMessage::Confirm, key_down=KeyEnter},
// Document Actions
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=KeyE, modifiers=[KeyControl]},
// Tool Actions

View file

@ -1,7 +1,7 @@
pub const NUMBER_OF_KEYS: usize = Key::NumKeys as usize;
// Edit this to specify the storage type used
// 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_BITS: usize = 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,
KeyShift,
KeyControl,
KeyDelete,
KeyBackspace,
KeyAlt,
KeyEscape,