lazy-widgets
Version:
Typescript retained mode GUI for the HTML canvas API
496 lines (495 loc) • 20.3 kB
TypeScript
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.
*
* Does nothing if the widget is inactive or doesn't belong to this UI root.
*/
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.
*
* Does nothing if the widget is inactive or doesn't belong to this UI root.
*/
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;
}