Event Guide
SableUI provides a safe event system that allows you to create interactive components with mouse, keyboard, and scroll input. Events can be attached directly to elements in the Layout() phase using inline callbacks, making it easy to build reactive UIs without complex custom even propagation logic.
Inline Events
SableUI provides simple callbacks that are element-specific, perfect for the common use cases.
Other events such as scrolling, all keyboard events, and non-standard mouse events are packaged alone with the
UIEventContext.
onClick
Triggered when the left mouse button is clicked on an element.
Div(onClick([this]() {
setCount(count + 1);
SableUI_Log("Clicked! Count: %d", count);
}))
{
Text(SableString::Fomrat("Click me, num clicks: %d", count),
textColour(255, 255, 255));
}
onSecondaryClick
Triggered when the right mouse button is clicked on an element.
Div(onSecondaryClick([this]() {
SableUI_Info("Right clicked");
}))
{
Text("Right-click me", textColour(200, 200, 200));
}
// TODO Can be paired alongside a context menu like this:
onDoubleClick
Triggered when an element is double-clicked within a short time window within an element.
Note: The double-click timing window is 300ms, and clicks must be within 5 pixels of each other to register as a double-click. Can be modified in
window.h.
onHover & onHoverExit
Triggered when the mouse pointer enters and exits the bounds of an element respectively.
Div(onHover([this]() {
setIsHovered(true);
}) onHoverExit([this]() {
setIsHovered(false);
}))
{
Text("Hover me", textColour(isHovered ? 100 : 200, 200, 200));
}
[!WARNING] State lambas can be dangerous and cause problems if used incorrectly, for example, reference lambdas (
[&]) can be unstable if used wrong. The best practice is to capturethisand other arguments by value, for example:onClick([this, otherVar1, otherVar2]() {});.
Keyboard Input
For keyboard input, SableUI provides access to the global event context through the OnUpdate() method.
Unlike mouse events which are element-specific, keyboard events are global, so it can be paired with
RectBoundingBox(Rect r, ivec2 p)wherercan be the rect ofrootElementandpcan bectx.mousePosfor only registering key presses when the cursor is hovering your component for example.
Accessing the Event Context
Override the OnUpdate() method in your component to access keyboard or other states:
class MyComponent : public SableUI::BaseComponent {
public:
void Layout() override {
// Your UI layout here
}
void OnUpdate(const UIEventContext& ctx) override {
// Handle keyboard input here
}
};
Key Constants
SableUI provides constexpr constants for all keyboard keys, borrowed from GLFW (the window manager) for easy translation. These constants follow the pattern SABLE_KEY_*:
A list of these keys can be grabbed in the file
events.h
Key State Queries
The UIEventContext provides three ways to query key states:
isKeyDown
Returns true every frame while the key is held down.
void OnUpdate(const UIEventContext& ctx) override {
if (ctx.isKeyDown.test(SABLE_KEY_W)) {
// Move forward continuously
posY -= speed * ctx.deltaTime;
MarkDirty();
}
if (ctx.isKeyDown.test(SABLE_KEY_S)) {
// Move backward continuously
posY += speed * ctx.deltaTime;
MarkDirty();
}
}
keyPressedEvent
Returns true only on the frame a key is pressed. Use for single actions.
void OnUpdate(const UIEventContext& ctx) override {
if (ctx.keyPressedEvent.test(SABLE_KEY_SPACE)) {
// Toggle state once per press
setIsPaused(!isPaused);
}
}
keyReleasedEvent
Returns teu only on the frame when a key is released.
void OnUpdate(const UIEventContext& ctx) override {
if (ctx.keyReleasedEvent.test(SABLE_KEY_LEFT_SHIFT)) {
// Stop running when shift is released
setIsRunning(false);
}
}
Modifer Keys
These tests can be paird amongst others to create key combination events.
void OnUpdate(const UIEventContext& ctx) override {
bool ctrlPressed = ctx.isKeyDown.test(SABLE_KEY_LEFT_CONTROL) ||
ctx.isKeyDown.test(SABLE_KEY_RIGHT_CONTROL);
// Ctrl+S for save
if (ctrlPressed && ctx.keyPressedEvent.test(SABLE_KEY_S)) {
Save();
}
}
Mouse Position and Scrolling
The UIEventContext also provides mouse position and scroll information:
Mouse Position
void OnUpdate(const UIEventContext& ctx) override {
int mouseX = ctx.mousePos.x;
int mouseY = ctx.mousePos.y;
// Mouse delta since last frame
int deltaX = ctx.mouseDelta.x;
int deltaY = ctx.mouseDelta.y;
SableUI_Log("Mouse pos: %dx%d, mouse delta: %dx%d",
mouseX, mouseY, deltaX, deltaY);
}
Scrolling
void OnUpdate(const UIEventContext& ctx) override {
float scrollX = ctx.scrollDelta.x;
float scrollY = ctx.scrollDelta.y;
if (scrollY != 0.0f) {
// Zoom in/out based on scroll
float zoomDelta = scrollY * 0.1f;
setZoomLevel(zoomLevel + zoomDelta);
}
}
Mouse Button State
Query mouse button states similarly to keyboard keys:
void OnUpdate(const UIEventContext& ctx) override {
// Check if left mouse button is held down
if (ctx.mouseDown.test(SABLE_MOUSE_BUTTON_LEFT)) {
// Drag operation
dragX += ctx.mouseDelta.x;
dragY += ctx.mouseDelta.y;
needsRerender = true;
}
// Check for mouse button press
if (ctx.mousePressed.test(SABLE_MOUSE_BUTTON_LEFT)) {
// Start drag
isDragging = true;
}
// Check for mouse button release
if (ctx.mouseReleased.test(SABLE_MOUSE_BUTTON_LEFT)) {
// End drag
setIsDragging(false);
}
}
Delta Time
The UIEventContext procides deltaTime for frame-independent animations and movement:
void OnUpdate(const UIEventContext& ctx) override {
if (ctx.isKeyDown.test(SABLE_KEY_RIGHT)) {
// Move at constant speed regardless of frame rate
posX += speed * ctx.deltaTime;
needsRerender = true;
}
}
deltaTime is in seconds, so if speed = 100.0f, the object moves at 100 pixels per second.
State Management
Learn useState and useRef for reactive components
Components
Understanding Layout(), OnUpdate(), and rendering
Examples
See event handling in real applications