koya-ui
Object-oriented Window and Element wrappers over the raw Koya engine APIs.
Import #
import { Window, Element, createWindow, createElement } from '/rom/std/koya-ui.js';koya-ui also re-exports InputText, Button, ScrollView, and blurAll from koya-controls, so a single import can cover everything.
Window #
Wraps a compositor surface. Create with the async factory; the constructor is private.
const win = await Window.create({
role: 'overlay',
anchor: 'top-left',
title: 'My App',
size: { x: 800, y: 600 },
keyboardInteractivity: 'on_demand'
});
const el = await win.createElement({ renderable: { type: 'box', colour: [1, 0.5, 0, 1] } });
await win.attach(el);
await win.render();Window functions #
Window instance.options.role:'window' | 'bar' | 'overlay' | 'background' | 'lock' | 'raw'— Required surface role.options.title:string— Window title.options.appId:string— Application identifier for the window manager.options.size:{ x, y }— Initial dimensions in pixels.options.anchor:string— Layer-shell anchor string.options.offset:{ x, y }— Layer-shell offset.options.state:{ maximized?, fullscreen? }— Initial window state.options.keyboardInteractivity:'none' | 'exclusive' | 'on_demand'— Keyboard focus mode.options.acceptPointerEvents:boolean— Whether the surface accepts pointer input.options.deviceSelector:(request) => string | PromiseLike<string>— Per-window Vulkan device selection with a five-second deadline. Example:({ devices, preferredDeviceId }) => devices.find(d => d.type === 'discrete' && d.suitable)?.id ?? preferredDeviceId.
Window.create(options: WindowOptions): Promise<Window>window.render(): Promise<boolean>window.destroy(): Promise<boolean>window.startDrag(options: { data: Record<string, Uint8Array>, actions?: ('copy' | 'move')[] }): Promise<boolean>window.cancelDrag(): Promise<boolean>window.setDropTarget(options: DropTargetOptions | null): Promise<boolean>window.info(): Promise<object>window.elevate(): Promise<boolean>window.lower(): Promise<boolean>window.attach(element: Element | number): Promise<boolean>null if not found.window.getElementById(id: string | number): Promise<Element | null>Element.create(window, options). Returns an Element bound to this window.window.createElement(options?: object): Promise<Element>window.setTitle(value: string): Promise<boolean>window.setAppId(value: string): Promise<boolean>window.setFullscreen(value: boolean): Promise<boolean>window.setMaximized(value: boolean): Promise<boolean>{ x, y } object.window.setSize(value: { x, y }): Promise<boolean>'none', 'exclusive', or 'on_demand'.window.setKeyboardInteractivity(value: string): Promise<boolean>window.setPointerEvents(value: boolean): Promise<boolean>'pointer', 'text', 'default').window.setCursor(value: string): Promise<boolean>window.setRenderingEnabled(value: boolean): Promise<boolean>window.setClearColor(value: [r,g,b,a] | { r, g, b, a? }): Promise<boolean>Element #
Wraps a UI element with its internal numeric ID and provides named animation management, handler registration, and property setters. Create via the async factory or from an existing ID.
const box = await win.createElement({
renderable: { type: 'box', colour: [0.2, 0.6, 1.0, 1.0] },
item: { size: { x: 100, y: 100 } }
});
await win.attach(box);
await box.addAnimation('pulse', [
{ time: 0.0, scale: { x: 1, y: 1 } },
{ time: 0.5, scale: { x: 1.2, y: 1.2 }, ease: 'inOutQuad' },
{ time: 1.0, scale: { x: 1, y: 1 }, ease: 'inOutQuad', looping: true }
]);
await box.startAnimation('pulse');Element construction #
options is a bare number, wraps an existing element ID instead.Element.create(window: Window | number, options?: object): Promise<Element>Element instance without creating a new element.Element.fromId(window: Window | number, id: number): Promise<Element>Tree and lifecycle #
parent.element.attach(parent: Element | number): Promise<boolean>parent.element.detach(parent: Element | number): Promise<boolean>element.destroy(): Promise<boolean>element.startDrag(options: { data: Record<string, Uint8Array>, actions?: ('copy' | 'move')[] }): Promise<boolean>element.setDropTarget(options: DropTargetOptions | null): Promise<boolean>Queries #
element.getFrame(): Promise<{ x, y, w, h }>element.getRenderableSize(): Promise<{ x, y }>Properties #
element.setEnabled(value: boolean): Promise<boolean>element.setClipToBounds(value: boolean): Promise<boolean>element.setHitTarget(value: boolean): Promise<boolean>element.setPosition(value: { x, y }): Promise<boolean>element.setLayoutSize(value: { x, y }): Promise<boolean>'start', 'centre', 'end'.element.setContentAlign(value: { x?, y? }): Promise<boolean>Text #
element.setTextString(value: string): Promise<boolean>element.setTextSize(value: number): Promise<boolean>element.setTextLetterSpacing(value: number): Promise<boolean>element.setTextLineSpacing(value: number): Promise<boolean>element.setTextColour(value: [r,g,b,a]): Promise<boolean>[start, end) and returns a stable area ID. Newer overlapping areas take precedence.element.addColourArea(start: number, end: number, colour: Colour): Promise<number>element.listColourAreas(): Promise<Array<{ id, start, end, colour }>>element.deleteColourArea(areaId: number): Promise<boolean>element.setTextFont(value: string): Promise<boolean>'left', 'center', or 'right'.element.setTextJustify(value: string): Promise<boolean>'top', 'middle', or 'bottom'.element.setTextVerticalAlign(value: string): Promise<boolean>Circle #
element.setCircleColour(value: [r,g,b,a]): Promise<boolean>0 = filled, > 0 = ring.element.setCircleInset(value: number): Promise<boolean>element.setCircleStart(value: number): Promise<boolean>element.setCircleEnd(value: number): Promise<boolean>element.setCircleResolution(value: number): Promise<boolean>element.setCircleRect(value: { min: { x, y }, max: { x, y } }): Promise<boolean>Resources #
element.setPanelSlice(value: { top, right, bottom, left }): Promise<boolean>element.setPanelColour(value): Promise<boolean>null or falsy to clear.element.setTexture(value: string | null): Promise<boolean>element.clearTexture(): Promise<boolean>null or falsy to clear.element.setShader(value: string | null): Promise<boolean>element.clearShader(): Promise<boolean>element.setShaderParams(value: object): Promise<boolean>Mouse handlers #
Each setter accepts a callback or null to remove the handler. Assigning a handler automatically enables hitTarget on the element. Move callbacks receive (window, local), button callbacks receive (window, local, button), and scroll callbacks receive (window, local, delta).
Animations #
Element maintains a local name-to-ID registry so you can refer to animations by name instead of tracking numeric IDs.
name and returns the engine animation ID. If an onAnimationEnd callback was previously bound to this name, it is automatically wired up.element.addAnimation(name: string, frames: AnimationFrame[]): Promise<number>element.updateAnimation(nameOrId: string | number, frames: AnimationFrame[]): Promise<number>restart: false to skip restarting if the same animation is already running.element.startAnimation(nameOrId: string | number, restart?: boolean): Promise<number>element.stopAnimation(): Promise<void>null to remove. Returns the engine animation ID.element.onAnimationEnd(nameOrId: string | number, callback: function | null): Promise<number>-1 if unknown. Synchronous.element.getAnimationId(name: string): numbertrue if an animation with the given name has been added. Synchronous.element.hasAnimation(name: string): booleanelement.listAnimations(): string[]Convenience functions #
Top-level shortcuts exported alongside the classes.
Window.create(options).createWindow(options?: WindowOptions): Promise<Window>Element.create(window, options).createElement(window: Window | number, options?: object): Promise<Element>Interop with raw APIs #
Both Window and Element expose a numeric .id property that maps directly to the engine handle. You can pass these IDs to any Koya/Compositor or Helix/UserInterface call when you need functionality not yet wrapped by the standard library.
import * as UI from 'Helix/UserInterface';
import { Window } from '/rom/std/koya-ui.js';
const win = await Window.create({ role: 'window', size: { x: 400, y: 300 } });
// Use the raw API with the wrapper's ID
const metrics = await UI.measureText(win.id, someElement.id);Virtual input setters are setOnPress, setOnActivate, setOnHover, setOnDrag, setOnScroll, setOnLongPress, setOnDoubleActivate, setOnTransform and setInputOptions. See /helix-input for their shared event contract.
Native scrolling #
Element creation accepts scroll: true or scroll: {axis: "y"}. element.setScroll(options), element.scrollTo({x,y}), element.stopScroll(), and element.getScrollOffset() return promises and use the native engine. Native scrolling documents options, units, and lifecycle events. For an owned viewport/content pair use the ScrollView standard control.