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:
parent
4f40a1f291
commit
e4b5aa3933
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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| {
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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 => (),
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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!(
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue