UNPKG

lazy-widgets

Version:

Typescript retained mode GUI for the HTML canvas API

149 lines 6.53 kB
import { PointerReleaseEvent } from '../events/PointerReleaseEvent.js'; import { GenericClickHelper } from './GenericClickHelper.js'; import { PointerPressEvent } from '../events/PointerPressEvent.js'; import { PointerEvent } from '../events/PointerEvent.js'; import { FocusType } from '../core/FocusType.js'; import { ClickState } from './ClickState.js'; import { LeaveEvent } from '../events/LeaveEvent.js'; /** * An aggregate helper class for widgets that can be clicked. * * Keeps its current click state as well as its last click state, last pointer * position and whether the last click state change resulted in an actual click. * * @category Helper */ export class ClickHelper extends GenericClickHelper { constructor() { super(...arguments); /** * Last pointer position in normalised coordinates ([0,0] to [1,1]). If * there is no last pointer position, such as after a leave event, this will * be null. If pointer position was outside box, it will be beyond the [0,0] * to [1,1] range. */ this.pointerPos = null; /** * Like {@link ClickHelper#pointerPos}, but only updated when a hold state * begins. * * Useful for implementing draggable widgets. */ this.startingPointerPos = null; /** Which pointer button should count as a click? Left button by default */ this.pointerButton = 0; } /** * Normalise pointer coordinates inside a rectangle * * @param pX - Pointer X coordinate, in pixels * @param pY - Pointer Y coordinate, in pixels * @param rLeft - Rectangle's left coordinate, in pixels * @param rRight - Rectangle's right coordinate, in pixels * @param rTop - Rectangle's top coordinate, in pixels * @param rBottom - Rectangle's bottom coordinate, in pixels * @returns Returns normalised coordinates */ getNormalInRect(pX, pY, rLeft, rRight, rTop, rBottom) { return [(pX - rLeft) / (rRight - rLeft), (pY - rTop) / (rBottom - rTop)]; } /** * Check if a point, in pixels, is inside a rectangle. * * @param pX - Pointer X coordinate, in pixels * @param pY - Pointer Y coordinate, in pixels * @param rLeft - Rectangle's left coordinate, in pixels * @param rRight - Rectangle's right coordinate, in pixels * @param rTop - Rectangle's top coordinate, in pixels * @param rBottom - Rectangle's bottom coordinate, in pixels * @returns Returns true if [pX, pY] is inside the rectangle, else, false */ isPointInRect(pX, pY, rLeft, rRight, rTop, rBottom) { return pX >= rLeft && pX < rRight && pY >= rTop && pY < rBottom; } /** * Check if a normalised point is inside a rectangle. * * Since the coordinates are normalised, you don't have to define the * coordinates of the rectangle, which may seem counterintuitive. * * @param pX - Pointer X coordinate, normalised * @param pY - Pointer Y coordinate, normalised * @returns Returns true if [pX, pY] is inside the rectangle, else, false */ isNormalInRect(pX, pY) { return pX >= 0 && pX < 1 && pY >= 0 && pY < 1; } /** * Updates the current {@link GenericClickHelper#clickState} given an event, * as well as {@link Root#_foci | focus}, * {@link GenericClickHelper#wasClick} and * {@link GenericClickHelper#clickStateChanged} flags, and requests pointer * styles when necessary. * * @param bounds - A 4-tuple containing, respectively, left coordinate, right coordinate, top coordinate and bottom coordinate of clickable area, in pixels */ handleClickEvent(event, root, bounds) { // TODO make bounds readonly if (event.isa(LeaveEvent)) { // Drop focus on this widget if this is a leave event root.dropFocus(FocusType.Pointer, this.widget); root.clearPointerStyle(this.widget, this); this.pointerPos = null; return this.setClickState(ClickState.Released, false); } else if (event instanceof PointerEvent) { // Normalise pointer coordinates in click area this.pointerPos = this.getNormalInRect(event.x, event.y, ...bounds); // If pointer is over the clickable rectangle, then change the // pointer style, else, if not targeted, drop focus const inside = this.isNormalInRect(...this.pointerPos); if (inside) { root.requestPointerStyle(this.widget, 'pointer', this); } else { root.clearPointerStyle(this.widget, this); if (event.target === null) { root.dropFocus(FocusType.Pointer, this.widget); return this.setClickState(ClickState.Released, false); } } // If this is a press event, request focus and set starting // pointer coordinates. Ignore if wrong button if (event.isa(PointerPressEvent) && event.button === this.pointerButton) { this.startingPointerPos = this.pointerPos; root.requestFocus(FocusType.Pointer, this.widget); return this.setClickState(ClickState.Hold, inside); } // If this is a release event, drop focus. Ignore if wrong button if (event.isa(PointerReleaseEvent) && event.button === this.pointerButton) { root.dropFocus(FocusType.Pointer, this.widget); if (inside) { return this.setClickState(ClickState.Hover, inside); } else { return this.setClickState(ClickState.Released, inside); } } // If event was focused, then it's a hold, else, it's a hover if (event.target === null) { return this.setClickState(ClickState.Hover, inside); } else { return this.setClickState(ClickState.Hold, inside); } } } reset() { super.reset(); this.pointerPos = null; this.startingPointerPos = null; this.pointerButton = 0; if (this.widget.attached) { const root = this.widget.root; root.dropFocus(FocusType.Pointer, this.widget); root.clearPointerStyle(this.widget, this); } } } //# sourceMappingURL=ClickHelper.js.map