mirror of
https://github.com/barsdeveloper/ueblueprint.git
synced 2026-02-03 23:55:04 +08:00
Large mouse events refactoring and cleanup
This commit is contained in:
@@ -1,11 +1,12 @@
|
||||
import Utility from "./Utility.js"
|
||||
import UEBlueprintDragScroll from "./UEBlueprintDragScroll.js"
|
||||
import UEBlueprintSelect from "./UEBlueprintSelect.js"
|
||||
import FastSelectionModel from "./FastSelectionModel.js"
|
||||
import SimpleSelectionModel from "./SimpleSelectionModel.js"
|
||||
import Utility from "./Utility"
|
||||
import UDragScroll from "./input/UDragScroll"
|
||||
import USelect from "./input/USelect"
|
||||
import UZoom from "./input/UZoom"
|
||||
import FastSelectionModel from "./selection/FastSelectionModel"
|
||||
import SimpleSelectionModel from "./selection/SimpleSelectionModel"
|
||||
|
||||
/**
|
||||
* @typedef {import("./UEBlueprintObject.js").default} UEBlueprintObject
|
||||
* @typedef {import("./UEBlueprintObject").default} UEBlueprintObject
|
||||
*/
|
||||
export default class UEBlueprint extends HTMLElement {
|
||||
|
||||
@@ -37,7 +38,7 @@ export default class UEBlueprint extends HTMLElement {
|
||||
}
|
||||
|
||||
static getElement(template) {
|
||||
let div = document.createElement('div');
|
||||
let div = document.createElement('div')
|
||||
div.innerHTML = template
|
||||
return div.firstElementChild
|
||||
}
|
||||
@@ -61,8 +62,6 @@ export default class UEBlueprint extends HTMLElement {
|
||||
this.selectorElement = null
|
||||
/** @type {HTMLElement} */
|
||||
this.nodesContainerElement = null
|
||||
/** @type {IntersectionObserver} */
|
||||
this.selectorObserver = null
|
||||
this.dragObject = null
|
||||
this.selectObject = null
|
||||
/** @type {Array<number>} */
|
||||
@@ -107,28 +106,19 @@ export default class UEBlueprint extends HTMLElement {
|
||||
this.nodesContainerElement = this.querySelector('[data-nodes]')
|
||||
this.insertChildren()
|
||||
|
||||
this.selectorObserver = new IntersectionObserver(
|
||||
(entries, observer) => {
|
||||
entries.map(entry => {
|
||||
/** @type {import("./UEBlueprintObject.js").default;}" */
|
||||
let target = entry.target
|
||||
target.setSelected(entry.isIntersecting)
|
||||
})
|
||||
}, {
|
||||
threshold: [0.01],
|
||||
root: this.selectorElement
|
||||
})
|
||||
this.nodes.forEach(element => this.selectorObserver.observe(element))
|
||||
|
||||
this.dragObject = new UEBlueprintDragScroll(this, {
|
||||
this.dragObject = new UDragScroll(this.getGridDOMElement(), this, {
|
||||
'clickButton': 2,
|
||||
'stepSize': 1,
|
||||
'exitDragAnyButton': false
|
||||
})
|
||||
|
||||
this.selectObject = new UEBlueprintSelect(this, {
|
||||
this.zoomObject = new UZoom(this.getGridDOMElement(), this, {
|
||||
looseTarget: true
|
||||
})
|
||||
|
||||
this.selectObject = new USelect(this.getGridDOMElement(), this, {
|
||||
'clickButton': 0,
|
||||
'exitSelectAnyButton': true
|
||||
'exitAnyButton': true
|
||||
})
|
||||
}
|
||||
|
||||
@@ -356,6 +346,13 @@ export default class UEBlueprint extends HTMLElement {
|
||||
this.selectionModel.selectTo(finalPosition)
|
||||
}
|
||||
|
||||
/**
|
||||
* Unselect all nodes
|
||||
*/
|
||||
unselectAll() {
|
||||
this.nodes.forEach(node => this.nodeSelectToggleFunction(node, false))
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {...UEBlueprintObject} blueprintNodes
|
||||
|
||||
@@ -1,66 +0,0 @@
|
||||
export default class UEBlueprintDrag {
|
||||
constructor(blueprintNode, options) {
|
||||
this.blueprintNode = blueprintNode;
|
||||
this.mousePosition = [0, 0];
|
||||
this.stepSize = options?.stepSize
|
||||
this.clickButton = options?.clickButton ?? 0
|
||||
this.exitDragAnyButton = options?.exitDragAnyButton ?? true
|
||||
let self = this;
|
||||
this.mouseDownHandler = function (e) {
|
||||
switch (e.button) {
|
||||
case self.clickButton:
|
||||
self.clicked(e.clientX, e.clientY)
|
||||
break;
|
||||
default:
|
||||
if (!self.exitDragAnyButton) {
|
||||
self.mouseUpHandler(e)
|
||||
}
|
||||
break;
|
||||
}
|
||||
};
|
||||
this.mouseMoveHandler = function (e) {
|
||||
let mousePosition = self.snapToGrid(e.clientX, e.clientY)
|
||||
const d = [mousePosition[0] - self.mousePosition[0], mousePosition[1] - self.mousePosition[1]]
|
||||
|
||||
if (d[0] == 0 && d[1] == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
self.blueprintNode.addLocation(d)
|
||||
|
||||
// Reassign the position of mouse
|
||||
self.mousePosition = mousePosition
|
||||
};
|
||||
this.mouseUpHandler = function (e) {
|
||||
if (!self.exitDragAnyButton || e.button == self.clickButton) {
|
||||
// Remove the handlers of `mousemove` and `mouseup`
|
||||
document.removeEventListener('mousemove', self.mouseMoveHandler)
|
||||
document.removeEventListener('mouseup', self.mouseUpHandler)
|
||||
}
|
||||
};
|
||||
this.blueprintNode.addEventListener('mousedown', this.mouseDownHandler)
|
||||
this.blueprintNode.addEventListener('contextmenu', e => e.preventDefault())
|
||||
}
|
||||
|
||||
unlistenDOMElement() {
|
||||
this.blueprintNode.removeEventListener('mousedown', this.mouseDownHandler)
|
||||
}
|
||||
|
||||
snapToGrid(posX, posY) {
|
||||
return [
|
||||
this.stepSize * Math.round(posX / this.stepSize),
|
||||
this.stepSize * Math.round(posY / this.stepSize)
|
||||
]
|
||||
}
|
||||
|
||||
clicked(x, y) {
|
||||
if (!this.stepSize) {
|
||||
this.stepSize = parseInt(getComputedStyle(this.blueprintNode).getPropertyValue('--ueb-grid-snap'))
|
||||
}
|
||||
// Get the current mouse position
|
||||
this.mousePosition = this.snapToGrid(x, y)
|
||||
// Attach the listeners to `document`
|
||||
document.addEventListener('mousemove', this.mouseMoveHandler)
|
||||
document.addEventListener('mouseup', this.mouseUpHandler)
|
||||
}
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
import UEBlueprintDrag from "./UEBlueprintDrag.js"
|
||||
|
||||
export default class UEBlueprintDragScroll extends UEBlueprintDrag {
|
||||
constructor(scrolledEntity, options) {
|
||||
super(scrolledEntity, options)
|
||||
this.minZoom = options?.minZoom ?? -12
|
||||
let self = this
|
||||
|
||||
this.mouseMoveHandler = function (e) {
|
||||
let mousePosition = self.snapToGrid(e.clientX, e.clientY)
|
||||
|
||||
// How far the mouse has been moved
|
||||
const dx = mousePosition[0] - self.mousePosition[0]
|
||||
const dy = mousePosition[1] - self.mousePosition[1]
|
||||
|
||||
self.blueprintNode.scrollDelta([-dx, -dy])
|
||||
|
||||
// Reassign the position of mouse
|
||||
self.mousePosition = mousePosition
|
||||
}
|
||||
|
||||
this.mouseWheelHandler = function (e) {
|
||||
e.preventDefault()
|
||||
let zoomLevel = self.blueprintNode.getZoom()
|
||||
zoomLevel -= Math.sign(e.deltaY)
|
||||
let scaleCorrection = 1 / self.blueprintNode.getScale()
|
||||
const targetOffset = e.target.getBoundingClientRect()
|
||||
const currentTargetOffset = e.currentTarget.getBoundingClientRect()
|
||||
let offset = [
|
||||
e.offsetX + targetOffset.x * scaleCorrection - currentTargetOffset.x * scaleCorrection,
|
||||
e.offsetY + targetOffset.y * scaleCorrection - currentTargetOffset.y * scaleCorrection
|
||||
]
|
||||
self.blueprintNode.setZoom(zoomLevel, offset)
|
||||
}
|
||||
|
||||
this.blueprintNode.getGridDOMElement().addEventListener('wheel', this.mouseWheelHandler, false)
|
||||
this.blueprintNode.getGridDOMElement().parentElement.addEventListener('wheel', e => e.preventDefault())
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import UEBlueprintDrag from "./UEBlueprintDrag.js"
|
||||
import UDrag from "./input/UDrag"
|
||||
|
||||
export default class UEBlueprintDraggableObject extends HTMLElement {
|
||||
|
||||
@@ -9,7 +9,9 @@ export default class UEBlueprintDraggableObject extends HTMLElement {
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
this.dragObject = new UEBlueprintDrag(this)
|
||||
this.dragObject = new UDrag(this, null, {
|
||||
looseTarget: true
|
||||
})
|
||||
}
|
||||
|
||||
disconnectedCallback() {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import UEBlueprintDraggableObject from "./UEBlueprintDraggableObject.js"
|
||||
import UEBlueprintDraggableObject from "./UEBlueprintDraggableObject"
|
||||
|
||||
export default class UEBlueprintObject extends UEBlueprintDraggableObject {
|
||||
static classInputs = [/*
|
||||
@@ -86,7 +86,7 @@ export default class UEBlueprintObject extends UEBlueprintDraggableObject {
|
||||
this.style.setProperty('--ueb-position-x', this.location[0])
|
||||
this.style.setProperty('--ueb-position-y', this.location[1])
|
||||
|
||||
let aDiv = document.createElement('div');
|
||||
let aDiv = document.createElement('div')
|
||||
aDiv.innerHTML = this.render()
|
||||
this.appendChild(aDiv.firstElementChild)
|
||||
}
|
||||
|
||||
@@ -1,60 +0,0 @@
|
||||
export default class UEBlueprintSelect {
|
||||
constructor(blueprintNode, options) {
|
||||
/** @type {import("./UEBlueprint.js").default;}" */
|
||||
this.blueprintNode = blueprintNode;
|
||||
this.mousePosition = [0, 0];
|
||||
this.clickButton = options?.clickButton ?? 0
|
||||
this.exitSelectAnyButton = options?.exitSelectAnyButton ?? true
|
||||
let self = this
|
||||
|
||||
this.mouseDownHandler = function (e) {
|
||||
switch (e.button) {
|
||||
case self.clickButton:
|
||||
self.clicked([e.offsetX, e.offsetY])
|
||||
break
|
||||
default:
|
||||
if (!self.exitSelectAnyButton) {
|
||||
self.mouseUpHandler(e)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
this.mouseMoveHandler = function (e) {
|
||||
e.preventDefault()
|
||||
let scaleCorrection = 1 / self.blueprintNode.getScale()
|
||||
const targetOffset = e.target.getBoundingClientRect()
|
||||
const currentTargetOffset = e.currentTarget.getBoundingClientRect()
|
||||
let offset = [
|
||||
e.offsetX + targetOffset.x * scaleCorrection - currentTargetOffset.x * scaleCorrection,
|
||||
e.offsetY + targetOffset.y * scaleCorrection - currentTargetOffset.y * scaleCorrection
|
||||
]
|
||||
self.blueprintNode.doSelecting(offset)
|
||||
}
|
||||
|
||||
this.mouseUpHandler = function (e) {
|
||||
if (!self.exitSelectAnyButton || e.button == self.clickButton) {
|
||||
// Remove the handlers of `mousemove` and `mouseup`
|
||||
self.blueprintNode.getGridDOMElement().removeEventListener('mousemove', self.mouseMoveHandler)
|
||||
self.blueprintNode.finishSelecting()
|
||||
document.removeEventListener('mouseup', self.mouseUpHandler)
|
||||
}
|
||||
}
|
||||
|
||||
let gridElement = this.blueprintNode.getGridDOMElement()
|
||||
gridElement.addEventListener('mousedown', this.mouseDownHandler)
|
||||
gridElement.addEventListener('contextmenu', e => e.preventDefault())
|
||||
}
|
||||
|
||||
unlistenDOMElement() {
|
||||
this.blueprintNode.removeEventListener('mousedown', this.mouseDownHandler)
|
||||
}
|
||||
|
||||
clicked(position) {
|
||||
// Attach the listeners to `document`
|
||||
this.blueprintNode.getGridDOMElement().addEventListener('mousemove', this.mouseMoveHandler)
|
||||
document.addEventListener('mouseup', this.mouseUpHandler)
|
||||
// Start selecting
|
||||
this.blueprintNode.startSelecting(position)
|
||||
}
|
||||
}
|
||||
38
js/input/UDrag.js
Normal file
38
js/input/UDrag.js
Normal file
@@ -0,0 +1,38 @@
|
||||
import UMouseClickDrag from "./UMouseClickDrag"
|
||||
|
||||
export default class UDrag extends UMouseClickDrag {
|
||||
constructor(target, blueprint, options) {
|
||||
super(target, blueprint, options)
|
||||
this.stepSize = options?.stepSize
|
||||
this.mousePosition = [0, 0]
|
||||
}
|
||||
|
||||
snapToGrid(posX, posY) {
|
||||
return [
|
||||
this.stepSize * Math.round(posX / this.stepSize),
|
||||
this.stepSize * Math.round(posY / this.stepSize)
|
||||
]
|
||||
}
|
||||
|
||||
startDrag(e) {
|
||||
if (!this.stepSize) {
|
||||
this.stepSize = parseInt(getComputedStyle(this.target).getPropertyValue('--ueb-grid-snap'))
|
||||
}
|
||||
// Get the current mouse position
|
||||
this.mousePosition = this.snapToGrid(e.clientX, e.clientY)
|
||||
}
|
||||
|
||||
dragTo(e) {
|
||||
let mousePosition = this.snapToGrid(e.clientX, e.clientY)
|
||||
const d = [mousePosition[0] - this.mousePosition[0], mousePosition[1] - this.mousePosition[1]]
|
||||
|
||||
if (d[0] == 0 && d[1] == 0) {
|
||||
return
|
||||
}
|
||||
|
||||
this.target.addLocation(d)
|
||||
|
||||
// Reassign the position of mouse
|
||||
this.mousePosition = mousePosition
|
||||
}
|
||||
}
|
||||
18
js/input/UDragScroll.js
Normal file
18
js/input/UDragScroll.js
Normal file
@@ -0,0 +1,18 @@
|
||||
import UDrag from "./UDrag"
|
||||
|
||||
export default class UDragScroll extends UDrag {
|
||||
|
||||
dragTo(e) {
|
||||
const mousePosition = this.snapToGrid(e.clientX, e.clientY)
|
||||
|
||||
// How far the mouse has been moved
|
||||
const dx = mousePosition[0] - this.mousePosition[0]
|
||||
const dy = mousePosition[1] - this.mousePosition[1]
|
||||
|
||||
this.blueprint.scrollDelta([-dx, -dy])
|
||||
|
||||
// Reassign the position of mouse
|
||||
this.mousePosition = mousePosition
|
||||
}
|
||||
|
||||
}
|
||||
94
js/input/UMouseClickDrag.js
Normal file
94
js/input/UMouseClickDrag.js
Normal file
@@ -0,0 +1,94 @@
|
||||
/**
|
||||
* This class manages the ui gesture of mouse click and drag. Tha actual operations are implemented by the subclasses.
|
||||
*/
|
||||
export default class UMouseClickDrag {
|
||||
constructor(target, blueprint, options) {
|
||||
this.target = target
|
||||
/** @type {import("../UEBlueprint").default}" */
|
||||
this.blueprint = blueprint
|
||||
this.clickButton = options?.clickButton ?? 0
|
||||
this.exitAnyButton = options?.exitAnyButton ?? true
|
||||
this.looseTarget = options?.looseTarget ?? false
|
||||
this.started = false
|
||||
this.clickedPosition = [0, 0]
|
||||
let movementSpace = this.blueprint?.getGridDOMElement() ?? document
|
||||
let self = this
|
||||
|
||||
this.mouseDownHandler = function (e) {
|
||||
switch (e.button) {
|
||||
case self.clickButton:
|
||||
// Either doesn't matter or consider the click only when clicking on the parent, not descandants
|
||||
if (self.looseTarget || e.target == e.currentTarget) {
|
||||
self.started = false
|
||||
// Attach the listeners
|
||||
movementSpace.addEventListener('mousemove', self.mouseStartedMovingHandler)
|
||||
document.addEventListener('mouseup', self.mouseUpHandler)
|
||||
self.clickedPosition = [e.offsetX, e.offsetY]
|
||||
self.clicked(e)
|
||||
}
|
||||
break
|
||||
default:
|
||||
if (!self.exitAnyButton) {
|
||||
self.mouseUpHandler(e)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
this.mouseStartedMovingHandler = function (e) {
|
||||
e.preventDefault()
|
||||
|
||||
// Delegate from now on to self.mouseMoveHandler
|
||||
movementSpace.removeEventListener('mousemove', self.mouseStartedMovingHandler)
|
||||
movementSpace.addEventListener('mousemove', self.mouseMoveHandler)
|
||||
|
||||
// Do actual actions
|
||||
self.startDrag(e)
|
||||
self.started = true
|
||||
}
|
||||
|
||||
this.mouseMoveHandler = function (e) {
|
||||
e.preventDefault()
|
||||
self.dragTo(e)
|
||||
}
|
||||
|
||||
this.mouseUpHandler = function (e) {
|
||||
if (!self.exitAnyButton || e.button == self.clickButton) {
|
||||
// Remove the handlers of `mousemove` and `mouseup`
|
||||
movementSpace.removeEventListener('mousemove', self.mouseStartedMovingHandler)
|
||||
movementSpace.removeEventListener('mousemove', self.mouseMoveHandler)
|
||||
document.removeEventListener('mouseup', self.mouseUpHandler)
|
||||
self.endDrag(e)
|
||||
}
|
||||
}
|
||||
|
||||
this.target.addEventListener('mousedown', this.mouseDownHandler)
|
||||
if (this.clickButton == 2) {
|
||||
this.target.addEventListener('contextmenu', this.preventDefault)
|
||||
}
|
||||
}
|
||||
|
||||
preventDefault(e) {
|
||||
e.preventDefault()
|
||||
}
|
||||
|
||||
unlistenDOMElement() {
|
||||
this.target.removeEventListener('mousedown', this.mouseDownHandler)
|
||||
if (this.clickButton == 2) {
|
||||
this.target.removeEventListener('contextmenu', this.preventDefault)
|
||||
}
|
||||
}
|
||||
|
||||
/* Subclasses will override the following methods */
|
||||
clicked(e) {
|
||||
}
|
||||
|
||||
startDrag(e) {
|
||||
}
|
||||
|
||||
dragTo(e) {
|
||||
}
|
||||
|
||||
endDrag(e) {
|
||||
}
|
||||
}
|
||||
47
js/input/UMouseWheel.js
Normal file
47
js/input/UMouseWheel.js
Normal file
@@ -0,0 +1,47 @@
|
||||
export default class UMouseWheel {
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {HTMLElement} target
|
||||
* @param {import("../UEBlueprint").default} blueprint
|
||||
* @param {Object} options
|
||||
*/
|
||||
constructor(target, blueprint, options) {
|
||||
this.target = target
|
||||
this.blueprint = blueprint
|
||||
this.looseTarget = options?.looseTarget ?? true
|
||||
this.movementSpace = this.blueprint?.getGridDOMElement() ?? document
|
||||
let self = this
|
||||
|
||||
this.mouseWheelHandler = function (e) {
|
||||
e.preventDefault()
|
||||
if (!self.looseTarget && e.target != e.currentTarget) {
|
||||
return
|
||||
}
|
||||
let scaleCorrection = 1 / self.blueprint.getScale()
|
||||
let offset = [e.offsetX, e.offsetY]
|
||||
if (self.looseTarget) {
|
||||
/*
|
||||
* Compensating for having used the mouse wheel over a descendant of the target (the element listened for the 'wheel' event).
|
||||
* We are interested to get the location relative to the listened target, not the exact target that caused the event.
|
||||
*/
|
||||
const targetOffset = e.target.getBoundingClientRect()
|
||||
const currentTargetOffset = e.currentTarget.getBoundingClientRect()
|
||||
offset = [
|
||||
offset[0] + targetOffset.x * scaleCorrection - currentTargetOffset.x * scaleCorrection,
|
||||
offset[1] + targetOffset.y * scaleCorrection - currentTargetOffset.y * scaleCorrection
|
||||
]
|
||||
}
|
||||
self.wheel(Math.sign(e.deltaY), offset)
|
||||
}
|
||||
|
||||
this.movementSpace.addEventListener('wheel', this.mouseWheelHandler, false)
|
||||
// Prevent movement space from being scrolled
|
||||
this.movementSpace.parentElement?.addEventListener('wheel', e => e.preventDefault())
|
||||
}
|
||||
|
||||
/* Subclasses will override the following method */
|
||||
wheel(location, variation) {
|
||||
|
||||
}
|
||||
}
|
||||
38
js/input/USelect.js
Normal file
38
js/input/USelect.js
Normal file
@@ -0,0 +1,38 @@
|
||||
import UMouseClickDrag from "./UMouseClickDrag"
|
||||
|
||||
export default class USelect extends UMouseClickDrag {
|
||||
|
||||
constructor(target, blueprint, options) {
|
||||
super(target, blueprint, options)
|
||||
|
||||
this.blueprint = blueprint // blueprint is needed
|
||||
this.stepSize = options?.stepSize
|
||||
this.mousePosition = [0, 0]
|
||||
}
|
||||
|
||||
clicked(e) {
|
||||
}
|
||||
|
||||
startDrag(e) {
|
||||
this.blueprint.startSelecting(this.clickedPosition)
|
||||
}
|
||||
|
||||
dragTo(e) {
|
||||
let scaleCorrection = 1 / this.blueprint.getScale()
|
||||
const targetOffset = e.target.getBoundingClientRect()
|
||||
const currentTargetOffset = e.currentTarget.getBoundingClientRect()
|
||||
let offset = [
|
||||
e.offsetX + targetOffset.x * scaleCorrection - currentTargetOffset.x * scaleCorrection,
|
||||
e.offsetY + targetOffset.y * scaleCorrection - currentTargetOffset.y * scaleCorrection
|
||||
]
|
||||
this.blueprint.doSelecting(offset)
|
||||
}
|
||||
|
||||
endDrag(e) {
|
||||
if (this.started) {
|
||||
this.blueprint.finishSelecting()
|
||||
} else {
|
||||
this.blueprint.unselectAll()
|
||||
}
|
||||
}
|
||||
}
|
||||
9
js/input/UZoom.js
Normal file
9
js/input/UZoom.js
Normal file
@@ -0,0 +1,9 @@
|
||||
import UMouseWheel from "./UMouseWheel";
|
||||
|
||||
export default class UZoom extends UMouseWheel {
|
||||
wheel(variation, location) {
|
||||
let zoomLevel = this.blueprint.getZoom()
|
||||
zoomLevel -= variation
|
||||
this.blueprint.setZoom(zoomLevel, location)
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import OrderedIndexArray from "./OrderedIndexArray.js"
|
||||
import OrderedIndexArray from "./OrderedIndexArray"
|
||||
|
||||
export default class FastSelectionModel {
|
||||
|
||||
@@ -20,16 +20,16 @@ export default class FastSelectionModel {
|
||||
* @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.
|
||||
* @param {(rect: Rectangle, selected: bool) => void} selectFunc A function that selects or deselects individual rectangles.
|
||||
*/
|
||||
constructor(initialPosition, rectangles, boundariesFunc, selectToggleFunction) {
|
||||
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.selectToggleFunction = selectToggleFunction
|
||||
this.selectFunc = selectFunc
|
||||
this.rectangles = rectangles
|
||||
this.primaryOrder.reserve(this.rectangles.length)
|
||||
this.secondaryOrder.reserve(this.rectangles.length)
|
||||
@@ -42,7 +42,7 @@ export default class FastSelectionModel {
|
||||
onSecondaryAxis: false
|
||||
}
|
||||
this.metadata[index] = rectangleMetadata
|
||||
selectToggleFunction(rect, false) // Initially deselected (Eventually)
|
||||
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
|
||||
@@ -65,7 +65,7 @@ export default class FastSelectionModel {
|
||||
if (rectangleBoundaries.secondarySup < this.initialPosition[1] || this.initialPosition[1] < rectangleBoundaries.secondaryInf) {
|
||||
this.secondaryOrder.insert(index)
|
||||
} else {
|
||||
selectToggleFunction(rect, true)
|
||||
selectFunc(rect, true)
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -78,22 +78,22 @@ export default class FastSelectionModel {
|
||||
this.boundaries = {
|
||||
// Primary axis negative expanding
|
||||
primaryN: {
|
||||
'value': this.primaryOrder.getPrevValue(),
|
||||
'index': this.primaryOrder.getPrev()
|
||||
v: this.primaryOrder.getPrevValue(),
|
||||
i: this.primaryOrder.getPrev()
|
||||
},
|
||||
primaryP: {
|
||||
'value': this.primaryOrder.getNextValue(),
|
||||
'index': this.primaryOrder.getNext()
|
||||
v: this.primaryOrder.getNextValue(),
|
||||
i: this.primaryOrder.getNext()
|
||||
},
|
||||
// Secondary axis negative expanding
|
||||
secondaryN: {
|
||||
'value': this.secondaryOrder.getPrevValue(),
|
||||
'index': this.secondaryOrder.getPrev()
|
||||
v: this.secondaryOrder.getPrevValue(),
|
||||
i: this.secondaryOrder.getPrev()
|
||||
},
|
||||
// Secondary axis positive expanding
|
||||
secondaryP: {
|
||||
'value': this.secondaryOrder.getNextValue(),
|
||||
'index': this.secondaryOrder.getNext()
|
||||
v: this.secondaryOrder.getNextValue(),
|
||||
i: this.secondaryOrder.getNext()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -105,7 +105,7 @@ export default class FastSelectionModel {
|
||||
]
|
||||
const primaryBoundaryCrossed = (index, added) => {
|
||||
if (this.metadata[index].onSecondaryAxis) {
|
||||
this.selectToggleFunction(this.rectangles[index], added)
|
||||
this.selectFunc(this.rectangles[index], added)
|
||||
} else {
|
||||
if (added) {
|
||||
this.secondaryOrder.insert(index, finalPosition[1])
|
||||
@@ -117,10 +117,10 @@ export default class FastSelectionModel {
|
||||
&& Math.sign(secondaryBoundary - this.initialPosition[1]) == direction[1]
|
||||
) {
|
||||
// Secondary axis is already satisfied then
|
||||
this.selectToggleFunction(this.rectangles[index], true)
|
||||
this.selectFunc(this.rectangles[index], true)
|
||||
}
|
||||
} else {
|
||||
this.selectToggleFunction(this.rectangles[index], false)
|
||||
this.selectFunc(this.rectangles[index], false)
|
||||
this.secondaryOrder.remove(index)
|
||||
}
|
||||
}
|
||||
@@ -128,35 +128,35 @@ export default class FastSelectionModel {
|
||||
this.selectTo(finalPosition)
|
||||
}
|
||||
|
||||
if (finalPosition[0] < this.boundaries.primaryN.value) {
|
||||
if (finalPosition[0] < this.boundaries.primaryN.v) {
|
||||
--this.primaryOrder.currentPosition
|
||||
primaryBoundaryCrossed(
|
||||
this.boundaries.primaryN.index,
|
||||
this.initialPosition[0] > this.boundaries.primaryN.value && finalPosition[0] < this.initialPosition[0])
|
||||
} else if (finalPosition[0] > this.boundaries.primaryP.value) {
|
||||
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.index,
|
||||
this.initialPosition[0] < this.boundaries.primaryP.value && this.initialPosition[0] < finalPosition[0])
|
||||
this.boundaries.primaryP.i,
|
||||
this.initialPosition[0] < this.boundaries.primaryP.v && this.initialPosition[0] < finalPosition[0])
|
||||
}
|
||||
|
||||
|
||||
const secondaryBoundaryCrossed = (index, added) => {
|
||||
this.selectToggleFunction(this.rectangles[index], added)
|
||||
this.selectFunc(this.rectangles[index], added)
|
||||
this.computeBoundaries(finalPosition)
|
||||
this.selectTo(finalPosition)
|
||||
}
|
||||
|
||||
if (finalPosition[1] < this.boundaries.secondaryN.value) {
|
||||
if (finalPosition[1] < this.boundaries.secondaryN.v) {
|
||||
--this.secondaryOrder.currentPosition
|
||||
secondaryBoundaryCrossed(
|
||||
this.boundaries.secondaryN.index,
|
||||
this.initialPosition[1] > this.boundaries.secondaryN.value && finalPosition[1] < this.initialPosition[1]);
|
||||
} else if (finalPosition[1] > this.boundaries.secondaryP.value) {
|
||||
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.index,
|
||||
this.initialPosition[1] < this.boundaries.secondaryP.value && this.initialPosition[1] < finalPosition[1]);
|
||||
this.boundaries.secondaryP.i,
|
||||
this.initialPosition[1] < this.boundaries.secondaryP.v && this.initialPosition[1] < finalPosition[1])
|
||||
}
|
||||
this.finalPosition = finalPosition
|
||||
}
|
||||
@@ -64,13 +64,6 @@ export default class OrderedIndexArray {
|
||||
* @returns {number} The position into occupied by value into the array.
|
||||
*/
|
||||
insert(element, comparisonValue = null) {
|
||||
let i = 0;
|
||||
for (i = 0; i < this.length; ++i) {
|
||||
if (element == this.array[i]) {
|
||||
console.log("error");
|
||||
break;
|
||||
}
|
||||
}
|
||||
let position = this.getPosition(this.comparisonValueSupplier(element))
|
||||
if (
|
||||
position < this.currentPosition
|
||||
@@ -87,9 +80,6 @@ export default class OrderedIndexArray {
|
||||
this.shiftRight(position)
|
||||
this.array[position] = element
|
||||
++this.length
|
||||
if (this.length > this.array.length) {
|
||||
console.log("error2")
|
||||
}
|
||||
return position
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
419
ueblueprint.js
419
ueblueprint.js
@@ -4,52 +4,106 @@ class Utility {
|
||||
}
|
||||
}
|
||||
|
||||
class UEBlueprintDrag {
|
||||
constructor(blueprintNode, options) {
|
||||
this.blueprintNode = blueprintNode;
|
||||
this.mousePosition = [0, 0];
|
||||
this.stepSize = options?.stepSize;
|
||||
/**
|
||||
* This class manages the ui gesture of mouse click and drag. Tha actual operations are implemented by the subclasses.
|
||||
*/
|
||||
class UMouseClickDrag {
|
||||
constructor(target, blueprint, options) {
|
||||
this.target = target;
|
||||
/** @type {import("../UEBlueprint").default}" */
|
||||
this.blueprint = blueprint;
|
||||
this.clickButton = options?.clickButton ?? 0;
|
||||
this.exitDragAnyButton = options?.exitDragAnyButton ?? true;
|
||||
this.exitAnyButton = options?.exitAnyButton ?? true;
|
||||
this.looseTarget = options?.looseTarget ?? false;
|
||||
this.started = false;
|
||||
this.clickedPosition = [0, 0];
|
||||
let movementSpace = this.blueprint?.getGridDOMElement() ?? document;
|
||||
let self = this;
|
||||
|
||||
this.mouseDownHandler = function (e) {
|
||||
switch (e.button) {
|
||||
case self.clickButton:
|
||||
self.clicked(e.clientX, e.clientY);
|
||||
break;
|
||||
// Either doesn't matter or consider the click only when clicking on the parent, not descandants
|
||||
if (self.looseTarget || e.target == e.currentTarget) {
|
||||
self.started = false;
|
||||
// Attach the listeners
|
||||
movementSpace.addEventListener('mousemove', self.mouseStartedMovingHandler);
|
||||
document.addEventListener('mouseup', self.mouseUpHandler);
|
||||
self.clickedPosition = [e.offsetX, e.offsetY];
|
||||
self.clicked(e);
|
||||
}
|
||||
break
|
||||
default:
|
||||
if (!self.exitDragAnyButton) {
|
||||
if (!self.exitAnyButton) {
|
||||
self.mouseUpHandler(e);
|
||||
}
|
||||
break;
|
||||
break
|
||||
}
|
||||
};
|
||||
|
||||
this.mouseStartedMovingHandler = function (e) {
|
||||
e.preventDefault();
|
||||
|
||||
// Delegate from now on to self.mouseMoveHandler
|
||||
movementSpace.removeEventListener('mousemove', self.mouseStartedMovingHandler);
|
||||
movementSpace.addEventListener('mousemove', self.mouseMoveHandler);
|
||||
|
||||
// Do actual actions
|
||||
self.startDrag(e);
|
||||
self.started = true;
|
||||
};
|
||||
|
||||
this.mouseMoveHandler = function (e) {
|
||||
let mousePosition = self.snapToGrid(e.clientX, e.clientY);
|
||||
const d = [mousePosition[0] - self.mousePosition[0], mousePosition[1] - self.mousePosition[1]];
|
||||
|
||||
if (d[0] == 0 && d[1] == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
self.blueprintNode.addLocation(d);
|
||||
|
||||
// Reassign the position of mouse
|
||||
self.mousePosition = mousePosition;
|
||||
e.preventDefault();
|
||||
self.dragTo(e);
|
||||
};
|
||||
|
||||
this.mouseUpHandler = function (e) {
|
||||
if (!self.exitDragAnyButton || e.button == self.clickButton) {
|
||||
if (!self.exitAnyButton || e.button == self.clickButton) {
|
||||
// Remove the handlers of `mousemove` and `mouseup`
|
||||
document.removeEventListener('mousemove', self.mouseMoveHandler);
|
||||
movementSpace.removeEventListener('mousemove', self.mouseStartedMovingHandler);
|
||||
movementSpace.removeEventListener('mousemove', self.mouseMoveHandler);
|
||||
document.removeEventListener('mouseup', self.mouseUpHandler);
|
||||
self.endDrag(e);
|
||||
}
|
||||
};
|
||||
this.blueprintNode.addEventListener('mousedown', this.mouseDownHandler);
|
||||
this.blueprintNode.addEventListener('contextmenu', e => e.preventDefault());
|
||||
|
||||
this.target.addEventListener('mousedown', this.mouseDownHandler);
|
||||
if (this.clickButton == 2) {
|
||||
this.target.addEventListener('contextmenu', this.preventDefault);
|
||||
}
|
||||
}
|
||||
|
||||
preventDefault(e) {
|
||||
e.preventDefault();
|
||||
}
|
||||
|
||||
unlistenDOMElement() {
|
||||
this.blueprintNode.removeEventListener('mousedown', this.mouseDownHandler);
|
||||
this.target.removeEventListener('mousedown', this.mouseDownHandler);
|
||||
if (this.clickButton == 2) {
|
||||
this.target.removeEventListener('contextmenu', this.preventDefault);
|
||||
}
|
||||
}
|
||||
|
||||
/* Subclasses will override the following methods */
|
||||
clicked(e) {
|
||||
}
|
||||
|
||||
startDrag(e) {
|
||||
}
|
||||
|
||||
dragTo(e) {
|
||||
}
|
||||
|
||||
endDrag(e) {
|
||||
}
|
||||
}
|
||||
|
||||
class UDrag extends UMouseClickDrag {
|
||||
constructor(target, blueprint, options) {
|
||||
super(target, blueprint, options);
|
||||
this.stepSize = options?.stepSize;
|
||||
this.mousePosition = [0, 0];
|
||||
}
|
||||
|
||||
snapToGrid(posX, posY) {
|
||||
@@ -59,115 +113,136 @@ class UEBlueprintDrag {
|
||||
]
|
||||
}
|
||||
|
||||
clicked(x, y) {
|
||||
startDrag(e) {
|
||||
if (!this.stepSize) {
|
||||
this.stepSize = parseInt(getComputedStyle(this.blueprintNode).getPropertyValue('--ueb-grid-snap'));
|
||||
this.stepSize = parseInt(getComputedStyle(this.target).getPropertyValue('--ueb-grid-snap'));
|
||||
}
|
||||
// Get the current mouse position
|
||||
this.mousePosition = this.snapToGrid(x, y);
|
||||
// Attach the listeners to `document`
|
||||
document.addEventListener('mousemove', this.mouseMoveHandler);
|
||||
document.addEventListener('mouseup', this.mouseUpHandler);
|
||||
this.mousePosition = this.snapToGrid(e.clientX, e.clientY);
|
||||
}
|
||||
|
||||
dragTo(e) {
|
||||
let mousePosition = this.snapToGrid(e.clientX, e.clientY);
|
||||
const d = [mousePosition[0] - this.mousePosition[0], mousePosition[1] - this.mousePosition[1]];
|
||||
|
||||
if (d[0] == 0 && d[1] == 0) {
|
||||
return
|
||||
}
|
||||
|
||||
this.target.addLocation(d);
|
||||
|
||||
// Reassign the position of mouse
|
||||
this.mousePosition = mousePosition;
|
||||
}
|
||||
}
|
||||
|
||||
class UEBlueprintDragScroll extends UEBlueprintDrag {
|
||||
constructor(scrolledEntity, options) {
|
||||
super(scrolledEntity, options);
|
||||
this.minZoom = options?.minZoom ?? -12;
|
||||
class UDragScroll extends UDrag {
|
||||
|
||||
dragTo(e) {
|
||||
const mousePosition = this.snapToGrid(e.clientX, e.clientY);
|
||||
|
||||
// How far the mouse has been moved
|
||||
const dx = mousePosition[0] - this.mousePosition[0];
|
||||
const dy = mousePosition[1] - this.mousePosition[1];
|
||||
|
||||
this.blueprint.scrollDelta([-dx, -dy]);
|
||||
|
||||
// Reassign the position of mouse
|
||||
this.mousePosition = mousePosition;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class USelect extends UMouseClickDrag {
|
||||
|
||||
constructor(target, blueprint, options) {
|
||||
super(target, blueprint, options);
|
||||
|
||||
this.blueprint = blueprint; // blueprint is needed
|
||||
this.stepSize = options?.stepSize;
|
||||
this.mousePosition = [0, 0];
|
||||
}
|
||||
|
||||
clicked(e) {
|
||||
}
|
||||
|
||||
startDrag(e) {
|
||||
this.blueprint.startSelecting(this.clickedPosition);
|
||||
}
|
||||
|
||||
dragTo(e) {
|
||||
let scaleCorrection = 1 / this.blueprint.getScale();
|
||||
const targetOffset = e.target.getBoundingClientRect();
|
||||
const currentTargetOffset = e.currentTarget.getBoundingClientRect();
|
||||
let offset = [
|
||||
e.offsetX + targetOffset.x * scaleCorrection - currentTargetOffset.x * scaleCorrection,
|
||||
e.offsetY + targetOffset.y * scaleCorrection - currentTargetOffset.y * scaleCorrection
|
||||
];
|
||||
this.blueprint.doSelecting(offset);
|
||||
}
|
||||
|
||||
endDrag(e) {
|
||||
if (this.started) {
|
||||
this.blueprint.finishSelecting();
|
||||
} else {
|
||||
this.blueprint.unselectAll();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class UMouseWheel {
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {HTMLElement} target
|
||||
* @param {import("../UEBlueprint").default} blueprint
|
||||
* @param {Object} options
|
||||
*/
|
||||
constructor(target, blueprint, options) {
|
||||
this.target = target;
|
||||
this.blueprint = blueprint;
|
||||
this.looseTarget = options?.looseTarget ?? true;
|
||||
this.movementSpace = this.blueprint?.getGridDOMElement() ?? document;
|
||||
let self = this;
|
||||
|
||||
this.mouseMoveHandler = function (e) {
|
||||
let mousePosition = self.snapToGrid(e.clientX, e.clientY);
|
||||
|
||||
// How far the mouse has been moved
|
||||
const dx = mousePosition[0] - self.mousePosition[0];
|
||||
const dy = mousePosition[1] - self.mousePosition[1];
|
||||
|
||||
self.blueprintNode.scrollDelta([-dx, -dy]);
|
||||
|
||||
// Reassign the position of mouse
|
||||
self.mousePosition = mousePosition;
|
||||
};
|
||||
|
||||
this.mouseWheelHandler = function (e) {
|
||||
e.preventDefault();
|
||||
let zoomLevel = self.blueprintNode.getZoom();
|
||||
zoomLevel -= Math.sign(e.deltaY);
|
||||
let scaleCorrection = 1 / self.blueprintNode.getScale();
|
||||
const targetOffset = e.target.getBoundingClientRect();
|
||||
const currentTargetOffset = e.currentTarget.getBoundingClientRect();
|
||||
let offset = [
|
||||
e.offsetX + targetOffset.x * scaleCorrection - currentTargetOffset.x * scaleCorrection,
|
||||
e.offsetY + targetOffset.y * scaleCorrection - currentTargetOffset.y * scaleCorrection
|
||||
];
|
||||
self.blueprintNode.setZoom(zoomLevel, offset);
|
||||
if (!self.looseTarget && e.target != e.currentTarget) {
|
||||
return
|
||||
}
|
||||
let scaleCorrection = 1 / self.blueprint.getScale();
|
||||
let offset = [e.offsetX, e.offsetY];
|
||||
if (self.looseTarget) {
|
||||
/*
|
||||
* Compensating for having used the mouse wheel over a descendant of the target (the element listened for the 'wheel' event).
|
||||
* We are interested to get the location relative to the listened target, not the exact target that caused the event.
|
||||
*/
|
||||
const targetOffset = e.target.getBoundingClientRect();
|
||||
const currentTargetOffset = e.currentTarget.getBoundingClientRect();
|
||||
offset = [
|
||||
offset[0] + targetOffset.x * scaleCorrection - currentTargetOffset.x * scaleCorrection,
|
||||
offset[1] + targetOffset.y * scaleCorrection - currentTargetOffset.y * scaleCorrection
|
||||
];
|
||||
}
|
||||
self.wheel(Math.sign(e.deltaY), offset);
|
||||
};
|
||||
|
||||
this.blueprintNode.getGridDOMElement().addEventListener('wheel', this.mouseWheelHandler, false);
|
||||
this.blueprintNode.getGridDOMElement().parentElement.addEventListener('wheel', e => e.preventDefault());
|
||||
this.movementSpace.addEventListener('wheel', this.mouseWheelHandler, false);
|
||||
// Prevent movement space from being scrolled
|
||||
this.movementSpace.parentElement?.addEventListener('wheel', e => e.preventDefault());
|
||||
}
|
||||
|
||||
/* Subclasses will override the following method */
|
||||
wheel(location, variation) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
class UEBlueprintSelect {
|
||||
constructor(blueprintNode, options) {
|
||||
/** @type {import("./UEBlueprint.js").default;}" */
|
||||
this.blueprintNode = blueprintNode;
|
||||
this.mousePosition = [0, 0];
|
||||
this.clickButton = options?.clickButton ?? 0;
|
||||
this.exitSelectAnyButton = options?.exitSelectAnyButton ?? true;
|
||||
let self = this;
|
||||
|
||||
this.mouseDownHandler = function (e) {
|
||||
switch (e.button) {
|
||||
case self.clickButton:
|
||||
self.clicked([e.offsetX, e.offsetY]);
|
||||
break
|
||||
default:
|
||||
if (!self.exitSelectAnyButton) {
|
||||
self.mouseUpHandler(e);
|
||||
}
|
||||
break
|
||||
}
|
||||
};
|
||||
|
||||
this.mouseMoveHandler = function (e) {
|
||||
e.preventDefault();
|
||||
let scaleCorrection = 1 / self.blueprintNode.getScale();
|
||||
const targetOffset = e.target.getBoundingClientRect();
|
||||
const currentTargetOffset = e.currentTarget.getBoundingClientRect();
|
||||
let offset = [
|
||||
e.offsetX + targetOffset.x * scaleCorrection - currentTargetOffset.x * scaleCorrection,
|
||||
e.offsetY + targetOffset.y * scaleCorrection - currentTargetOffset.y * scaleCorrection
|
||||
];
|
||||
self.blueprintNode.doSelecting(offset);
|
||||
};
|
||||
|
||||
this.mouseUpHandler = function (e) {
|
||||
if (!self.exitSelectAnyButton || e.button == self.clickButton) {
|
||||
// Remove the handlers of `mousemove` and `mouseup`
|
||||
self.blueprintNode.getGridDOMElement().removeEventListener('mousemove', self.mouseMoveHandler);
|
||||
self.blueprintNode.finishSelecting();
|
||||
document.removeEventListener('mouseup', self.mouseUpHandler);
|
||||
}
|
||||
};
|
||||
|
||||
let gridElement = this.blueprintNode.getGridDOMElement();
|
||||
gridElement.addEventListener('mousedown', this.mouseDownHandler);
|
||||
gridElement.addEventListener('contextmenu', e => e.preventDefault());
|
||||
}
|
||||
|
||||
unlistenDOMElement() {
|
||||
this.blueprintNode.removeEventListener('mousedown', this.mouseDownHandler);
|
||||
}
|
||||
|
||||
clicked(position) {
|
||||
// Attach the listeners to `document`
|
||||
this.blueprintNode.getGridDOMElement().addEventListener('mousemove', this.mouseMoveHandler);
|
||||
document.addEventListener('mouseup', this.mouseUpHandler);
|
||||
// Start selecting
|
||||
this.blueprintNode.startSelecting(position);
|
||||
class UZoom extends UMouseWheel {
|
||||
wheel(variation, location) {
|
||||
let zoomLevel = this.blueprint.getZoom();
|
||||
zoomLevel -= variation;
|
||||
this.blueprint.setZoom(zoomLevel, location);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -237,13 +312,6 @@ class OrderedIndexArray {
|
||||
* @returns {number} The position into occupied by value into the array.
|
||||
*/
|
||||
insert(element, comparisonValue = null) {
|
||||
let i = 0;
|
||||
for (i = 0; i < this.length; ++i) {
|
||||
if (element == this.array[i]) {
|
||||
console.log("error");
|
||||
break;
|
||||
}
|
||||
}
|
||||
let position = this.getPosition(this.comparisonValueSupplier(element));
|
||||
if (
|
||||
position < this.currentPosition
|
||||
@@ -260,9 +328,6 @@ class OrderedIndexArray {
|
||||
this.shiftRight(position);
|
||||
this.array[position] = element;
|
||||
++this.length;
|
||||
if (this.length > this.array.length) {
|
||||
console.log("error2");
|
||||
}
|
||||
return position
|
||||
}
|
||||
|
||||
@@ -355,16 +420,16 @@ class FastSelectionModel {
|
||||
* @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.
|
||||
* @param {(rect: Rectangle, selected: bool) => void} selectFunc A function that selects or deselects individual rectangles.
|
||||
*/
|
||||
constructor(initialPosition, rectangles, boundariesFunc, selectToggleFunction) {
|
||||
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.selectToggleFunction = selectToggleFunction;
|
||||
this.selectFunc = selectFunc;
|
||||
this.rectangles = rectangles;
|
||||
this.primaryOrder.reserve(this.rectangles.length);
|
||||
this.secondaryOrder.reserve(this.rectangles.length);
|
||||
@@ -377,7 +442,7 @@ class FastSelectionModel {
|
||||
onSecondaryAxis: false
|
||||
};
|
||||
this.metadata[index] = rectangleMetadata;
|
||||
selectToggleFunction(rect, false); // Initially deselected (Eventually)
|
||||
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
|
||||
@@ -400,7 +465,7 @@ class FastSelectionModel {
|
||||
if (rectangleBoundaries.secondarySup < this.initialPosition[1] || this.initialPosition[1] < rectangleBoundaries.secondaryInf) {
|
||||
this.secondaryOrder.insert(index);
|
||||
} else {
|
||||
selectToggleFunction(rect, true);
|
||||
selectFunc(rect, true);
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -413,22 +478,22 @@ class FastSelectionModel {
|
||||
this.boundaries = {
|
||||
// Primary axis negative expanding
|
||||
primaryN: {
|
||||
'value': this.primaryOrder.getPrevValue(),
|
||||
'index': this.primaryOrder.getPrev()
|
||||
v: this.primaryOrder.getPrevValue(),
|
||||
i: this.primaryOrder.getPrev()
|
||||
},
|
||||
primaryP: {
|
||||
'value': this.primaryOrder.getNextValue(),
|
||||
'index': this.primaryOrder.getNext()
|
||||
v: this.primaryOrder.getNextValue(),
|
||||
i: this.primaryOrder.getNext()
|
||||
},
|
||||
// Secondary axis negative expanding
|
||||
secondaryN: {
|
||||
'value': this.secondaryOrder.getPrevValue(),
|
||||
'index': this.secondaryOrder.getPrev()
|
||||
v: this.secondaryOrder.getPrevValue(),
|
||||
i: this.secondaryOrder.getPrev()
|
||||
},
|
||||
// Secondary axis positive expanding
|
||||
secondaryP: {
|
||||
'value': this.secondaryOrder.getNextValue(),
|
||||
'index': this.secondaryOrder.getNext()
|
||||
v: this.secondaryOrder.getNextValue(),
|
||||
i: this.secondaryOrder.getNext()
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -440,7 +505,7 @@ class FastSelectionModel {
|
||||
];
|
||||
const primaryBoundaryCrossed = (index, added) => {
|
||||
if (this.metadata[index].onSecondaryAxis) {
|
||||
this.selectToggleFunction(this.rectangles[index], added);
|
||||
this.selectFunc(this.rectangles[index], added);
|
||||
} else {
|
||||
if (added) {
|
||||
this.secondaryOrder.insert(index, finalPosition[1]);
|
||||
@@ -452,10 +517,10 @@ class FastSelectionModel {
|
||||
&& Math.sign(secondaryBoundary - this.initialPosition[1]) == direction[1]
|
||||
) {
|
||||
// Secondary axis is already satisfied then
|
||||
this.selectToggleFunction(this.rectangles[index], true);
|
||||
this.selectFunc(this.rectangles[index], true);
|
||||
}
|
||||
} else {
|
||||
this.selectToggleFunction(this.rectangles[index], false);
|
||||
this.selectFunc(this.rectangles[index], false);
|
||||
this.secondaryOrder.remove(index);
|
||||
}
|
||||
}
|
||||
@@ -463,35 +528,35 @@ class FastSelectionModel {
|
||||
this.selectTo(finalPosition);
|
||||
};
|
||||
|
||||
if (finalPosition[0] < this.boundaries.primaryN.value) {
|
||||
if (finalPosition[0] < this.boundaries.primaryN.v) {
|
||||
--this.primaryOrder.currentPosition;
|
||||
primaryBoundaryCrossed(
|
||||
this.boundaries.primaryN.index,
|
||||
this.initialPosition[0] > this.boundaries.primaryN.value && finalPosition[0] < this.initialPosition[0]);
|
||||
} else if (finalPosition[0] > this.boundaries.primaryP.value) {
|
||||
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.index,
|
||||
this.initialPosition[0] < this.boundaries.primaryP.value && this.initialPosition[0] < finalPosition[0]);
|
||||
this.boundaries.primaryP.i,
|
||||
this.initialPosition[0] < this.boundaries.primaryP.v && this.initialPosition[0] < finalPosition[0]);
|
||||
}
|
||||
|
||||
|
||||
const secondaryBoundaryCrossed = (index, added) => {
|
||||
this.selectToggleFunction(this.rectangles[index], added);
|
||||
this.selectFunc(this.rectangles[index], added);
|
||||
this.computeBoundaries(finalPosition);
|
||||
this.selectTo(finalPosition);
|
||||
};
|
||||
|
||||
if (finalPosition[1] < this.boundaries.secondaryN.value) {
|
||||
if (finalPosition[1] < this.boundaries.secondaryN.v) {
|
||||
--this.secondaryOrder.currentPosition;
|
||||
secondaryBoundaryCrossed(
|
||||
this.boundaries.secondaryN.index,
|
||||
this.initialPosition[1] > this.boundaries.secondaryN.value && finalPosition[1] < this.initialPosition[1]);
|
||||
} else if (finalPosition[1] > this.boundaries.secondaryP.value) {
|
||||
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.index,
|
||||
this.initialPosition[1] < this.boundaries.secondaryP.value && this.initialPosition[1] < finalPosition[1]);
|
||||
this.boundaries.secondaryP.i,
|
||||
this.initialPosition[1] < this.boundaries.secondaryP.v && this.initialPosition[1] < finalPosition[1]);
|
||||
}
|
||||
this.finalPosition = finalPosition;
|
||||
}
|
||||
@@ -499,7 +564,7 @@ class FastSelectionModel {
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef {import("./UEBlueprintObject.js").default} UEBlueprintObject
|
||||
* @typedef {import("./UEBlueprintObject").default} UEBlueprintObject
|
||||
*/
|
||||
class UEBlueprint extends HTMLElement {
|
||||
|
||||
@@ -555,8 +620,6 @@ class UEBlueprint extends HTMLElement {
|
||||
this.selectorElement = null;
|
||||
/** @type {HTMLElement} */
|
||||
this.nodesContainerElement = null;
|
||||
/** @type {IntersectionObserver} */
|
||||
this.selectorObserver = null;
|
||||
this.dragObject = null;
|
||||
this.selectObject = null;
|
||||
/** @type {Array<number>} */
|
||||
@@ -601,28 +664,19 @@ class UEBlueprint extends HTMLElement {
|
||||
this.nodesContainerElement = this.querySelector('[data-nodes]');
|
||||
this.insertChildren();
|
||||
|
||||
this.selectorObserver = new IntersectionObserver(
|
||||
(entries, observer) => {
|
||||
entries.map(entry => {
|
||||
/** @type {import("./UEBlueprintObject.js").default;}" */
|
||||
let target = entry.target;
|
||||
target.setSelected(entry.isIntersecting);
|
||||
});
|
||||
}, {
|
||||
threshold: [0.01],
|
||||
root: this.selectorElement
|
||||
});
|
||||
this.nodes.forEach(element => this.selectorObserver.observe(element));
|
||||
|
||||
this.dragObject = new UEBlueprintDragScroll(this, {
|
||||
this.dragObject = new UDragScroll(this.getGridDOMElement(), this, {
|
||||
'clickButton': 2,
|
||||
'stepSize': 1,
|
||||
'exitDragAnyButton': false
|
||||
});
|
||||
|
||||
this.selectObject = new UEBlueprintSelect(this, {
|
||||
this.zoomObject = new UZoom(this.getGridDOMElement(), this, {
|
||||
looseTarget: true
|
||||
});
|
||||
|
||||
this.selectObject = new USelect(this.getGridDOMElement(), this, {
|
||||
'clickButton': 0,
|
||||
'exitSelectAnyButton': true
|
||||
'exitAnyButton': true
|
||||
});
|
||||
}
|
||||
|
||||
@@ -850,6 +904,13 @@ class UEBlueprint extends HTMLElement {
|
||||
this.selectionModel.selectTo(finalPosition);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unselect all nodes
|
||||
*/
|
||||
unselectAll() {
|
||||
this.nodes.forEach(node => this.nodeSelectToggleFunction(node, false));
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {...UEBlueprintObject} blueprintNodes
|
||||
@@ -878,7 +939,9 @@ class UEBlueprintDraggableObject extends HTMLElement {
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
this.dragObject = new UEBlueprintDrag(this);
|
||||
this.dragObject = new UDrag(this, null, {
|
||||
looseTarget: true
|
||||
});
|
||||
}
|
||||
|
||||
disconnectedCallback() {
|
||||
|
||||
Reference in New Issue
Block a user