Move layer selection logic from vue to editor (#410)
* Add vue selectLayer(layer, ctrl, shift) * Individual selection working, range fill next * Frontend package-lock.json seems apparently needs to be pushed. Weird. * Selection working with ctrl, shift from editor. Still some bugs to sqaush with folder nesting. * WIP resolving nesting folders issues * Changed comparison approach, handling corner cases now * Fully working selection. * Reverted changes to package-lock.json * Removed unused code * Resolved ctrl click not behaving similar to windows * Slight comment clarification * Double checked a windows behavior and corrected. Changed last_selected name. * Simplified if statement slightly. * Made the naming clearer regarding UUIDs versus indices * Clarified comments further * Minor comment fixup * Implemented suggestions, clarified comments * Resolved todo regarding clearing selection when ctrl not pressed * Ensure we only push responses when needed Co-authored-by: Keavon Chambers <keavon@keavon.com> Reviewed by: @TrueDoctor <3
This commit is contained in:
parent
30418c51f8
commit
e54fedc6a5
|
|
@ -191,7 +191,7 @@ mod test {
|
||||||
const LINE_INDEX: usize = 0;
|
const LINE_INDEX: usize = 0;
|
||||||
const PEN_INDEX: usize = 1;
|
const PEN_INDEX: usize = 1;
|
||||||
|
|
||||||
editor.handle_message(DocumentMessage::CreateFolder(vec![]));
|
editor.handle_message(DocumentMessage::CreateEmptyFolder(vec![]));
|
||||||
|
|
||||||
let document_before_added_shapes = editor.dispatcher.documents_message_handler.active_document().graphene_document.clone();
|
let document_before_added_shapes = editor.dispatcher.documents_message_handler.active_document().graphene_document.clone();
|
||||||
let folder_id = document_before_added_shapes.root.as_folder().unwrap().layer_ids[FOLDER_INDEX];
|
let folder_id = document_before_added_shapes.root.as_folder().unwrap().layer_ids[FOLDER_INDEX];
|
||||||
|
|
|
||||||
|
|
@ -64,6 +64,7 @@ pub struct DocumentMessageHandler {
|
||||||
pub saved_document_identifier: u64,
|
pub saved_document_identifier: u64,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub layer_data: HashMap<Vec<LayerId>, LayerData>,
|
pub layer_data: HashMap<Vec<LayerId>, LayerData>,
|
||||||
|
layer_range_selection_reference: Vec<LayerId>,
|
||||||
movement_handler: MovementMessageHandler,
|
movement_handler: MovementMessageHandler,
|
||||||
transform_layer_handler: TransformLayerMessageHandler,
|
transform_layer_handler: TransformLayerMessageHandler,
|
||||||
pub snapping_enabled: bool,
|
pub snapping_enabled: bool,
|
||||||
|
|
@ -78,6 +79,7 @@ impl Default for DocumentMessageHandler {
|
||||||
name: String::from("Untitled Document"),
|
name: String::from("Untitled Document"),
|
||||||
saved_document_identifier: 0,
|
saved_document_identifier: 0,
|
||||||
layer_data: vec![(vec![], LayerData::new(true))].into_iter().collect(),
|
layer_data: vec![(vec![], LayerData::new(true))].into_iter().collect(),
|
||||||
|
layer_range_selection_reference: Vec::new(),
|
||||||
movement_handler: MovementMessageHandler::default(),
|
movement_handler: MovementMessageHandler::default(),
|
||||||
transform_layer_handler: TransformLayerMessageHandler::default(),
|
transform_layer_handler: TransformLayerMessageHandler::default(),
|
||||||
snapping_enabled: true,
|
snapping_enabled: true,
|
||||||
|
|
@ -96,12 +98,13 @@ pub enum DocumentMessage {
|
||||||
SetSelectedLayers(Vec<Vec<LayerId>>),
|
SetSelectedLayers(Vec<Vec<LayerId>>),
|
||||||
AddSelectedLayers(Vec<Vec<LayerId>>),
|
AddSelectedLayers(Vec<Vec<LayerId>>),
|
||||||
SelectAllLayers,
|
SelectAllLayers,
|
||||||
|
SelectLayer(Vec<LayerId>, bool, bool),
|
||||||
SelectionChanged,
|
SelectionChanged,
|
||||||
DeselectAllLayers,
|
DeselectAllLayers,
|
||||||
DeleteLayer(Vec<LayerId>),
|
DeleteLayer(Vec<LayerId>),
|
||||||
DeleteSelectedLayers,
|
DeleteSelectedLayers,
|
||||||
DuplicateSelectedLayers,
|
DuplicateSelectedLayers,
|
||||||
CreateFolder(Vec<LayerId>),
|
CreateEmptyFolder(Vec<LayerId>),
|
||||||
SetBlendModeForSelectedLayers(BlendMode),
|
SetBlendModeForSelectedLayers(BlendMode),
|
||||||
SetOpacityForSelectedLayers(f64),
|
SetOpacityForSelectedLayers(f64),
|
||||||
RenameLayer(Vec<LayerId>, String),
|
RenameLayer(Vec<LayerId>, String),
|
||||||
|
|
@ -312,6 +315,7 @@ impl DocumentMessageHandler {
|
||||||
saved_document_identifier: 0,
|
saved_document_identifier: 0,
|
||||||
name,
|
name,
|
||||||
layer_data: vec![(vec![], LayerData::new(true))].into_iter().collect(),
|
layer_data: vec![(vec![], LayerData::new(true))].into_iter().collect(),
|
||||||
|
layer_range_selection_reference: Vec::new(),
|
||||||
movement_handler: MovementMessageHandler::default(),
|
movement_handler: MovementMessageHandler::default(),
|
||||||
transform_layer_handler: TransformLayerMessageHandler::default(),
|
transform_layer_handler: TransformLayerMessageHandler::default(),
|
||||||
snapping_enabled: true,
|
snapping_enabled: true,
|
||||||
|
|
@ -497,14 +501,16 @@ impl MessageHandler<DocumentMessage, &InputPreprocessor> for DocumentMessageHand
|
||||||
.into(),
|
.into(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
CreateFolder(mut path) => {
|
CreateEmptyFolder(mut path) => {
|
||||||
let id = generate_uuid();
|
let id = generate_uuid();
|
||||||
path.push(id);
|
path.push(id);
|
||||||
self.layerdata_mut(&path).expanded = true;
|
self.layerdata_mut(&path).expanded = true;
|
||||||
responses.push_back(DocumentOperation::CreateFolder { path }.into())
|
responses.push_back(DocumentOperation::CreateFolder { path }.into())
|
||||||
}
|
}
|
||||||
GroupSelectedLayers => {
|
GroupSelectedLayers => {
|
||||||
let common_prefix = self.graphene_document.common_prefix(self.selected_layers());
|
let selected_layers = self.selected_layers();
|
||||||
|
|
||||||
|
let common_prefix = self.graphene_document.common_layer_path_prefix(selected_layers);
|
||||||
let (_id, common_prefix) = common_prefix.split_last().unwrap_or((&0, &[]));
|
let (_id, common_prefix) = common_prefix.split_last().unwrap_or((&0, &[]));
|
||||||
|
|
||||||
let mut new_folder_path = common_prefix.to_vec();
|
let mut new_folder_path = common_prefix.to_vec();
|
||||||
|
|
@ -568,6 +574,43 @@ impl MessageHandler<DocumentMessage, &InputPreprocessor> for DocumentMessageHand
|
||||||
responses.push_back(DocumentOperation::DuplicateLayer { path }.into());
|
responses.push_back(DocumentOperation::DuplicateLayer { path }.into());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
SelectLayer(selected, ctrl, shift) => {
|
||||||
|
let mut paths = vec![];
|
||||||
|
let last_selection_exists = !self.layer_range_selection_reference.is_empty();
|
||||||
|
|
||||||
|
// If we have shift pressed and a layer already selected then fill the range
|
||||||
|
if shift && last_selection_exists {
|
||||||
|
// Fill the selection range
|
||||||
|
self.layer_data
|
||||||
|
.iter()
|
||||||
|
.filter(|(target, _)| self.graphene_document.layer_is_between(&target, &selected, &self.layer_range_selection_reference))
|
||||||
|
.for_each(|(layer_path, _)| {
|
||||||
|
paths.push(layer_path.clone());
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
if ctrl {
|
||||||
|
// Toggle selection when holding ctrl
|
||||||
|
let layer = self.layerdata_mut(&selected);
|
||||||
|
layer.selected = !layer.selected;
|
||||||
|
responses.push_back(LayerChanged(selected.clone()).into());
|
||||||
|
} else {
|
||||||
|
paths.push(selected.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set our last selection reference
|
||||||
|
self.layer_range_selection_reference = selected;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't create messages for empty operations
|
||||||
|
if paths.len() > 0 {
|
||||||
|
// Add or set our selected layers
|
||||||
|
if ctrl {
|
||||||
|
responses.push_front(AddSelectedLayers(paths).into());
|
||||||
|
} else {
|
||||||
|
responses.push_front(SetSelectedLayers(paths).into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
SetSelectedLayers(paths) => {
|
SetSelectedLayers(paths) => {
|
||||||
self.layer_data.iter_mut().filter(|(_, layer_data)| layer_data.selected).for_each(|(path, layer_data)| {
|
self.layer_data.iter_mut().filter(|(_, layer_data)| layer_data.selected).for_each(|(path, layer_data)| {
|
||||||
layer_data.selected = false;
|
layer_data.selected = false;
|
||||||
|
|
@ -593,7 +636,10 @@ impl MessageHandler<DocumentMessage, &InputPreprocessor> for DocumentMessageHand
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
responses.push_front(SetSelectedLayers(all_layer_paths).into());
|
responses.push_front(SetSelectedLayers(all_layer_paths).into());
|
||||||
}
|
}
|
||||||
DeselectAllLayers => responses.push_front(SetSelectedLayers(vec![]).into()),
|
DeselectAllLayers => {
|
||||||
|
responses.push_front(SetSelectedLayers(vec![]).into());
|
||||||
|
self.layer_range_selection_reference.clear();
|
||||||
|
}
|
||||||
DocumentHistoryBackward => self.undo(responses).unwrap_or_else(|e| log::warn!("{}", e)),
|
DocumentHistoryBackward => self.undo(responses).unwrap_or_else(|e| log::warn!("{}", e)),
|
||||||
DocumentHistoryForward => self.redo(responses).unwrap_or_else(|e| log::warn!("{}", e)),
|
DocumentHistoryForward => self.redo(responses).unwrap_or_else(|e| log::warn!("{}", e)),
|
||||||
Undo => {
|
Undo => {
|
||||||
|
|
@ -639,7 +685,8 @@ impl MessageHandler<DocumentMessage, &InputPreprocessor> for DocumentMessageHand
|
||||||
self.layer_data.insert(path.clone(), LayerData::new(false));
|
self.layer_data.insert(path.clone(), LayerData::new(false));
|
||||||
responses.push_back(LayerChanged(path.clone()).into());
|
responses.push_back(LayerChanged(path.clone()).into());
|
||||||
if !self.graphene_document.layer(&path).unwrap().overlay {
|
if !self.graphene_document.layer(&path).unwrap().overlay {
|
||||||
responses.push_back(SetSelectedLayers(vec![path]).into())
|
self.layer_range_selection_reference = path.clone();
|
||||||
|
responses.push_back(SetSelectedLayers(vec![path]).into());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
DocumentResponse::DocumentChanged => responses.push_back(RenderDocument.into()),
|
DocumentResponse::DocumentChanged => responses.push_back(RenderDocument.into()),
|
||||||
|
|
|
||||||
|
|
@ -209,7 +209,7 @@ impl Default for Mapping {
|
||||||
entry! {action=DocumentMessage::Undo, key_down=KeyZ, modifiers=[KeyControl]},
|
entry! {action=DocumentMessage::Undo, key_down=KeyZ, modifiers=[KeyControl]},
|
||||||
entry! {action=DocumentMessage::DeselectAllLayers, key_down=KeyA, modifiers=[KeyControl, KeyAlt]},
|
entry! {action=DocumentMessage::DeselectAllLayers, key_down=KeyA, modifiers=[KeyControl, KeyAlt]},
|
||||||
entry! {action=DocumentMessage::SelectAllLayers, key_down=KeyA, modifiers=[KeyControl]},
|
entry! {action=DocumentMessage::SelectAllLayers, key_down=KeyA, modifiers=[KeyControl]},
|
||||||
entry! {action=DocumentMessage::CreateFolder(vec![]), key_down=KeyN, modifiers=[KeyControl, KeyShift]},
|
entry! {action=DocumentMessage::CreateEmptyFolder(vec![]), key_down=KeyN, modifiers=[KeyControl, KeyShift]},
|
||||||
entry! {action=DocumentMessage::DeleteSelectedLayers, key_down=KeyDelete},
|
entry! {action=DocumentMessage::DeleteSelectedLayers, key_down=KeyDelete},
|
||||||
entry! {action=DocumentMessage::DeleteSelectedLayers, key_down=KeyX},
|
entry! {action=DocumentMessage::DeleteSelectedLayers, key_down=KeyX},
|
||||||
entry! {action=DocumentMessage::DeleteSelectedLayers, key_down=KeyBackspace},
|
entry! {action=DocumentMessage::DeleteSelectedLayers, key_down=KeyBackspace},
|
||||||
|
|
|
||||||
|
|
@ -36,10 +36,10 @@
|
||||||
class="layer"
|
class="layer"
|
||||||
:class="{ selected: layer.layer_data.selected }"
|
:class="{ selected: layer.layer_data.selected }"
|
||||||
:style="{ marginLeft: layerIndent(layer) }"
|
:style="{ marginLeft: layerIndent(layer) }"
|
||||||
@click.shift.exact.stop="handleShiftClick(layer)"
|
@click.shift.exact.stop="selectLayer(layer, false, true)"
|
||||||
@click.ctrl.exact.stop="handleControlClick(layer)"
|
@click.shift.ctrl.exact.stop="selectLayer(layer, true, true)"
|
||||||
@click.alt.exact.stop="handleControlClick(layer)"
|
@click.ctrl.exact.stop="selectLayer(layer, true, false)"
|
||||||
@click.exact.stop="handleClick(layer)"
|
@click.exact.stop="selectLayer(layer, false, false)"
|
||||||
>
|
>
|
||||||
<div class="layer-thumbnail" v-html="layer.thumbnail"></div>
|
<div class="layer-thumbnail" v-html="layer.thumbnail"></div>
|
||||||
<div class="layer-type-icon">
|
<div class="layer-type-icon">
|
||||||
|
|
@ -289,39 +289,8 @@ export default defineComponent({
|
||||||
async setLayerOpacity() {
|
async setLayerOpacity() {
|
||||||
this.editor.instance.set_opacity_for_selected_layers(this.opacity);
|
this.editor.instance.set_opacity_for_selected_layers(this.opacity);
|
||||||
},
|
},
|
||||||
async handleControlClick(clickedLayer: LayerPanelEntry) {
|
async selectLayer(clickedLayer: LayerPanelEntry, ctrl: boolean, shift: boolean) {
|
||||||
const index = this.layers.indexOf(clickedLayer);
|
this.editor.instance.select_layer(clickedLayer.path, ctrl, shift);
|
||||||
clickedLayer.layer_data.selected = !clickedLayer.layer_data.selected;
|
|
||||||
|
|
||||||
this.selectionRangeEndLayer = undefined;
|
|
||||||
this.selectionRangeStartLayer =
|
|
||||||
this.layers.slice(index).filter((layer) => layer.layer_data.selected)[0] ||
|
|
||||||
this.layers
|
|
||||||
.slice(0, index)
|
|
||||||
.reverse()
|
|
||||||
.filter((layer) => layer.layer_data.selected)[0];
|
|
||||||
|
|
||||||
this.sendSelectedLayers();
|
|
||||||
},
|
|
||||||
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 previous Shift+Click)
|
|
||||||
this.clearSelection();
|
|
||||||
|
|
||||||
this.selectionRangeEndLayer = clickedLayer;
|
|
||||||
if (!this.selectionRangeStartLayer) this.selectionRangeStartLayer = clickedLayer;
|
|
||||||
this.fillSelectionRange(this.selectionRangeStartLayer, this.selectionRangeEndLayer, true);
|
|
||||||
|
|
||||||
this.sendSelectedLayers();
|
|
||||||
},
|
|
||||||
async handleClick(clickedLayer: LayerPanelEntry) {
|
|
||||||
this.selectionRangeStartLayer = clickedLayer;
|
|
||||||
this.selectionRangeEndLayer = clickedLayer;
|
|
||||||
|
|
||||||
this.clearSelection();
|
|
||||||
clickedLayer.layer_data.selected = true;
|
|
||||||
|
|
||||||
this.sendSelectedLayers();
|
|
||||||
},
|
},
|
||||||
async deselectAllLayers() {
|
async deselectAllLayers() {
|
||||||
this.selectionRangeStartLayer = undefined;
|
this.selectionRangeStartLayer = undefined;
|
||||||
|
|
@ -329,39 +298,11 @@ export default defineComponent({
|
||||||
|
|
||||||
this.editor.instance.deselect_all_layers();
|
this.editor.instance.deselect_all_layers();
|
||||||
},
|
},
|
||||||
async fillSelectionRange(start: LayerPanelEntry, end: LayerPanelEntry, selected = true) {
|
|
||||||
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();
|
|
||||||
|
|
||||||
if (min !== -1) {
|
|
||||||
for (let i = min; i <= max; i += 1) {
|
|
||||||
this.layers[i].layer_data.selected = selected;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
async clearSelection() {
|
async clearSelection() {
|
||||||
this.layers.forEach((layer) => {
|
this.layers.forEach((layer) => {
|
||||||
layer.layer_data.selected = false;
|
layer.layer_data.selected = false;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
async sendSelectedLayers() {
|
|
||||||
const paths = this.layers.filter((layer) => layer.layer_data.selected).map((layer) => layer.path);
|
|
||||||
|
|
||||||
const length = paths.reduce((acc, cur) => acc + cur.length, 0) + paths.length - 1;
|
|
||||||
const output = new BigUint64Array(length);
|
|
||||||
|
|
||||||
let i = 0;
|
|
||||||
paths.forEach((path, index) => {
|
|
||||||
output.set(path, i);
|
|
||||||
i += path.length;
|
|
||||||
if (index < paths.length) {
|
|
||||||
output[i] = (1n << 64n) - 1n;
|
|
||||||
}
|
|
||||||
i += 1;
|
|
||||||
});
|
|
||||||
this.editor.instance.select_layers(output);
|
|
||||||
},
|
|
||||||
setBlendModeForSelectedLayers() {
|
setBlendModeForSelectedLayers() {
|
||||||
const selected = this.layers.filter((layer) => layer.layer_data.selected);
|
const selected = this.layers.filter((layer) => layer.layer_data.selected);
|
||||||
|
|
||||||
|
|
@ -383,6 +324,7 @@ export default defineComponent({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
setOpacityForSelectedLayers() {
|
setOpacityForSelectedLayers() {
|
||||||
|
// todo figure out why this is here
|
||||||
const selected = this.layers.filter((layer) => layer.layer_data.selected);
|
const selected = this.layers.filter((layer) => layer.layer_data.selected);
|
||||||
|
|
||||||
if (selected.length < 1) {
|
if (selected.length < 1) {
|
||||||
|
|
|
||||||
|
|
@ -322,6 +322,11 @@ impl JsEditorHandle {
|
||||||
self.dispatch(message);
|
self.dispatch(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn select_layer(&self, paths: Vec<LayerId>, ctrl: bool, shift: bool) {
|
||||||
|
let message = DocumentMessage::SelectLayer(paths, ctrl, shift);
|
||||||
|
self.dispatch(message);
|
||||||
|
}
|
||||||
|
|
||||||
/// Select all layers
|
/// Select all layers
|
||||||
pub fn select_all_layers(&self) {
|
pub fn select_all_layers(&self) {
|
||||||
let message = DocumentMessage::SelectAllLayers;
|
let message = DocumentMessage::SelectAllLayers;
|
||||||
|
|
@ -443,7 +448,7 @@ impl JsEditorHandle {
|
||||||
|
|
||||||
/// Requests the backend to add a layer to the layer list
|
/// Requests the backend to add a layer to the layer list
|
||||||
pub fn add_folder(&self, path: Vec<LayerId>) {
|
pub fn add_folder(&self, path: Vec<LayerId>) {
|
||||||
let message = DocumentMessage::CreateFolder(path);
|
let message = DocumentMessage::CreateEmptyFolder(path);
|
||||||
self.dispatch(message);
|
self.dispatch(message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
use std::{
|
use std::{
|
||||||
|
cmp::max,
|
||||||
collections::hash_map::DefaultHasher,
|
collections::hash_map::DefaultHasher,
|
||||||
hash::{Hash, Hasher},
|
hash::{Hash, Hasher},
|
||||||
};
|
};
|
||||||
|
|
@ -102,7 +103,7 @@ impl Document {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn deepest_common_folder<'a>(&self, layers: impl Iterator<Item = &'a [LayerId]>) -> Result<&'a [LayerId], DocumentError> {
|
pub fn deepest_common_folder<'a>(&self, layers: impl Iterator<Item = &'a [LayerId]>) -> Result<&'a [LayerId], DocumentError> {
|
||||||
let common_prefix_of_path = self.common_prefix(layers);
|
let common_prefix_of_path = self.common_layer_path_prefix(layers);
|
||||||
|
|
||||||
Ok(match self.layer(common_prefix_of_path)?.data {
|
Ok(match self.layer(common_prefix_of_path)?.data {
|
||||||
LayerDataType::Folder(_) => common_prefix_of_path,
|
LayerDataType::Folder(_) => common_prefix_of_path,
|
||||||
|
|
@ -110,7 +111,7 @@ impl Document {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn common_prefix<'a>(&self, layers: impl Iterator<Item = &'a [LayerId]>) -> &'a [LayerId] {
|
pub fn common_layer_path_prefix<'a>(&self, layers: impl Iterator<Item = &'a [LayerId]>) -> &'a [LayerId] {
|
||||||
layers
|
layers
|
||||||
.reduce(|a, b| {
|
.reduce(|a, b| {
|
||||||
let number_of_uncommon_ids_in_a = (0..a.len()).position(|i| b.starts_with(&a[..a.len() - i])).unwrap_or_default();
|
let number_of_uncommon_ids_in_a = (0..a.len()).position(|i| b.starts_with(&a[..a.len() - i])).unwrap_or_default();
|
||||||
|
|
@ -119,6 +120,53 @@ impl Document {
|
||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Determines which layer is closer to the root, if path_a return true, if path_b return false
|
||||||
|
// Answers the question: Is A closer to the root than B?
|
||||||
|
pub fn layer_closer_to_root(&self, path_a: &Vec<u64>, path_b: &Vec<u64>) -> bool {
|
||||||
|
// Convert UUIDs to indices
|
||||||
|
let indices_for_path_a = self.indices_for_path(path_a).unwrap();
|
||||||
|
let indices_for_path_b = self.indices_for_path(path_b).unwrap();
|
||||||
|
|
||||||
|
let longest = max(indices_for_path_a.len(), indices_for_path_b.len());
|
||||||
|
for i in 0..longest {
|
||||||
|
// usize::MAX becomes negative one here, sneaky. So folders are compared as [X, -1]. This is intentional.
|
||||||
|
let index_a = *indices_for_path_a.get(i).unwrap_or(&usize::MAX) as i32;
|
||||||
|
let index_b = *indices_for_path_b.get(i).unwrap_or(&usize::MAX) as i32;
|
||||||
|
|
||||||
|
// index_a == index_b -> true, this means the "2" indices being compared are within the same folder
|
||||||
|
// eg -> [2, X] == [2, X] since we are only comparing the "2" in this iteration
|
||||||
|
// Continue onto comparing the X indices.
|
||||||
|
if index_a == index_b {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If index_a is smaller, index_a is closer to the root
|
||||||
|
return index_a < index_b;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Is the target layer between a <-> b layers, inclusive
|
||||||
|
pub fn layer_is_between(&self, target: &Vec<u64>, path_a: &Vec<u64>, path_b: &Vec<u64>) -> bool {
|
||||||
|
// If the target is a nonsense path, it isn't between
|
||||||
|
if target.len() < 1 {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// This function is inclusive, so we consider path_a, path_b to be between themselves
|
||||||
|
if target == path_a || target == path_b {
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
// These can't both be true and be between two values
|
||||||
|
let layer_vs_a = self.layer_closer_to_root(target, path_a);
|
||||||
|
let layer_vs_b = self.layer_closer_to_root(target, path_b);
|
||||||
|
|
||||||
|
// To be inbetween you need to be above A and below B or vice versa
|
||||||
|
return layer_vs_a != layer_vs_b;
|
||||||
|
}
|
||||||
|
|
||||||
/// Given a path to a layer, returns a vector of the indices in the layer tree
|
/// Given a path to a layer, returns a vector of the indices in the layer tree
|
||||||
/// These indices can be used to order a list of layers
|
/// These indices can be used to order a list of layers
|
||||||
pub fn indices_for_path(&self, path: &[LayerId]) -> Result<Vec<usize>, DocumentError> {
|
pub fn indices_for_path(&self, path: &[LayerId]) -> Result<Vec<usize>, DocumentError> {
|
||||||
|
|
@ -126,6 +174,7 @@ impl Document {
|
||||||
let mut indices = vec![];
|
let mut indices = vec![];
|
||||||
let (path, layer_id) = split_path(path)?;
|
let (path, layer_id) = split_path(path)?;
|
||||||
|
|
||||||
|
// TODO: appears to be n^2? should we maintain a lookup table?
|
||||||
for id in path {
|
for id in path {
|
||||||
let pos = root.layer_ids.iter().position(|x| *x == *id).ok_or(DocumentError::LayerNotFound)?;
|
let pos = root.layer_ids.iter().position(|x| *x == *id).ok_or(DocumentError::LayerNotFound)?;
|
||||||
indices.push(pos);
|
indices.push(pos);
|
||||||
|
|
|
||||||
|
|
@ -41,6 +41,7 @@ impl Folder {
|
||||||
/// When a insertion id is provided, try to insert the layer with the given id.
|
/// When a insertion id is provided, try to insert the layer with the given id.
|
||||||
/// If that id is already used, return None.
|
/// If that id is already used, return None.
|
||||||
/// When no insertion id is provided, search for the next free id and insert it with that.
|
/// When no insertion id is provided, search for the next free id and insert it with that.
|
||||||
|
/// Negative values for insert_index represent distance from the end
|
||||||
pub fn add_layer(&mut self, layer: Layer, id: Option<LayerId>, insert_index: isize) -> Option<LayerId> {
|
pub fn add_layer(&mut self, layer: Layer, id: Option<LayerId>, insert_index: isize) -> Option<LayerId> {
|
||||||
let mut insert_index = insert_index as i128;
|
let mut insert_index = insert_index as i128;
|
||||||
if insert_index < 0 {
|
if insert_index < 0 {
|
||||||
|
|
@ -54,13 +55,16 @@ impl Folder {
|
||||||
if self.layer_ids.contains(&self.next_assignment_id) {
|
if self.layer_ids.contains(&self.next_assignment_id) {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
let id = self.next_assignment_id;
|
let id = self.next_assignment_id;
|
||||||
self.layers.insert(insert_index as usize, layer);
|
self.layers.insert(insert_index as usize, layer);
|
||||||
self.layer_ids.insert(insert_index as usize, id);
|
self.layer_ids.insert(insert_index as usize, id);
|
||||||
|
|
||||||
// Linear probing for collision avoidance
|
// Linear probing for collision avoidance
|
||||||
while self.layer_ids.contains(&self.next_assignment_id) {
|
while self.layer_ids.contains(&self.next_assignment_id) {
|
||||||
self.next_assignment_id += 1;
|
self.next_assignment_id += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
Some(id)
|
Some(id)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue