Skip to content

Events

events

Event Subscription Model

libwui implements a "flat subscription" model. Unlike traditional frameworks where events are passed down an inheritance chain, here the Window acts as a central dispatcher. Any object — be it a control, dialog, or business logic — can subscribe to the window's event stream.

Advantages

Decoupling from Hierarchy You don't need to inherit from wui::button to override on_click. You simply subscribe any method of any class to events of the desired type. This allows separating UI and logic (real MVVM/MVC without pain).

Targeted or Global Events - If the control parameter is passed — you receive events only for that control (clicks, focus). - If control == nullptr — you subscribe to system or internal window events (resize, theme change, hotkeys). This is the ideal place for adaptive layout logic.

Lifetime Management (Subscriber ID) The method returns a unique subscriber_id (string). This allows dynamic unsubscribing at any time without destroying the object. This is critical for "detachable" windows and dynamic interfaces.

Performance on Modern C++ Using std::function and std::shared_ptr ensures the callback is invoked as quickly and safely as possible.

Bitmask Event Types The event_type parameter is a bitmask. You can subscribe to mouse | keyboard | internal at once, reducing the number of callback registrations.

// Subscribe to keyboard events and system changes
std::string sub_id = window->subscribe([this](const wui::event& ev) {
    if (ev.type == wui::event_type::keyboard) {
        // Hotkey logic
    }
}, wui::event_type::keyboard | wui::event_type::system);

// When component is destroyed - just unsubscribe by ID
window->unsubscribe(sub_id);

Usage Example

// MainFrame subscribes to window resize
window->subscribe([this](const wui::event& ev) {
    if (ev.type == wui::event_type::internal && 
        ev.internal_event_.type == wui::internal_event_type::size_changed) {
        // Adaptive layout
        updateLayout(ev.internal_event_.x, ev.internal_event_.y);
    }
}, wui::event_type::internal);

// DialingDialog subscribes to cancel button
cancelButton = std::make_shared<wui::button>("Cancel", [this]() {
    Cancel();
});

Note: We abandoned the concept of virtual on_something handlers in favor of a unified window event bus. Now any component is just a subscriber. Want the sidebar to know about a key press in the text field? Just subscribe it. This turns a monolithic UI into a flexible constructor where components communicate via std::function without knowing anything about each other.

Basic API

The subscription model is used to handle events. The control or user code subscribes in the window to the events it needs and receives them. Events can be generated by the system or user code.

All events pass through the window. The following methods are used to work with them:

class window
{
public:
    ...
    std::string subscribe(std::function<void(const event&)> receive_callback, event_type event_types, std::shared_ptr<i_control> control = nullptr);
    void unsubscribe(const std::string &subscriber_id);

    void emit_event(int32_t x, int32_t y);
    ...
};

std::string subscribe(...)

Used to receive events from the window. The method returns a unique identifier of the subscriber which should be passed to unsubscribe() for unsubscribing.

  • receive_callback - function that receives events
  • event_types - bit mask indicating which events should be received
  • control - if this field is set, only mouse events occurring on this control will be received. and keyboard events if it has input focus. Otherwise, all events from the window will be received.

unsubscribe(const std::string &subscriber_id)

Used to unsubscribe from window events

  • subscriber_id is a unique identifier of the subscriber, given by the `subscribe method.

emit_event(int32_t x, int32_t y)

A method that generates an internal event. Used by custom code to generate its own events.

Event types

enum class event_type : uint32_t
{
    system = (1 << 0),
    mouse = (1 << 1),
    keyboard = (1 << 2),
    internal = (1 << 3),

    all = system | mouse | keyboard | internal
};

struct event
{
    event_type type;

    union
    {
        mouse_event mouse_event_;
        keyboard_event keyboard_event_;
        internal_event internal_event_;
        system_event system_event_;
    };
};

Depending on the type of event, the value from the event association is taken.

System events

enum class system_event_type
{
    device_change
};

struct system_event
{
    system_event_type type;
    int32_t x, y;
};

Mouse events

enum class mouse_event_type
{
    move,
    enter,
    leave,
    right_down,
    right_up,
    center_down,
    center_up,
    left_down,
    left_up,
    left_double,
    wheel
};

struct mouse_event
{
    mouse_event_type type;

    int32_t x, y;

    int32_t wheel_delta;
};

Keyboard events

enum class keyboard_event_type
{
    down, 
    up,
    key
};

struct keyboard_event
{
    keyboard_event_type type;

    uint8_t modifier; /// vk_capital or vk_shift or vk_alt or vk_insert
    char key[4];
    uint8_t key_size;
};

Internal events

enum class internal_event_type
{
    set_focus,
    remove_focus,
    execute_focused,

    size_changed,
    position_changed,

    user_emitted
};

struct internal_event
{
    internal_event_type type;
    int32_t x, y;
};

Internal Implementation: Event Dispatching via std::vector

In libwui, we abandoned heavy associative containers (std::map / std::unordered_map) in favor of a simple and efficient std::vector<subscriber>.

Advantages of std::vector for Performance

Cache Locality
On modern processors, cache misses are the main enemy of performance. Subscribers in a vector are stored in memory as a dense block. When iterating events, the processor pulls them into cache in whole chunks. In std::map, nodes are scattered across the heap, reducing performance on every mouse event.

Efficient Dispatching
The event dispatch loop to subscribers is maximally linear. The compiler can easily vectorize this loop using AVX2 instructions.

ID Management via Prefix
Although ID lookup in a vector is O(n), the number of subscribers per window rarely exceeds hundreds. At this scale, linear search through a vector is faster than computing a hash in unordered_map.

Note: We use std::vector to store subscribers. In modern C++, sequential memory access is orders of magnitude more important than theoretical algorithm complexity. Event dispatching in libwui is a memory scan with predictable access time, not pointer jumping through the heap.