UNPKG

lazy-widgets

Version:

Typescript retained mode GUI for the HTML canvas API

244 lines (243 loc) 10.8 kB
import { FocusType } from '../core/FocusType.js'; import type { Widget } from '../widgets/Widget.js'; import type { Driver } from '../core/Driver.js'; import type { Root } from '../core/Root.js'; import type { CaptureList } from '../core/CaptureList.js'; /** * A group of Roots. When a {@link TabSelectEvent} is not captured by a * {@link Root} in a group, the TabSelectEvent is carried over to the next (or * previous, depending on the direction) root in the group. Although * TabSelectEvent events are carried over between Roots in the same group, they * are **not** automatically carried over **between different groups**. * * This behaviour is useful for binding Roots to DOM elements. For example, * {@link DOMRoot | DOMRoots} should be placed in groups with a single DOMRoot, * since the DOMRoot owns its own DOM element, but Roots used for an external 3D * engine, where the roots share a single DOM element such as a canvas used as * the output for rendering, should all be in the same group. * * @category Driver */ export interface KeyboardDriverGroup { /** * The list of {@link Root | Roots} assigned to this group, in the order * they were added to this group. Not to be confused with * {@link KeyboardDriver#accessList}. */ roots: Array<Root>; /** * Similar to {@link KeyboardDriverGroup#roots}, but only contains enabled * {@link Root | Roots}. */ enabledRoots: Array<Root>; /** * Similar to {@link KeyboardDriverGroup#enabledRoots}, but only contains * enabled {@link Root | Roots} that are {@link Root#tabFocusable}. */ tabbableRoots: Array<Root>; /** * Should {@link TabSelectEvent} events wrap-around to the other end of the * group if not captured by the last (or first) {@link Root}? If this is * true, the navigation will be trapped to this group for keyboard-only * users, since that will be the only way to change keyboard focus. Useful * for 3D engines where all Roots share the same canvas. */ wrapsAround: boolean; } /** * Options used for creating a new {@link KeyboardDriverGroup}. * * @category Driver */ export interface KeyboardDriverGroupOptions { /** See {@link KeyboardDriverGroup#wrapsAround}. */ wrapsAround: boolean; } /** * A generic keyboard {@link Driver | driver}. * * Does nothing on its own, but provides an API for sending keyboard events to * registered roots. * * @category Driver */ export declare class KeyboardDriver<G extends KeyboardDriverGroup = KeyboardDriverGroup, O extends KeyboardDriverGroupOptions = KeyboardDriverGroupOptions> implements Driver { /** * Groups belonging to this driver. Roots in the same group transfer tab * selections between themselves. Do not modify from a child class. */ protected readonly groups: G[]; /** * A map from a Root to a group. Used only for optimisation purposes. Do not * modify from a child class. */ protected readonly groupMap: Map<Root, G>; /** * The list of {@link Root | Roots} that are using this driver, in the order * of access; the last focused (with any focus type) Root is moved to the * beginning of the list. * * Used as a fallback for {@link KeyboardDriver#focus}. */ private accessList; /** A set containing the keys currently down. */ private keysDown; /** The currently focused root. New keyboard events will go to this root */ private focus; /** * Changes the current {@link KeyboardDriver#focus | root focus}. * * If there was a previous root focus, that root's {@link Root#clearFocus} * is called with {@link FocusType#Keyboard}. * * {@link KeyboardDriver#keysDown} is cleared. */ protected changeFocusedRoot(root: Root | null): void; /** * Get the current {@link KeyboardDriver#focus | root focus}. * * @returns Returns {@link KeyboardDriver#focus} */ getFocusedRoot(): Root | null; /** * Similar to {@link KeyboardDriver#getFocusedRoot}, but can fall back to * the first root of {@link KeyboardDriver#accessList} if * {@link KeyboardDriver#focus} is null. */ getEffectiveFocusedRoot(): Root | null; /** * Clear the current {@link KeyboardDriver#focus | root focus}. Calls * {@link KeyboardDriver#changeFocusedRoot} with null. */ clearFocus(): void; /** * Dispatch a new {@link KeyPressEvent} event to the * {@link KeyboardDriver#getEffectiveFocusedRoot | effective focused Root}. * * @param key - Must follow the {@link https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key/Key_Values | KeyboardEvent.key} Web API. * @param shift - Is shift being pressed? * @param ctrl - Is control being pressed? * @param alt - Is alt being pressed? * @param virtual - Is the key down originating from a virtual keyboard? False by default * @returns Returns a list of dispatched events and whether they were captured. */ keyDown(key: string, shift: boolean, ctrl: boolean, alt: boolean, virtual?: boolean): CaptureList; /** * Dispatch a new {@link KeyReleaseEvent} event to the * {@link KeyboardDriver#getEffectiveFocusedRoot | effective focused Root}. * * @param key - Must follow the {@link https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key/Key_Values | KeyboardEvent.key} Web API. * @param shift - Is shift being pressed? * @param ctrl - Is control being pressed? * @param alt - Is alt being pressed? * @param virtual - Is the key up originating from a virtual keyboard? False by default * @returns Returns a list of dispatched events and whether they were captured. */ keyUp(key: string, shift: boolean, ctrl: boolean, alt: boolean, virtual?: boolean): CaptureList; /** * Calls {@link KeyboardDriver#keyDown} followed by * {@link KeyboardDriver#keyUp}. If the key was already down before calling * ({@link KeyboardDriver#isKeyDown}), keyUp is not called. * * @param key - Must follow the {@link https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key/Key_Values | KeyboardEvent.key} Web API. * @param shift - Is shift being pressed? * @param ctrl - Is control being pressed? * @param alt - Is alt being pressed? * @param virtual - Is the key press originating from a virtual keyboard? False by default * @returns Returns a list of dispatched events and whether they were captured. */ keyPress(key: string, shift: boolean, ctrl: boolean, alt: boolean, virtual?: boolean): CaptureList; /** * Check if a key is pressed. * * @param key - Must follow the {@link https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key/Key_Values | KeyboardEvent.key} Web API. * * @returns Returns true if key was in {@link KeyboardDriver#keysDown} */ isKeyDown(key: string): boolean; /** * Adds enabled root to {@link KeyboardDriver#accessList} and its respective * group. */ onEnable(root: Root): void; /** * Removes disabled root from {@link KeyboardDriver#accessList} and its * respective group. If the root was the {@link KeyboardDriver#focus}, then * {@link KeyboardDriver#clearFocus | the focus is cleared }. */ onDisable(root: Root): void; update(_root: Root): void; /** * Does nothing if the new focus type is not a {@link FocusType.Keyboard}. * If the focus comes from a root which is not the * {@link KeyboardDriver#focus | root focus}, then the root focus is * {@link KeyboardDriver#changeFocusedRoot | changed to the new root}. If * there is no new focused widget (the root's keyboard focus was cleared), * then nothing happens. * * If a {@link Root} becomes focused (with any focus type, not just keyboard * focus), it is moved to the beginning of the * {@link KeyboardDriver#accessList} list. * * This behaviour is confusing, however, it's required so that the keyboard * focus "lingers" for future tab key presses; this way, pressing tab can do * tab selection even when there is no widget that wants keyboard input. * When a focus is lingering, then it means that key events are still being * dispatched to the last focused root, but they don't have a target. This * way, most events get dropped, but tab key events are used for tab * selection. */ onFocusChanged(root: Root, focusType: FocusType, newFocus: Widget | null): void; onFocusCapturerChanged(_root: Root, _focusType: FocusType, _oldCapturer: Widget | null, _newCapturer: Widget | null): void; /** * Check if the currently focused root needs keyboard input. Virtual * keyboard should query this property to know when to show themselves. */ get needsInput(): boolean; /** * Dispatches an event to the currently focused root (or a fallback). * Handles wrap-around for tab selection. Internal use only. * * @param event - The event to dispatch */ private dispatchEvent; /** Get the index of a group in the groups list. For internal use only */ private getGroupIndex; /** * Get the group that a {@link Root} is assigned to. Throws an error if the * Root is not assigned to any group in this driver. * * @returns Returns the group assigned to this Root. The group is live, do not modify it directly. */ getGroup(root: Root): G; /** * Get a new group, with no {@link Root | Roots}. * * @returns Returns the created group. The group is live, do not modify it directly. */ createGroup(options: O): G; /** * Delete a group that is assigned to this keyboard. Throws an error if the * group is still in use (has assigned {@link Root | Roots}). */ deleteGroup(group: G): void; /** * Bind a {@link Root} to a group that is assigned to this keyboard. Throws * an error if the Root is already assigned to a group in this driver. */ bindRoot(root: Root, group: G): void; /** * Unbind a {@link Root} from its assigned group. Throws an error if the * Root is not assigned to any group in this driver. */ unbindGroup(root: Root): void; /** * Bind a {@link Root} to this keyboard, in a new group dedicated to the * Root. Equivalent to creating a new group and binding a Root to it. Useful * if you are using lazy-widgets directly in the DOM, where each Root has a * dedicated DOM element. * * @returns Returns the created group. The group is live, do not modify it directly. */ bindSingletRoot(root: Root, options: O): G; }