UNPKG

lazy-widgets

Version:

Typescript retained mode GUI for the HTML canvas API

492 lines (491 loc) 20.1 kB
import { CanvasViewport } from './CanvasViewport.js'; import { FocusType } from './FocusType.js'; import { Theme } from '../theme/Theme.js'; import { WidgetEvent } from '../events/WidgetEvent.js'; import type { PointerStyleHandler } from './PointerStyleHandler.js'; import type { LayoutConstraints } from './LayoutConstraints.js'; import { type TextInputHandler, type TextInputHandlerListener } from './TextInputHandler.js'; import type { Widget } from '../widgets/Widget.js'; import type { Driver } from './Driver.js'; import type { CaptureList } from './CaptureList.js'; import type { WidgetEventEmitter, WidgetEventListener } from '../events/WidgetEventEmitter.js'; import { type Rect } from '../helpers/Rect.js'; /** * Allowed cursor styles and in order of priority; lower indices have higher * priority */ export declare const ALLOWED_CURSOR_STYLES: string[]; /** * Optional Root constructor properties. * * @category Core */ export interface RootProperties { /** Sets {@link Root#pointerStyleHandler}. */ pointerStyleHandler?: PointerStyleHandler | null; /** Sets {@link Root#child}'s {@link Widget#inheritedTheme}. */ theme?: Theme; /** Sets {@link Root#resolution}. */ resolution?: number; /** Sets {@link Root#preventBleeding}. */ preventBleeding?: boolean; /** * Sets {@link CanvasViewport#preventAtlasBleeding} in the internal viewport * ({@link Root#viewport}). */ preventAtlasBleeding?: boolean; /** The starting width of the {@link Root#viewport}'s canvas. */ canvasStartingWidth?: number; /** The starting height of the {@link Root#viewport}'s canvas. */ canvasStartingHeight?: number; /** The starting layout constraints of the Root. */ constraints?: LayoutConstraints; /** Sets {@link Root#maxCanvasWidth}. */ maxCanvasWidth?: number; /** Sets {@link Root#maxCanvasHeight}. */ maxCanvasHeight?: number; /** Sets {@link Root#tabFocusable}. */ tabFocusable?: boolean; } /** * A Root is the parent of all widgets, but not a widget itself. It contains a * single child and manages dimensions and input handling * * @category Core */ export declare class Root implements WidgetEventEmitter { /** Typed user listeners attached to this Root */ private typedListeners; /** Untyped user listeners attached to this Root */ private untypedListeners; /** Next user listener ID */ private nextListener; /** A one-way map from an ID to a descendant Widget. Internal use only. */ private idMap; /** The internal viewport. Manages drawing */ protected viewport: CanvasViewport; /** The list of drivers registered to this root */ protected drivers: Set<Driver>; /** * Is the Root enabled? For internal use only. * * See {@link Root#enabled} */ protected _enabled: boolean; /** * For internal use only. Current value of {@link Root#pointerStyleHandler}. */ protected _pointerStyleHandler: PointerStyleHandler | null; /** * Current component foci (event targets for each focus type). * * For internal use only. * * See {@link Root#requestFocus}, {@link Root#dropFocus}, * {@link Root#clearFocus} and {@link Root#getFocus} */ protected _foci: Map<FocusType, Widget | null>; /** * Last capturer of each component focus (event targets for each focus * type). * * For internal use only. * * See {@link Root#getFocusCapturer} */ protected _fociCapturers: Map<FocusType, Widget | null>; /** * Text input handler constructor for environments where getting keyboard * input is hard, such as mobile and WebXR. If not null, widgets that need * text may call this to get strings and cursor positions as text is typed. * * See {@link Root#getTextInput} */ textInputHandler: (new (listener: TextInputHandlerListener) => TextInputHandler) | null; /** See {@link Root#currentTextInputHandler}. For internal use only. */ private _currentTextInputHandler; /** * Has the warning for poorly captured TabSelectEvent events been issued? */ private static badTabCaptureWarned; /** * The list of widgets that were hovered in the last check. Will be swapped * with {@link Root#hoveredWidgets} every time a pointer event is * dispatched. For internal use only. */ private lastHoveredWidgets; /** * The list of widgets that are currently hovered. Will be swapped with * {@link Root#lastHoveredWidgets} every time a pointer event is dispatched. * For internal use only. */ private hoveredWidgets; /** * The list of widgets that had a pointer focus but dropped it while * processing an event. For internal use only. */ private droppedPointerFoci; /** * Currently requested pointer styles. The list is ordered, where higher * priority pointer styles have a lower index than lower priority pointer * styles; the highest priority pointer style is always at index 0. For * internal use only. */ private requestedPointerStyles; /** * Helper list for {@link requestedPointerStyles} which contains the * respective requesters. For internal use only. */ private requestedPointerStyleSources; /** * Helper list for {@link requestedPointerStyles} which contains the * respective requesters' widget. For internal use only. */ private requestedPointerStyleWidgets; /** * For internal use only. Returns true if the child widget has been detached * from the root. */ private hasDetached; /** * Can tab controls be used in this UI root? True by default. Can only be * set once when the UI is created. */ readonly tabFocusable: boolean; constructor(child: Widget, properties?: Readonly<RootProperties>); /** * For internal use only. Sets the pointer style to 'default' if there is a * pointer style handler. */ private dropPointerStyleHandler; /** * For internal use only. Sets the pointer style to the highest priority * requested pointer style if there is a pointer style handler, and if there * is a requested pointer style. */ private restorePointerStyleHandler; /** * Pointer style handler, decides how to show the given pointer style. * Normally a function which sets the CSS cursor style of the Root's canvas */ get pointerStyleHandler(): PointerStyleHandler | null; set pointerStyleHandler(pointerStyleHandler: PointerStyleHandler | null); /** * Creates a new CanvasViewport instance for a new Root. Normally it * wouldn't make sense to separate this from the constructor, but this makes * viewport creation hookable, allowing for the creation of debug overlay * viewports. * * @internal * @returns Returns a new CanvasViewport (or child class instance) for the Root */ static makeViewport(child: Widget, properties?: Readonly<RootProperties>): CanvasViewport; /** The {@link Root#viewport}'s {@link Viewport#constraints | constraints} */ get constraints(): LayoutConstraints; set constraints(constraints: LayoutConstraints); /** * The {@link Root#viewport}'s * {@link CanvasViewport#canvasDimensions | canvasDimensions} */ get canvasDimensions(): [number, number]; /** * The {@link Root#child}'s {@link Widget#dimensions | dimensions} */ get dimensions(): [number, number]; /** * Is this root enabled? If not enabled, painting, updating or resolving * layout will do nothing. {@link Root#drivers | Drivers} will also be * notified by calling {@link Driver#onEnable} or {@link Driver#onDisable}, * pointer style will be reset and all {@link Root#_foci | foci} will be * cleared ({@link Root#clearFocus}). */ get enabled(): boolean; set enabled(newEnabled: boolean); /** * The {@link Root#viewport}'s {@link CanvasViewport#canvas | canvas}. The * canvas must not be modified directly; consider it output-only. */ get canvas(): HTMLCanvasElement; /** * The text input handler that is currently in use. `null` if none in use. * * See {@link Root#textInputHandler}. */ get currentTextInputHandler(): TextInputHandler | null; /** * Resolve the layout of this root. Does nothing if root is disabled. * * Calls {@link Root#viewport}'s {@link Viewport#resolveLayout} with * {@link Root#child} * * Call this before calling {@link Root#postLayoutUpdate} and after calling * {@link Root#preLayoutUpdate} * * @returns Returns true if the viewport was resized or re-scaled */ resolveLayout(): boolean; /** * Paint this root's next frame if needed. Does nothing if root is disabled. * * Calls {@link Root#viewport}'s {@link Viewport#paint} with * {@link Root#child}. * * Call this after calling {@link Root#postLayoutUpdate}. * * @returns Returns a list of dirty rectangles in the texture's coordinates, or null if the child widget was not repainted. Use this to tell an external 3D library whether to update a mesh's texture or not, and where to update the mesh's texture. */ paint(): null | Array<Rect>; /** * Dispatches a {@link WidgetEvent} to this root's {@link Root#child} by * calling {@link Widget#dispatchEvent}. Updates * {@link Root#_fociCapturers | foci capturers} and notifies * {@link Root#drivers} by calling {@link Driver#onFocusCapturerChanged} if * the capturer changes. Does nothing if root is disabled. * * Note that if an event with a focus is dispatched and no widget captures * the event due to the widget not existing anymore or being disabled, the * focus type of the event will be cleared in the root with * {@link Root#clearFocus}. * * Dispatching a single event can result in a chain of dispatched events. * These extra events will be returned. * * @returns Returns a list of dispatched events and whether they were captured. */ dispatchEvent(baseEvent: WidgetEvent): CaptureList; /** * Do a pre-layout update; calls {@link Root#drivers}' {@link Driver#update} * and {@link Root#child}'s {@link Widget#preLayoutUpdate}. Does nothing if * root is disabled. * * Call this before calling {@link Root#resolveLayout} */ preLayoutUpdate(): void; /** * Do a post-layout update; calls {@link Root#child}'s * {@link Widget#postLayoutUpdate}. Does nothing if root is disabled. * * Call this before calling {@link Root#paint} and after calling * {@link Root#resolveLayout} */ postLayoutUpdate(): void; /** * Internal method similar to {@link requestFocus}, except only a specific * focus is given; no partner foci are added. */ private giveFocus; /** * Sets the current {@link Root#_foci | focus} of a given type to a given * widget. If the focus changes, {@link Root#clearFocus} is called and * {@link Root#drivers} are notified by calling * {@link Driver#onFocusChanged}. * * If the target widget doesn't capture the dispatched {@link FocusEvent}, * then the focus is not changed. */ requestFocus(focusType: FocusType, widget: Widget): void; /** * Clears the current {@link Root#_foci | focus} of a given type if it is * currently set to a given widget. Achieved by calling * {@link Root#clearFocus}. */ dropFocus(focusType: FocusType, widget: Widget): void; /** * Clears all the {@link Root#_foci | foci} that are set to a given Widget. * Achieved by calling {@link Root#dropFocus} */ dropFoci(widget: Widget): void; /** * Clears the current {@link Root#_foci | focus} of a given type. If there * was a focus set, {@link Root#drivers} are notified by calling * {@link Driver#onFocusChanged}. */ clearFocus(focusType: FocusType): void; /** * Gets the current {@link Root#_foci | focus} of a given type. */ getFocus(focusType: FocusType): Widget | null; /** * Gets the last {@link Root#_fociCapturers | focus capturer} of a given * type. */ getFocusCapturer(focusType: FocusType): Widget | null; /** * Registers a {@link Driver} to the root, adding it to the * {@link Root#drivers} list and calling {@link Driver#onEnable}. If the * driver was already registered, nothing happens. */ registerDriver(driver: Driver): void; /** * Unregisters a {@link Driver} from the root, removing it from the * {@link Root#drivers} list and calling {@link Driver#onDisable}. If the * driver was not registered, nothing happens. */ unregisterDriver(driver: Driver): void; /** * Unregisters all {@link Root#drivers} from the root, by calling * {@link Root#unregisterDriver}. */ clearDrivers(): void; /** * Handle initialization of a text input handler. You probably don't need to * implement this method, unless you do something with the HTML elements * returned by the input handler (such as listening to focus or blur * events). */ protected handleTextInputHandlerShow(_handler: TextInputHandler): void; /** * Dispose all resources associated with text input handler. You probably * don't need to implement this method, unless you do something with the * HTML elements returned by the input handler (such as listening to focus * or blur events). */ protected handleTextInputHandlerDismiss(_handler: TextInputHandler): void; /** * Instantiate a text input handler. Used for mobile or WebXR where keyboard * events are hard to get. Note that this will replace the current handler * if there is any. * * @returns If {@link Root#textInputHandler} is set, returns a new instance, otherwise, returns null. */ getTextInput(listener: TextInputHandlerListener, initialInput?: string, selectStart?: number, selectEnd?: number): TextInputHandler | null; /** * Shortcut for {@link Root#viewport}'s {@link CanvasViewport#resolution} * property. * * Note that, although the resolution is part of the {@link CanvasViewport} * API, widgets will treat the resolution property as being per-Root, not * per-Viewport (hence the lack of a Viewport.resolution property). The * resolution property is part of the CanvasViewport class so that * CanvasViewport is not circularly dependent on the Root class. */ get resolution(): number; set resolution(resolution: number); /** * Shortcut for {@link Root#viewport}'s * {@link CanvasViewport#preventBleeding} property. */ get preventBleeding(): boolean; set preventBleeding(preventBleeding: boolean); /** * Shortcut for {@link Root#viewport}'s * {@link CanvasViewport#preventAtlasBleeding} property. */ get preventAtlasBleeding(): boolean; /** * Shortcut for {@link Root#viewport}'s * {@link CanvasViewport#maxCanvasWidth} property */ get maxCanvasWidth(): number; set maxCanvasWidth(maxCanvasWidth: number); /** * Shortcut for {@link Root#viewport}'s * {@link CanvasViewport#maxCanvasHeight} property */ get maxCanvasHeight(): number; set maxCanvasHeight(maxCanvasHeight: number); /** * Get the scale used for the {@link Root#viewport}. The horizontal and/or * vertical scale may not be 1 if {@link Root#maxCanvasWidth} or * {@link Root#maxCanvasHeight} are exceeded. * * Note that this is only valid after resolving {@link Root#child}'s layout. * * Equivalent to getting {@link Viewport#effectiveScale} on * {@link Root#viewport}. */ get effectiveScale(): [scaleX: number, scaleY: number]; /** * The root widget of this UI tree. Equivalent to getting * {@link Root#viewport}.{@link Viewport#child}. */ get child(): Widget; /** * Destroy this Root. Disables the Root, clears all drivers, detaches the * {@link Root#child} Widget and resets {@link Root#textInputHandler}. * * Root must not be used after calling this method. Doing so will cause * exceptions to be thrown. There is no way to un-destroy a destroyed Root. * * Call this if you are no longer going to use this Root. */ destroy(): void; /** * Listen to a specific event with a user listener. Chainable. * * Only events that pass through the Root will be listened; all trickling * events that start at the root will be listened, sticky events will only * be listened if they are dispatched at the Root, and bubbling events will * only be listened if none of the child widgets capture the event. * * @param eventType - The {@link WidgetEvent#"type"} to listen to * @param listener - The user-provided callback that will be invoked when the event is listened * @param once - Should the listener only be invoked once? False by default */ on(eventType: string, listener: WidgetEventListener, once?: boolean): this; /** * Similar to {@link Root#on}, but any event type invokes the user-provided * callback, the listener can't be invoked only once, and the listener is * called with a lower priority than specific event listeners. Chainable. * * @param listener - The user-provided callback that will be invoked when a event is listened */ onAny(listener: WidgetEventListener): this; /** * Remove an event listeners added with {@link Root#on}. Not chainable. * * @param eventType - The {@link WidgetEvent#"type"} to stop listening to * @param listener - The user-provided callback that was used in {@link Root#on} * @param once - Was the listener only meant to be invoked once? Must match what was used in {@link Root#on} */ off(eventType: string, listener: WidgetEventListener, once?: boolean): boolean; /** * Remove an event listeners added with {@link Root#onAny}. Not chainable. * * @param listener - The user-provided callback that was used in {@link Root#onAny} */ offAny(listener: WidgetEventListener): boolean; /** * Request that a specific ID is assigned to a specific {@link Widget} that * is attached to this Root. Must not be called manually; Widget will * automatically manage its ID when needed. * * @param id - The wanted ID * @param widget - The widget that the ID will be assigned to */ requestID(id: string, widget: Widget): void; /** * Stop assigning a specific widget ID. Must not be called manually. * * @param id - The ID to stop assigning */ dropID(id: string): void; /** * Get the widget that an ID is assigned to. If no widget is assigned to a * given ID, an error is thrown. * * @param id - The ID of the wanted {@link Widget} */ getWidgetByID(id: string): Widget; /** * Mark a widget as hovered (received a pointer event since the last check). * Widgets will call this method automatically, there is no need to manually * call this. */ markHovered(widget: Widget): void; private indexOfPointerStyle; /** * Request a pointer style. If the pointer style has a lower priority than * the current pointer style, it won't be displayed, but will still be * queued up in case the higher-priority style is cleared. */ requestPointerStyle(widget: Widget, pointerStyle: string, source?: unknown): void; /** * Stop requesting a pointer style. */ clearPointerStyle(widget: Widget, source?: unknown): void; /** * Stop requesting all pointer styles from a specific widget. */ clearPointerStylesFromWidget(widget: Widget): void; }