Large mouse events refactoring and cleanup

This commit is contained in:
barsdeveloper
2021-10-04 00:32:22 +02:00
parent dbff7a2c5f
commit fece3da438
17 changed files with 545 additions and 414 deletions

View File

@@ -0,0 +1,164 @@
import OrderedIndexArray from "./OrderedIndexArray"
export default class FastSelectionModel {
/**
* @typedef {{
* primaryInf: number,
* primarySup: number,
* secondaryInf: number,
* secondarySup: number
* }} BoundariesInfo
* @typedef {{
* primaryBoundary: number,
* secondaryBoundary: number,
* insertionPosition: number,
* rectangle: number
* onSecondaryAxis: Boolean
* }} Metadata
* @typedef {numeric} Rectangle
* @param {number[]} initialPosition Coordinates of the starting point of selection [primaryAxisValue, secondaryAxisValue].
* @param {Rectangle[]} rectangles Rectangles that can be selected by this object.
* @param {(rect: Rectangle) => BoundariesInfo} boundariesFunc A function that, given a rectangle, it provides the boundaries of such rectangle.
* @param {(rect: Rectangle, selected: bool) => void} selectFunc A function that selects or deselects individual rectangles.
*/
constructor(initialPosition, rectangles, boundariesFunc, selectFunc) {
this.initialPosition = initialPosition
this.finalPosition = initialPosition
/** @type Metadata[] */
this.metadata = new Array(rectangles.length)
this.primaryOrder = new OrderedIndexArray((element) => this.metadata[element].primaryBoundary)
this.secondaryOrder = new OrderedIndexArray((element) => this.metadata[element].secondaryBoundary)
this.selectFunc = selectFunc
this.rectangles = rectangles
this.primaryOrder.reserve(this.rectangles.length)
this.secondaryOrder.reserve(this.rectangles.length)
rectangles.forEach((rect, index) => {
/** @type Metadata */
let rectangleMetadata = {
primaryBoundary: this.initialPosition[0],
secondaryBoundary: this.initialPosition[1],
rectangle: index, // used to move both expandings inside the this.metadata array
onSecondaryAxis: false
}
this.metadata[index] = rectangleMetadata
selectFunc(rect, false) // Initially deselected (Eventually)
const rectangleBoundaries = boundariesFunc(rect)
// Secondary axis first because it may be inserted in this.secondaryOrder during the primary axis check
if (this.initialPosition[1] < rectangleBoundaries.secondaryInf) { // Initial position is before the rectangle
rectangleMetadata.secondaryBoundary = rectangleBoundaries.secondaryInf
} else if (rectangleBoundaries.secondarySup < this.initialPosition[1]) { // Initial position is after the rectangle
rectangleMetadata.secondaryBoundary = rectangleBoundaries.secondarySup
} else {
rectangleMetadata.onSecondaryAxis = true
}
if (this.initialPosition[0] < rectangleBoundaries.primaryInf) { // Initial position is before the rectangle
rectangleMetadata.primaryBoundary = rectangleBoundaries.primaryInf
this.primaryOrder.insert(index)
} else if (rectangleBoundaries.primarySup < this.initialPosition[0]) { // Initial position is after the rectangle
rectangleMetadata.primaryBoundary = rectangleBoundaries.primarySup
this.primaryOrder.insert(index)
} else { // Initial lays inside the rectangle (considering just this axis)
// Secondary order depends on primary order, if primary boundaries are not satisfied, the element is not watched for secondary ones
if (rectangleBoundaries.secondarySup < this.initialPosition[1] || this.initialPosition[1] < rectangleBoundaries.secondaryInf) {
this.secondaryOrder.insert(index)
} else {
selectFunc(rect, true)
}
}
})
this.primaryOrder.currentPosition = this.primaryOrder.getPosition(this.initialPosition[0])
this.secondaryOrder.currentPosition = this.secondaryOrder.getPosition(this.initialPosition[1])
this.computeBoundaries(this.initialPosition)
}
computeBoundaries() {
this.boundaries = {
// Primary axis negative expanding
primaryN: {
v: this.primaryOrder.getPrevValue(),
i: this.primaryOrder.getPrev()
},
primaryP: {
v: this.primaryOrder.getNextValue(),
i: this.primaryOrder.getNext()
},
// Secondary axis negative expanding
secondaryN: {
v: this.secondaryOrder.getPrevValue(),
i: this.secondaryOrder.getPrev()
},
// Secondary axis positive expanding
secondaryP: {
v: this.secondaryOrder.getNextValue(),
i: this.secondaryOrder.getNext()
}
}
}
selectTo(finalPosition) {
const direction = [
Math.sign(finalPosition[0] - this.initialPosition[0]),
Math.sign(finalPosition[1] - this.initialPosition[1])
]
const primaryBoundaryCrossed = (index, added) => {
if (this.metadata[index].onSecondaryAxis) {
this.selectFunc(this.rectangles[index], added)
} else {
if (added) {
this.secondaryOrder.insert(index, finalPosition[1])
const secondaryBoundary = this.metadata[index].secondaryBoundary
if (
// If inserted before the current position
Math.sign(finalPosition[1] - secondaryBoundary) == direction[1]
// And after initial position
&& Math.sign(secondaryBoundary - this.initialPosition[1]) == direction[1]
) {
// Secondary axis is already satisfied then
this.selectFunc(this.rectangles[index], true)
}
} else {
this.selectFunc(this.rectangles[index], false)
this.secondaryOrder.remove(index)
}
}
this.computeBoundaries(finalPosition)
this.selectTo(finalPosition)
}
if (finalPosition[0] < this.boundaries.primaryN.v) {
--this.primaryOrder.currentPosition
primaryBoundaryCrossed(
this.boundaries.primaryN.i,
this.initialPosition[0] > this.boundaries.primaryN.v && finalPosition[0] < this.initialPosition[0])
} else if (finalPosition[0] > this.boundaries.primaryP.v) {
++this.primaryOrder.currentPosition
primaryBoundaryCrossed(
this.boundaries.primaryP.i,
this.initialPosition[0] < this.boundaries.primaryP.v && this.initialPosition[0] < finalPosition[0])
}
const secondaryBoundaryCrossed = (index, added) => {
this.selectFunc(this.rectangles[index], added)
this.computeBoundaries(finalPosition)
this.selectTo(finalPosition)
}
if (finalPosition[1] < this.boundaries.secondaryN.v) {
--this.secondaryOrder.currentPosition
secondaryBoundaryCrossed(
this.boundaries.secondaryN.i,
this.initialPosition[1] > this.boundaries.secondaryN.v && finalPosition[1] < this.initialPosition[1])
} else if (finalPosition[1] > this.boundaries.secondaryP.v) {
++this.secondaryOrder.currentPosition
secondaryBoundaryCrossed(
this.boundaries.secondaryP.i,
this.initialPosition[1] < this.boundaries.secondaryP.v && this.initialPosition[1] < finalPosition[1])
}
this.finalPosition = finalPosition
}
}

View File

@@ -0,0 +1,153 @@
export default class OrderedIndexArray {
/**
* @param {(arrayElement: number) => number} compareFunction A function that, given acouple of elements of the array telles what order are they on.
* @param {(number|array)} value Initial length or array to copy from
*/
constructor(comparisonValueSupplier = (a) => a, value = null) {
this.array = new Uint32Array(value)
this.comparisonValueSupplier = comparisonValueSupplier
this.length = 0
this.currentPosition = 0
}
/**
*
* @param {number} index The index of the value to return
* @returns The element of the array
*/
get(index) {
if (index >= 0 && index < this.length) {
return this.array[index]
}
return null
}
/**
* Returns the array used by this object.
* @returns The array.
*/
getArray() {
return this.array
}
/**
* Get the position that the value supplied should (or does) occupy in the aray.
* @param {number} value The value to look for (it doesn't have to be part of the array).
* @returns The position index.
*/
getPosition(value) {
let l = 0
let r = this.length
while (l < r) {
let m = Math.floor((l + r) / 2)
if (this.comparisonValueSupplier(this.array[m]) < value) {
l = m + 1
} else {
r = m
}
}
return l
}
reserve(length) {
if (this.array.length < length) {
let newArray = new Uint32Array(length)
newArray.set(this.array)
this.array = newArray
}
}
/**
* Inserts the element in the array.
* @param element {number} The value to insert into the array.
* @returns {number} The position into occupied by value into the array.
*/
insert(element, comparisonValue = null) {
let position = this.getPosition(this.comparisonValueSupplier(element))
if (
position < this.currentPosition
|| comparisonValue != null && position == this.currentPosition && this.comparisonValueSupplier(element) < comparisonValue) {
++this.currentPosition
}
/*
let newArray = new Uint32Array(this.array.length + 1)
newArray.set(this.array.subarray(0, position), 0)
newArray[position] = element
newArray.set(this.array.subarray(position), position + 1)
this.array = newArray
*/
this.shiftRight(position)
this.array[position] = element
++this.length
return position
}
/**
* Removes the element from the array.
* @param {number} value The value of the element to be remove.
*/
remove(element) {
let position = this.getPosition(this.comparisonValueSupplier(element))
if (this.array[position] == element) {
this.removeAt(position)
}
}
/**
* Removes the element into the specified position from the array.
* @param {number} position The index of the element to be remove.
*/
removeAt(position) {
if (position < this.currentPosition) {
--this.currentPosition
}
/*
let newArray = new Uint32Array(this.array.length - 1)
newArray.set(this.array.subarray(0, position), 0)
newArray.set(this.array.subarray(position + 1), position)
this.array = newArray
*/
this.shiftLeft(position)
--this.length
return position
}
getNext() {
if (this.currentPosition >= 0 && this.currentPosition < this.length) {
return this.get(this.currentPosition)
}
return null
}
getNextValue() {
if (this.currentPosition >= 0 && this.currentPosition < this.length) {
return this.comparisonValueSupplier(this.get(this.currentPosition))
} else {
return Number.MAX_SAFE_INTEGER
}
}
getPrev() {
if (this.currentPosition > 0) {
return this.get(this.currentPosition - 1)
}
return null
}
getPrevValue() {
if (this.currentPosition > 0) {
return this.comparisonValueSupplier(this.get(this.currentPosition - 1))
} else {
return Number.MIN_SAFE_INTEGER
}
}
shiftLeft(leftLimit, steps = 1) {
this.array.set(this.array.subarray(leftLimit + steps), leftLimit)
}
shiftRight(leftLimit, steps = 1) {
this.array.set(this.array.subarray(leftLimit, -steps), leftLimit + steps)
}
}

View File

@@ -0,0 +1,44 @@
export default class SimpleSelectionModel {
/**
* @typedef {{
* primaryInf: number,
* primarySup: number,
* secondaryInf: number,
* secondarySup: number
* }} BoundariesInfo
* @typedef {numeric} Rectangle
* @param {number[]} initialPosition Coordinates of the starting point of selection [primaryAxisValue, secondaryAxisValue].
* @param {Rectangle[]} rectangles Rectangles that can be selected by this object.
* @param {(rect: Rectangle) => BoundariesInfo} boundariesFunc A function that, given a rectangle, it provides the boundaries of such rectangle.
* @param {(rect: Rectangle, selected: bool) => void} selectToggleFunction A function that selects or deselects individual rectangles.
*/
constructor(initialPosition, rectangles, boundariesFunc, selectToggleFunction) {
this.initialPosition = initialPosition
this.finalPosition = initialPosition
this.boundariesFunc = boundariesFunc
this.selectToggleFunction = selectToggleFunction
this.rectangles = rectangles
}
selectTo(finalPosition) {
let primaryInf = Math.min(finalPosition[0], this.initialPosition[0])
let primarySup = Math.max(finalPosition[0], this.initialPosition[0])
let secondaryInf = Math.min(finalPosition[1], this.initialPosition[1])
let secondarySup = Math.max(finalPosition[1], this.initialPosition[1])
this.finalPosition = finalPosition
this.rectangles.forEach(rect => {
let boundaries = this.boundariesFunc(rect)
if (
Math.max(boundaries.primaryInf, primaryInf) < Math.min(boundaries.primarySup, primarySup)
&& Math.max(boundaries.secondaryInf, secondaryInf) < Math.min(boundaries.secondarySup, secondarySup)
) {
this.selectToggleFunction(rect, true)
} else {
this.selectToggleFunction(rect, false)
}
})
}
}