UI Interaction
Interactable
Marks a UI entity as interactive (receives pointer events). Required for all interactive widgets.
| Property | Type | Default | Description |
|---|---|---|---|
enabled | boolean | true | Whether interaction is active |
blockRaycast | boolean | true | Block events from reaching elements behind |
raycastTarget | boolean | true | Whether this entity is a valid raycast hit target |
UIInteraction
Automatically added by the plugin to entities with Interactable. Provides per-entity state:
| Property | Type | Description |
|---|---|---|
hovered | boolean | Pointer is over this entity |
pressed | boolean | Entity is being pressed |
justPressed | boolean | Press started this frame |
justReleased | boolean | Press released this frame |
UIEvents
The UIEvents resource collects interaction events each frame:
import { defineSystem, addSystem, Res } from 'esengine';import { UIEvents } from 'esengine';
addSystem(defineSystem( [Res(UIEvents)], (events) => { for (const e of events.query('click')) { console.log('Clicked:', e.entity); } }));Event Types
| Type | Description | Bubbles | Triggered By |
|---|---|---|---|
click | Pointer released on same entity where pressed | Yes | UIInteraction |
press | Pointer button pressed down | Yes | UIInteraction |
release | Pointer button released | Yes | UIInteraction |
hover_enter | Pointer entered entity bounds | No | UIInteraction |
hover_exit | Pointer left entity bounds | No | UIInteraction |
focus | Element received keyboard focus (click or Tab) | No | Focusable |
blur | Element lost keyboard focus | No | Focusable |
submit | Enter key pressed on single-line TextInput | No | TextInput |
change | Value changed on Slider, Toggle, TextInput, or Dropdown | No | Widget-specific |
drag_start | Pointer moved beyond drag threshold | No | Draggable |
drag_move | Pointer moved while dragging | No | Draggable |
drag_end | Pointer released after drag | No | Draggable |
scroll | ScrollView content scrolled | No | ScrollView |
UIEvent Structure
Each event object contains:
| Property | Type | Description |
|---|---|---|
entity | Entity | The entity that received the event |
type | string | Event type (see table above) |
target | Entity | Original entity that triggered the event |
currentTarget | Entity | Entity currently handling the event during bubbling |
Querying Events
// Query all events of a specific typefor (const e of events.query('click')) { console.log('Clicked:', e.entity);}
// Check if a specific entity has a specific eventif (events.hasEvent(buttonEntity, 'click')) { handleButton();}
// Drain all events (returns and clears the queue)const all = events.drain();Callback Subscriptions
Instead of polling events each frame, you can subscribe to events with callbacks using .on(). This is ideal for one-off setup in startup systems:
import { defineSystem, addStartupSystem, Res, GetWorld } from 'esengine';import { UIEvents, makeInteractable } from 'esengine';
addStartupSystem(defineSystem( [Res(UIEvents), GetWorld()], (events, world) => { const button = findEntityByName(world, 'StartButton');
// Entity-scoped: only fires for this entity const unsub = events.on(button, 'click', (e) => { console.log('Start button clicked!'); });
// Global: fires for any entity with this event type events.on('change', (e) => { console.log('Value changed on:', e.entity); });
// Call unsub() to remove the listener }));| Overload | Description |
|---|---|
events.on(entity, type, handler) | Subscribe to events on a specific entity. Returns Unsubscribe function |
events.on(type, handler) | Subscribe to all events of a type globally. Returns Unsubscribe function |
makeInteractable
Helper function to ensure an entity has the Interactable component with sensible defaults:
import { makeInteractable } from 'esengine';
makeInteractable(world, entity);// Equivalent to adding Interactable with { enabled: true, blockRaycast: true, raycastTarget: true }Use this when creating interactive entities from code — it avoids manually importing and inserting the Interactable component.
Event Bubbling
press, release, and click events bubble up the entity hierarchy. If a parent has Interactable with blockRaycast: true, bubbling stops there. hover_enter and hover_exit do not bubble.
Focusable
Enables keyboard focus and Tab navigation.
| Property | Type | Default | Description |
|---|---|---|---|
tabIndex | number | 0 | Focus order (lower = earlier) |
isFocused | boolean | false | Current focus state |
Draggable
Enables drag-to-move on a UI element.
| Property | Type | Default | Description |
|---|---|---|---|
enabled | boolean | true | Enable dragging |
dragThreshold | number | 5 | Pixels before drag starts |
lockX | boolean | false | Lock horizontal movement |
lockY | boolean | false | Lock vertical movement |
constraintMin | {x, y} | null | null | Minimum position constraint |
constraintMax | {x, y} | null | null | Maximum position constraint |
Draggable supports both Transform-based and UIRect-based (anchored) elements. When an entity has UIRect, dragging adjusts the layout offsets; otherwise it moves the Transform position directly.
When dragging is active, the engine adds a DragState component to the entity:
| Property | Type | Description |
|---|---|---|
isDragging | boolean | Currently dragging |
startWorldPos | {x, y} | Entity position when drag started |
pointerStartWorld | {x, y} | Pointer position in world space when drag started |
currentWorldPos | {x, y} | Current world position |
deltaWorld | {x, y} | Movement this frame |
totalDeltaWorld | {x, y} | Total movement since drag start |
Default Tinting
When a Button or Toggle has no explicit transition, the UI system applies automatic state-based tinting to the entity’s Sprite or Image color:
| State | Effect |
|---|---|
| Normal | No change |
| Hovered | 1.15x brightness |
| Pressed | 0.75x brightness |
| Disabled | 0.5x brightness, 0.6 alpha |
This provides visual feedback without requiring explicit ColorTransition definitions.
Processing Pipeline
UI interaction systems run in a fixed order each frame:
| Schedule | System | Purpose |
|---|---|---|
| Last (prev frame) | InputPlugin | Clears one-frame input state |
| PreUpdate | UIInteractionSystem | Hit test, hover, press, release, click |
| PreUpdate | DragPlugin | Drag state tracking, drag events |
| Update | ButtonSystem, TogglePlugin, SliderPlugin, etc. | Widget state updates |
Next Steps
- Widgets — buttons, sliders, toggles, and more
- UI Layout — anchors, flex, and grid layout
- Input Handling — keyboard, mouse, and touch input