Last active
January 1, 2024 20:53
-
-
Save nhrones/7e3e6d5ceebfc78a4edf0dca6ebd253e to your computer and use it in GitHub Desktop.
DWM=ReactiveUI system events
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { activeNodes } from '../render/activeNodes.ts' | |
import { Host, ctx, dwmWindow, hasVisiblePopup } from '../render/renderContext.ts' | |
import type { View } from '../types.ts'; | |
import { events } from './eventBus.ts' | |
//==================================================== | |
// Sytem Events Module | |
// Watch for all host window generated events. | |
// When appropriate, propagate these host events | |
// to our central eventBus - events. | |
// | |
// When an event targets an active node, based on | |
// the event type, set the active nodes state to | |
// either hovered, or to focused, and reference | |
// that node as the hoveredNode or the focusedNode. | |
//==================================================== | |
const left = 0 | |
// values re-used repeatedly in event handlers | |
// -- we reuse these to reduce pressure on GC | |
let x = 0 | |
let y = 0 | |
let hit = false | |
let node: View | null = null | |
let hoveredNode: View | null = null | |
let focusedNode: View | null = null | |
/** | |
* Initialize an environment for mouse/touch event handlers. | |
* | |
* Registers event handlers for: | |
* WindowInputEvent | |
* WindowKeyboardEvent | |
* mousedown + touchstart => handleClickOrTouch() | |
* mousemove => handleMouseMove | |
*/ | |
export function initHostEvents( ): void { | |
// handle all host window `input` events | |
addEventListener("input", (evt: any) => { | |
// look for a focused node, if none, just ignore the event | |
if (focusedNode !== null) { | |
// we'll fire this event directly to amy focused node | |
events.fire('WindowInput', focusedNode.name, evt) | |
} | |
}) | |
// handler for `keydown` -- enter backspace, delete, etc. | |
addEventListener('keydown', (evt: any) => { | |
let focusNum = 0 | |
// handle Tab key | |
if (evt.code === 'Tab') { | |
if (focusedNode !== null) { | |
const direction = (evt.shiftKey) ? -1 : +1 | |
focusNum = focusNext(focusedNode.tabOrder + direction, evt.shiftKey) | |
} else { | |
focusNum = focusNext(1, evt.shiftKey) // focus first | |
} | |
if (focusNum === 0) { // not found | |
const last = (evt.shiftKey) ? 20 : 1 | |
focusNext(last, evt.shiftKey) // focus first | |
} | |
return | |
} | |
// handle Enter key | |
if (evt.code === 'Enter') { | |
if (hasVisiblePopup === true) { | |
events.fire(`PopupReset`, "", null) | |
} else if (focusedNode !== null) { | |
focusedNode.touched() | |
} | |
} | |
// look for a currently `focused` node | |
if (focusedNode !== null) { | |
// we'll signal only the node with focus | |
events.fire('WindowKeyDown', focusedNode.name, evt) | |
} | |
}) | |
// register a handler for our host mousedown event | |
addEventListener('mousedown', (evt) => { | |
evt.preventDefault() | |
if (evt.button === left) { | |
if (hasVisiblePopup === false) { | |
// we'll hit-test all activeNodes | |
handleClickOrTouch(evt.pageX, evt.pageY) | |
} // a popup iwas open, just close it | |
else { | |
events.fire(`PopupReset`, "", null) | |
} | |
} | |
}, false) | |
// register a handler for our hosts mousemove event | |
addEventListener('mousemove', (evt) => { | |
evt.preventDefault() | |
// If a popup is open, don't bother with hover testing! | |
if (hasVisiblePopup === false) { | |
handleMouseMove(evt) | |
} | |
}) | |
// we send all scroll events unconditionally | |
// to service any scrollable containers | |
addEventListener('scroll', (evt) => { | |
evt.preventDefault(); | |
const y = (Math.sign(evt.scrollY)); | |
events.fire('Scroll', "", { deltaY: y }) | |
}); | |
} | |
//===================================================== | |
// custom event handlers | |
//===================================================== | |
/** | |
* Handles canvas mouse-move event. | |
* Provides logic to emulate 'onmouseenter', and | |
* 'onmouseleave' DOM events on our virtual elements. | |
* Uses the canvasContexts 'isPointInPath' method for hit-testing. | |
* @param {MouseEvent} evt - from canvas.mousemove event | |
*/ | |
function handleMouseMove(evt: any,) { | |
x = evt.clientX | |
y = evt.clientY | |
// test for hovered | |
node = null | |
for (const n of activeNodes) { | |
if (ctx.isPointInPath(n.path, x, y)) { | |
// going from bottom to top, top-most object wins | |
node = n | |
} | |
} | |
if (node !== null) { // did we get a hit? | |
if (node !== hoveredNode) { // hit was not the current hovered node | |
clearHovered() // clear any prior hover | |
node.hovered = true // set this nodes `hovered` flag | |
node.update() // command to update the hovered node | |
hoveredNode = node // register this node as currently hovered | |
dwmWindow.setCursor("hand") // change the cursor | |
Host.dirty() // force a flush and swap | |
} | |
} else { // no node was hit | |
if (hoveredNode !== null) { // is there a hovered node? | |
clearHovered() // remove hovered state | |
hoveredNode = null // no node currently hover | |
} | |
} | |
} | |
/** | |
* Handler for both, canvas-mouse-Click and canvas-Touch events. | |
* Uses the canvasContexts 'isPointInPath' method for hit-testing. | |
* | |
* If a hit is detected, we directly call the elements touched() method. | |
* When called, the elements `touched()` method will then broadcast | |
* a `touched` event to any registered subscribers. | |
* | |
* @param {number} x - horizontal location of this event | |
* @param {number} y - vertical location of this event | |
*/ | |
function handleClickOrTouch(x: number, y: number) { | |
hit = false | |
for (const node of activeNodes) { | |
if (!hit) { // short circuit once we get a hit | |
// check each node (bottom to top), top-most object wins | |
if (ctx.isPointInPath(node.path, x, y)) { | |
// got one, call the nodes touched method | |
node.touched() | |
// clear any currently focused view | |
clearFocused() | |
// set this as the currently focused node | |
focusedNode = node | |
// tell others about this newly focused node | |
if (focusedNode) | |
events.fire('Focused', focusedNode.name, true); | |
hit = true | |
} | |
} | |
} | |
// nothing touched - clear the currently focused node | |
if (!hit) clearFocused() | |
} | |
/** clear last focused object */ | |
function clearFocused() { | |
if (focusedNode !== null) { | |
focusedNode.focused = false; | |
focusedNode.hovered = false | |
events.fire('Focused', focusedNode.name, focusedNode.focused); | |
focusedNode.update(); // re-render the node | |
} | |
} | |
/** clear last hovered object */ | |
function clearHovered() { | |
//dwmWindow.setCursor("arrow") | |
if (hoveredNode !== null) { | |
hoveredNode.hovered = false | |
hoveredNode.update() // re-render the node | |
} | |
} | |
/** change focus to this tabOrder */ | |
function focusNext(target: number, _shift: boolean) { | |
hit = false | |
for (const node of activeNodes) { | |
if (hit === false) { // short circuit once we get a hit | |
if (node.tabOrder === target) { | |
// clear any currently focused view | |
clearFocused() | |
clearHovered() | |
// set this one as focused | |
focusedNode = node | |
if (focusedNode) { | |
focusedNode.focused = true; | |
focusedNode.hovered = true | |
// got one, call the nodes update method | |
focusedNode.update() | |
// tell others about this newly focused node | |
events.fire('Focused', focusedNode.name, true) | |
} | |
hit = true | |
} | |
} | |
} | |
return (hit === false) ? 0 : target | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment