Input refactoring (#12)

* Fix folder name typo

* Smaller fixes

* Shortcut rename to Shortcuts

* Fix quoted attributes in UE 5.3

* remove KeyboardShortcutAction

* Remove more trivial classes

* Rename IKeyboardShortcut

* Node delete shortcut
This commit is contained in:
barsdeveloper
2023-08-11 02:48:50 +02:00
committed by GitHub
parent 2284789e6e
commit ed43ee3edd
29 changed files with 652 additions and 485 deletions

View File

@@ -2,7 +2,7 @@ import Configuration from "../Configuration.js"
/** @typedef {import("../Blueprint.js").default} Blueprint */
/** @template {HTMLElement} T */
/** @template {Element} T */
export default class IInput {
/** @type {T} */
@@ -17,6 +17,8 @@ export default class IInput {
return this.#blueprint
}
consumeEvent = true
/** @type {Object} */
options
@@ -35,6 +37,7 @@ export default class IInput {
options.unlistenOnTextEdit ??= false
this.#target = target
this.#blueprint = blueprint
this.consumeEvent = options.consumeEvent
this.options = options
}

View File

@@ -0,0 +1,8 @@
import IInput from "./IInput"
/** @typedef {import("../Blueprint.js").default} Blueprint */
export default class InputCombination {
constructor() { }
}

View File

@@ -13,7 +13,7 @@ export default class Copy extends IInput {
options.unlistenOnTextEdit ??= true // No nodes copy if inside a text field, just text (default behavior)
super(target, blueprint, options)
let self = this
this.#copyHandler = _ => self.copied()
this.#copyHandler = () => self.copied()
}
listenEvents() {

View File

@@ -1,6 +1,5 @@
import IInput from "../IInput.js"
import ObjectSerializer from "../../serialization/ObjectSerializer.js"
import KeyboardCanc from "../keybaord/KeyboardCanc.js"
export default class Cut extends IInput {
@@ -14,7 +13,7 @@ export default class Cut extends IInput {
options.unlistenOnTextEdit ??= true // No nodes copy if inside a text field, just text (default behavior)
super(target, blueprint, options)
let self = this
this.#cutHandler = _ => self.cut()
this.#cutHandler = () => self.cut()
}
listenEvents() {
@@ -34,6 +33,6 @@ export default class Cut extends IInput {
cut() {
this.blueprint.template.getCopyInputObject().copied()
this.blueprint.template.getInputObject(KeyboardCanc).fire()
this.blueprint.removeGraphElement(...this.blueprint.getNodes(true))
}
}

View File

@@ -1,21 +0,0 @@
import IKeyboardShortcut from "./IKeyboardShortcut.js"
import Shortcut from "../../Shortcut.js"
/** @typedef {import("../../Blueprint.js").default} Blueprint */
export default class KeyboardCanc extends IKeyboardShortcut {
/**
* @param {HTMLElement} target
* @param {Blueprint} blueprint
* @param {Object} options
*/
constructor(target, blueprint, options = {}) {
options.activationKeys = Shortcut.deleteNodes
super(target, blueprint, options)
}
fire() {
this.blueprint.removeGraphElement(...this.blueprint.getNodes(true))
}
}

View File

@@ -1,21 +0,0 @@
import IKeyboardShortcut from "./IKeyboardShortcut.js"
import Shortcut from "../../Shortcut.js"
/** @typedef {import("../../Blueprint.js").default} Blueprint */
export default class KeyboardSelectAll extends IKeyboardShortcut {
/**
* @param {HTMLElement} target
* @param {Blueprint} blueprint
* @param {Object} options
*/
constructor(target, blueprint, options = {}) {
options.activationKeys = Shortcut.selectAllNodes
super(target, blueprint, options)
}
fire() {
this.blueprint.selectAll()
}
}

View File

@@ -1,41 +0,0 @@
import IKeyboardShortcut from "./IKeyboardShortcut.js"
/** @typedef {import("../../Blueprint.js").default} Blueprint */
/**
* @template {HTMLElement} T
* @extends IKeyboardShortcut<T>
*/
export default class KeyboardShortcutAction extends IKeyboardShortcut {
static #ignoreEvent =
/** @param {KeyboardShortcutAction} self */
self => { }
/**
* @param {T} target
* @param {Blueprint} blueprint
* @param {Object} options
* @param {(self: KeyboardShortcutAction<T>) => void} onKeyDown
* @param {(self: KeyboardShortcutAction<T>) => void} onKeyUp
*/
constructor(
target,
blueprint,
options,
onKeyDown = KeyboardShortcutAction.#ignoreEvent,
onKeyUp = KeyboardShortcutAction.#ignoreEvent
) {
super(target, blueprint, options)
this.onKeyDown = onKeyDown
this.onKeyUp = onKeyUp
}
fire() {
this.onKeyDown(this)
}
unfire() {
this.onKeyUp(this)
}
}

View File

@@ -1,10 +1,10 @@
import IKeyboardShortcut from "./IKeyboardShortcut.js"
import Shortcut from "../../Shortcut.js"
import KeyboardShortcut from "./KeyboardShortcut.js"
import Shortcuts from "../../Shortcuts.js"
import Zoom from "../mouse/Zoom.js"
/** @typedef {import("../../Blueprint.js").default} Blueprint */
export default class KeyboardEnableZoom extends IKeyboardShortcut {
export default class KeyboardEnableZoom extends KeyboardShortcut {
/** @type {Zoom} */
#zoomInputObject
@@ -15,12 +15,12 @@ export default class KeyboardEnableZoom extends IKeyboardShortcut {
* @param {Object} options
*/
constructor(target, blueprint, options = {}) {
options.activationKeys = Shortcut.enableZoomIn
options.activationKeys = Shortcuts.enableZoomIn
super(target, blueprint, options)
}
fire() {
this.#zoomInputObject = this.blueprint.getInputObject(Zoom)
this.#zoomInputObject = this.blueprint.template.getZoomInputObject()
this.#zoomInputObject.enableZoonIn = true
}

View File

@@ -6,10 +6,14 @@ import KeyBindingEntity from "../../entity/KeyBindingEntity.js"
/** @typedef {import("../../Blueprint.js").default} Blueprint */
/**
* @template {HTMLElement} T
* @template {Element} T
* @extends IInput<T>
*/
export default class IKeyboardShortcut extends IInput {
export default class KeyboardShortcut extends IInput {
static #ignoreEvent =
/** @param {KeyboardShortcut} self */
self => { }
/** @type {KeyBindingEntity[]} */
#activationKeys
@@ -21,8 +25,13 @@ export default class IKeyboardShortcut extends IInput {
* @param {Blueprint} blueprint
* @param {Object} options
*/
constructor(target, blueprint, options = {}) {
options.activateAnyKey ??= false
constructor(
target,
blueprint,
options = {},
onKeyDown = KeyboardShortcut.#ignoreEvent,
onKeyUp = KeyboardShortcut.#ignoreEvent
) {
options.activationKeys ??= []
options.consumeEvent ??= true
options.listenOnFocus ??= true
@@ -44,6 +53,8 @@ export default class IKeyboardShortcut extends IInput {
})
super(target, blueprint, options)
this.onKeyDown = onKeyDown
this.onKeyUp = onKeyUp
this.#activationKeys = this.options.activationKeys ?? []
@@ -55,15 +66,14 @@ export default class IKeyboardShortcut extends IInput {
/** @param {KeyboardEvent} e */
this.keyDownHandler = e => {
if (
this.options.activateAnyKey
|| self.#activationKeys.some(keyEntry =>
self.#activationKeys.some(keyEntry =>
wantsShift(keyEntry) == e.shiftKey
&& wantsCtrl(keyEntry) == e.ctrlKey
&& wantsAlt(keyEntry) == e.altKey
&& Configuration.Keys[keyEntry.Key] == e.code
&& Configuration.Keys[keyEntry.Key.value] == e.code
)
) {
if (options.consumeEvent) {
if (this.consumeEvent) {
e.preventDefault()
e.stopImmediatePropagation()
}
@@ -77,16 +87,15 @@ export default class IKeyboardShortcut extends IInput {
/** @param {KeyboardEvent} e */
this.keyUpHandler = e => {
if (
this.options.activateAnyKey
|| self.#activationKeys.some(keyEntry =>
self.#activationKeys.some(keyEntry =>
keyEntry.bShift && e.key == "Shift"
|| keyEntry.bCtrl && e.key == "Control"
|| keyEntry.bAlt && e.key == "Alt"
|| keyEntry.bCmd && e.key == "Meta"
|| Configuration.Keys[keyEntry.Key] == e.code
|| Configuration.Keys[keyEntry.Key.value] == e.code
)
) {
if (options.consumeEvent) {
if (this.consumeEvent) {
e.stopImmediatePropagation()
}
self.unfire()
@@ -105,11 +114,13 @@ export default class IKeyboardShortcut extends IInput {
document.removeEventListener("keydown", this.keyDownHandler)
}
// Subclasses will want to override
/* Subclasses can override */
fire() {
this.onKeyDown(this)
}
unfire() {
this.onKeyUp(this)
}
}

View File

@@ -21,15 +21,17 @@ export default class IMouseClickDrag extends IPointing {
case this.options.clickButton:
// Either doesn't matter or consider the click only when clicking on the parent, not descandants
if (!this.options.strictTarget || e.target == e.currentTarget) {
if (this.options.consumeEvent) {
if (this.consumeEvent) {
e.stopImmediatePropagation() // Captured, don't call anyone else
}
// Attach the listeners
this.#movementListenedElement.addEventListener("mousemove", this.#mouseStartedMovingHandler)
document.addEventListener("mouseup", this.#mouseUpHandler)
this.clickedPosition = this.locationFromEvent(e)
this.blueprint.mousePosition[0] = this.clickedPosition[0]
this.blueprint.mousePosition[1] = this.clickedPosition[1]
this.setLocationFromEvent(e)
this.clickedPosition[0] = this.location[0]
this.clickedPosition[1] = this.location[1]
this.blueprint.mousePosition[0] = this.location[0]
this.blueprint.mousePosition[1] = this.location[1]
if (this.target instanceof IDraggableElement) {
this.clickedOffset = [
this.clickedPosition[0] - this.target.locationX,
@@ -49,7 +51,7 @@ export default class IMouseClickDrag extends IPointing {
/** @param {MouseEvent} e */
#mouseStartedMovingHandler = e => {
if (this.options.consumeEvent) {
if (this.consumeEvent) {
e.stopImmediatePropagation() // Captured, don't call anyone else
}
// Delegate from now on to this.#mouseMoveHandler
@@ -58,19 +60,19 @@ export default class IMouseClickDrag extends IPointing {
// Handler calls e.preventDefault() when it receives the event, this means dispatchEvent returns false
const dragEvent = this.getEvent(Configuration.trackingMouseEventName.begin)
this.#trackingMouse = this.target.dispatchEvent(dragEvent) == false
const location = this.locationFromEvent(e)
this.setLocationFromEvent(e)
// Do actual actions
this.lastLocation = Utility.snapToGrid(this.clickedPosition[0], this.clickedPosition[1], this.stepSize)
this.startDrag(location)
this.startDrag(this.location)
this.started = true
}
/** @param {MouseEvent} e */
#mouseMoveHandler = e => {
if (this.options.consumeEvent) {
if (this.consumeEvent) {
e.stopImmediatePropagation() // Captured, don't call anyone else
}
const location = this.locationFromEvent(e)
const location = this.setLocationFromEvent(e)
const movement = [e.movementX, e.movementY]
this.dragTo(location, movement)
if (this.#trackingMouse) {
@@ -104,7 +106,7 @@ export default class IMouseClickDrag extends IPointing {
/** @param {MouseEvent} e */
#mouseUpHandler = e => {
if (!this.options.exitAnyButton || e.button == this.options.clickButton) {
if (this.options.consumeEvent) {
if (this.consumeEvent) {
e.stopImmediatePropagation() // Captured, don't call anyone else
}
// Remove the handlers of "mousemove" and "mouseup"

View File

@@ -1,12 +1,29 @@
import IInput from "../IInput.js"
import Utility from "../../Utility.js"
/** @typedef {import("../keyboard/KeyboardShortcut.js").default} KeyboardShortcut */
/**
* @template {HTMLElement} T
* @template {Element} T
* @extends {IInput<T>}
*/
export default class IPointing extends IInput {
#location = [0, 0]
get location() {
return this.#location
}
/** @type {KeyboardShortcut?} */
#enablerKey
get enablerKey() {
return this.#enablerKey
}
#enablerActivated = true
get enablerActivated() {
return this.#enablerActivated
}
constructor(target, blueprint, options = {}) {
options.ignoreTranslateCompensate ??= false
options.ignoreScale ??= false
@@ -14,17 +31,28 @@ export default class IPointing extends IInput {
super(target, blueprint, options)
/** @type {HTMLElement} */
this.movementSpace = options.movementSpace
if (options.enablerKey) {
this.#enablerKey = options.enablerKey
this.#enablerKey.onKeyDown = () => this.#enablerActivated = true
this.#enablerKey.onKeyUp = () => this.#enablerActivated = false
this.#enablerKey.consumeEvent = false
this.#enablerKey.listenEvents()
this.#enablerActivated = false
}
}
/** @param {MouseEvent} mouseEvent */
locationFromEvent(mouseEvent) {
const location = Utility.convertLocation(
setLocationFromEvent(mouseEvent) {
let location = Utility.convertLocation(
[mouseEvent.clientX, mouseEvent.clientY],
this.movementSpace,
this.options.ignoreScale
)
return this.options.ignoreTranslateCompensate
location = this.options.ignoreTranslateCompensate
? location
: this.blueprint.compensateTranslation(location[0], location[1])
this.#location[0] = location[0]
this.#location[1] = location[1]
return this.#location
}
}

View File

@@ -2,26 +2,40 @@ import Configuration from "../../Configuration.js"
import IPointing from "./IPointing.js"
/**
* @template {HTMLElement} T
* @typedef {import("../../Blueprint.js").default} Blueprint
* @typedef {import("../keyboard/KeyboardShortcut.js").default} KeyboardShortcut
*/
/**
* @template {Element} T
* @extends {IPointing<T>}
*/
export default class MouseClick extends IPointing {
static #ignoreEvent =
/** @param {MouseClick} self */
self => { }
/** @param {MouseEvent} e */
#mouseDownHandler = e => {
this.blueprint.setFocused(true)
if (this.enablerKey && !this.enablerActivated) {
return
}
switch (e.button) {
case this.options.clickButton:
// Either doesn't matter or consider the click only when clicking on the target, not descandants
if (!this.options.strictTarget || e.target === e.currentTarget) {
if (this.options.consumeEvent) {
if (this.consumeEvent) {
e.stopImmediatePropagation() // Captured, don't call anyone else
}
// Attach the listeners
document.addEventListener("mouseup", this.#mouseUpHandler)
this.clickedPosition = this.locationFromEvent(e)
this.blueprint.mousePosition[0] = this.clickedPosition[0]
this.blueprint.mousePosition[1] = this.clickedPosition[1]
this.setLocationFromEvent(e)
this.clickedPosition[0] = this.location[0]
this.clickedPosition[1] = this.location[1]
this.blueprint.mousePosition[0] = this.location[0]
this.blueprint.mousePosition[1] = this.location[1]
this.clicked(this.clickedPosition)
}
break
@@ -36,7 +50,7 @@ export default class MouseClick extends IPointing {
/** @param {MouseEvent} e */
#mouseUpHandler = e => {
if (!this.options.exitAnyButton || e.button == this.options.clickButton) {
if (this.options.consumeEvent) {
if (this.consumeEvent) {
e.stopImmediatePropagation() // Captured, don't call anyone else
}
// Remove the handlers of "mousemove" and "mouseup"
@@ -47,12 +61,25 @@ export default class MouseClick extends IPointing {
clickedPosition = [0, 0]
constructor(target, blueprint, options = {}) {
/**
* @param {T} target
* @param {Blueprint} blueprint
* @param {Object} options
*/
constructor(
target,
blueprint,
options = {},
onClick = MouseClick.#ignoreEvent,
onUnclick = MouseClick.#ignoreEvent,
) {
options.clickButton ??= Configuration.mouseClickButton
options.consumeEvent ??= true
options.exitAnyButton ??= true
options.strictTarget ??= false
super(target, blueprint, options)
this.onClick = onClick
this.onUnclick = onUnclick
this.listenEvents()
}
@@ -69,8 +96,10 @@ export default class MouseClick extends IPointing {
/* Subclasses will override the following methods */
clicked(location) {
this.onClick(this)
}
unclicked(location) {
this.onUnclick(this)
}
}

View File

@@ -12,10 +12,10 @@ export default class MouseDbClick extends IPointing {
/** @param {MouseEvent} e */
#mouseDbClickHandler = e => {
if (!this.options.strictTarget || e.target === e.currentTarget) {
if (this.options.consumeEvent) {
if (this.consumeEvent) {
e.stopImmediatePropagation() // Captured, don't call anyone else
}
this.clickedPosition = this.locationFromEvent(e)
this.clickedPosition = this.setLocationFromEvent(e)
this.blueprint.mousePosition[0] = this.clickedPosition[0]
this.blueprint.mousePosition[1] = this.clickedPosition[1]
this.dbclicked(this.clickedPosition)

View File

@@ -9,7 +9,9 @@ export default class MouseTracking extends IPointing {
/** @param {MouseEvent} e */
#mousemoveHandler= e => {
e.preventDefault()
this.blueprint.mousePosition = this.locationFromEvent(e)
this.setLocationFromEvent(e)
this.blueprint.mousePosition[0] = this.location[0]
this.blueprint.mousePosition[1] = this.location[1]
}
/** @param {CustomEvent} e */

View File

@@ -2,13 +2,26 @@ import IPointing from "./IPointing.js"
/** @typedef {import("../../Blueprint.js").default} Blueprint */
export default class IMouseWheel extends IPointing {
export default class MouseWheel extends IPointing {
static #ignoreEvent =
/** @param {MouseWheel} self */
self => { }
#variation = 0
get variation() {
return this.#variation
}
/** @param {WheelEvent} e */
#mouseWheelHandler = e => {
if (this.enablerKey && !this.enablerActivated) {
return
}
e.preventDefault()
const location = this.locationFromEvent(e)
this.wheel(e.deltaY, location)
this.#variation = e.deltaY
this.setLocationFromEvent(e)
this.wheel()
}
/** @param {WheelEvent} e */
@@ -19,11 +32,17 @@ export default class IMouseWheel extends IPointing {
* @param {Blueprint} blueprint
* @param {Object} options
*/
constructor(target, blueprint, options = {}) {
constructor(
target,
blueprint,
options = {},
onWheel = MouseWheel.#ignoreEvent,
) {
options.listenOnFocus = true
options.strictTarget ??= false
super(target, blueprint, options)
this.strictTarget = options.strictTarget
this.onWheel = onWheel
}
listenEvents() {
@@ -36,7 +55,8 @@ export default class IMouseWheel extends IPointing {
this.movementSpace.parentElement?.removeEventListener("wheel", this.#mouseParentWheelHandler)
}
/* Subclasses will override the following method */
wheel(variation, location) {
/* Subclasses can override */
wheel() {
this.onWheel(this)
}
}

View File

@@ -1,7 +1,7 @@
import Configuration from "../../Configuration.js"
import IMouseWheel from "./IMouseWheel.js"
import MouseWheel from "./MouseWheel.js"
export default class Zoom extends IMouseWheel {
export default class Zoom extends MouseWheel {
#accumulatedVariation = 0
@@ -16,19 +16,17 @@ export default class Zoom extends IMouseWheel {
this.#enableZoonIn = value
}
wheel(variation, location) {
this.#accumulatedVariation += -variation
variation = this.#accumulatedVariation
wheel() {
this.#accumulatedVariation += -this.variation
if (Math.abs(this.#accumulatedVariation) < Configuration.mouseWheelZoomThreshold) {
return
} else {
this.#accumulatedVariation = 0
}
let zoomLevel = this.blueprint.getZoom()
if (!this.enableZoonIn && zoomLevel == 0 && variation > 0) {
if (!this.enableZoonIn && zoomLevel == 0 && this.#accumulatedVariation > 0) {
return
}
zoomLevel += Math.sign(variation)
this.blueprint.setZoom(zoomLevel, location)
zoomLevel += Math.sign(this.#accumulatedVariation)
this.blueprint.setZoom(zoomLevel, this.location)
this.#accumulatedVariation = 0
}
}