UI Events and Scheduling

The UI runtime is built on a small, flexible event system that is also available as a public API. Most applications interact with it indirectly through Application, but you can use it directly when you need fine-grained control over event flow, timers, or background work.

This layer becomes especially useful when you:

  • integrate the UI into an existing event loop

  • schedule delayed or repeated actions

  • run background tasks without blocking the UI thread

Important

The UI Framework is currently in beta, and parts of the API may change in future releases.

Usage

Driving an Event Queue Manually

EventDriver manages the event queue and dispatches events to a single handler. Combine it with EventScheduler if you need time-based events.

using namespace erbsland::cterm::ui;

auto driver = EventDriver{};
driver.setEventHandler([](const Event &event) {
    if (event.type() == EventType::Resize) {
        // Recalculate layout here.
    }
});

auto scheduler = EventScheduler{driver};
scheduler.schedule(
    EventType::Resize,
    {},
    EventClock::now() + std::chrono::milliseconds{50});

while (driver.processEvents(std::chrono::milliseconds{10}).status
       != EventDriver::ProcessResult::Quit) {
    scheduler.poll();
}

This level of control is mainly useful for testing, embedding, or custom runtime integration. In typical applications, Application manages both the driver and the scheduler for you.

Scheduling Surface-Local UI Actions

Each Surface can lazily create its own Scheduler. All scheduled callbacks run on the UI thread, which makes them safe for modifying surface state.

auto notice = TextBox::create("Saving...", Alignment::Center);
auto action = notice->scheduler().addSingleShot(
    [weakNotice = SurfaceWeakPtr{notice}]() {
        if (const auto locked = weakNotice.lock()) {
            locked->flags().setPaintOutdated();
        }
    },
    std::chrono::milliseconds{750});

if (action.isValid()) {
    // The UI thread will execute the callback once the delay expires.
}

Use this mechanism for UI-related delays such as animations, status updates, or deferred rendering changes.

ScheduledActionRef acts as a handle for the scheduled task. You can use it to reschedule or cancel the action later without introducing your own identifier system.

Running Background Work with Cooperative Stop

EventThread runs callbacks on a dedicated worker thread. This allows you to perform longer-running tasks without blocking the UI.

Stoppable callbacks receive a StopToken, which lets them exit cooperatively when the application shuts down.

auto worker = getApplication().createEventThread();
worker->invoke([](StopToken stopToken) {
    while (!stopToken.stopRequested()) {
        // Perform one unit of work here.
    }
});

worker->quitAndWait(std::chrono::seconds{1});

Important: do not update UI surfaces directly from a worker thread. Instead, post results back to the UI thread using:

This keeps your UI consistent and avoids subtle race conditions.

Key and Quit Events

The framework currently exposes two primary event payload types:

Key events are routed through the focused surface chain, allowing each surface to handle or forward input.

Quit events stop the event loop gracefully and can carry the final exit code of your application.

Interface

enum class erbsland::cterm::ui::EventType : uint8_t

The event type.

Values:

enumerator NoEvent

Special flag for error handling.

enumerator Quit

A quit event. Stops a running event loop.

enumerator KeyPress

A key was pressed.

enumerator Resize

The terminal was resized.

enumerator ScheduledAction

A scheduled action event.

enumerator Invocation

A function was invoked.

class EventData

The base class for event data.

If an event has additional data, it is stored as a subclass of this class.

Subclassed by erbsland::cterm::ui::KeyPressEvent, erbsland::cterm::ui::QuitEvent, erbsland::cterm::ui::impl::InvocationEvent, erbsland::cterm::ui::impl::ScheduledActionEvent

Public Functions

inline virtual bool isHandled() const noexcept

Test if this event was handled and should not be propagated further.

inline void setHandled() noexcept

Mark this event as handled and should not be propagated further.

class Event

A single event.

Public Functions

inline explicit Event(const EventType type) noexcept

Create an event of a given type.

inline Event(const EventType type, EventDataUniquePtr data) noexcept

Create an event of a given type with optional data.

inline Event(Event &&other) noexcept

Move constructor.

inline Event &operator=(Event &&other) noexcept

Move assignment operator.

inline EventTime time() const noexcept

The event creation time.

inline EventType type() const noexcept

The event type.

inline const EventDataUniquePtr &data() const noexcept

Optional event data.

class EventDriver

Event driver for creating event-driven applications.

All methods in the event driver are thread-safe.

Public Types

enum class State : uint8_t

The state of the event driver.

Values:

enumerator Running
enumerator Quit

Public Functions

void addEvent(EventType eventType, EventDataUniquePtr &&eventData)

Add an event to the event loop.

void addEvent(Event &&event)

This is an overloaded member function, provided for convenience. It differs from the above function only in what argument(s) it accepts.

void setEventHandler(std::function<void(const Event&)> eventHandler)

Set the callback for queued non-control events.

Parameters:

eventHandler – The handler that receives all non-quit events.

bool hasPendingEvents() const

Test if events are pending in the queue.

Returns:

true if at least one event is queued.

ProcessResult run()

Run this event loop continuously.

This runs the event loop continuously until a quit event is received.

ProcessResult processEvents(std::chrono::milliseconds timeout)

Manually process events with a timeout.

A call of this method will process events in the event queue until the queue is empty or timeout is reached; whatever happens first.

Parameters:

timeout – The timeout for polling events. Zero = no timeout (not recommended).

Throws:

std::excepton – If an event throws an exception, the exception is rethrown.

ProcessResult processOneEvent()

Process at most one queued event.

Returns:

Processed if one regular event was handled, Idle if no event was queued, or Quit if the driver stopped.

void processEvent(const Event &event)

Process a single event.

void quit(int exitCode = 0)

Quit this event loop with the given exit code.

class ProcessResult

The result of processing events.

Public Types

enum Status

Processing status for one event loop step.

Values:

enumerator Idle
enumerator Processed
enumerator Timeout
enumerator Quit

Public Functions

inline explicit ProcessResult(const Status status = Idle, const int exitCode = 0) noexcept

Create one event-loop processing result.

Parameters:
  • status – The processing status.

  • exitCode – The quit exit code if processing stopped.

inline Status status() const noexcept

Access the processing status.

inline int exitCode() const noexcept

Access the quit exit code, meaningful when status() == Quit.

class EventScheduler

A scheduler for events.

Stores events until a given time and add them to the event driver.

  • thread-safe.

  • limited to a maximum of 10’000 scheduled events. If more events are added, they are discarded.

  • limited to a scheduling time of 0-12h.

Public Functions

inline explicit EventScheduler(EventDriver &driver)

Create a new scheduler using the given driver.

Parameters:

driver – The event driver to add events to.

void schedule(EventType eventType, EventDataUniquePtr &&data, EventTime eventTime)

Schedule an event.

Parameters:
  • eventType – The event type.

  • data – The event data.

  • eventTime – The time when the event should be scheduled.

void poll()

Poll the scheduler.

If an event is ready, it is removed from the scheduler and passed to the event driver.

void clear()

Remove all scheduled events.

std::optional<EventTime> nextEventTime() const

Get the time of the next scheduled event.

Returns:

The next scheduled event time, or an empty optional if no events are pending.

class Scheduler

A scheduler for UI actions.

Scheduler management methods are thread-safe. Scheduled callbacks are always executed on the application UI thread.

Public Types

using milliseconds = std::chrono::milliseconds

Duration type used for delays and repeat intervals.

using ActionFn = std::function<void()>

Callback type executed on the application UI thread.

Public Functions

explicit Scheduler(SurfaceWeakPtr surface)

Create a scheduler for one surface.

Parameters:

surface – The owning surface.

ScheduledActionRef addSingleShot(ActionFn &&action, milliseconds delay)

Schedules a single action with a delay.

Parameters:
  • action – The action to schedule.

  • delay – The delay before the action is executed.

Returns:

The reference of the scheduled action.

ScheduledActionRef addSingleShot(ActionFn &&action, ScheduledActionRef actionRef, milliseconds delay)

Schedules a single action with a delay.

This is either schedule a new action or move the execution time of an existing action.

Parameters:
  • action – The action to schedule.

  • actionRef – The reference of the action to reschedule if it exists.

  • delay – The delay before the action is executed.

Returns:

The reference of the scheduled action.

ScheduledActionRef addRepeated(ActionFn &&action, milliseconds interval)

Add a repeating action to the scheduler.

Parameters:
  • action – The action to schedule.

  • interval – The interval between executions.

Returns:

The reference of the scheduled action.

void setDelayOrInterval(ScheduledActionRef actionRef, milliseconds delayOrInterval)

Change the interval or delay of a scheduled action.

This resets the due time relative to the current time.

Parameters:
  • actionRef – The reference to the action. If the action does not exist, this call is ignored.

  • delayOrInterval – The new delay or interval for the action.

void remove(ScheduledActionRef actionRef)

Stop/Remove a scheduled action.

If the given reference does not exist, this call is ignored.

Parameters:

actionRef – The reference of the action to remove.

void removeAll()

Remove all scheduled actions.

Public Static Attributes

static constexpr auto cMaximumScheduledActions = std::size_t{1000}

Maximum number of scheduled actions tracked at once.

static constexpr auto cMinimumInterval = milliseconds{10}

Minimum supported delay or repeat interval.

static constexpr auto cMaximumInterval = std::chrono::duration_cast<milliseconds>(std::chrono::hours{12})

Maximum supported delay or repeat interval.

class ScheduledActionRef

Represents a scheduled action.

Public Functions

ScheduledActionRef() = default

Creates a “no scheduled action” reference.

inline bool isValid() const

Test if this scheduled action reference is valid.

inline std::size_t hash() const noexcept

Get a hash for this reference.

class StopToken

Cooperative stop token passed to stoppable UI callbacks.

Public Functions

StopToken() noexcept = default

Create an empty stop token that can never observe stop requests.

inline bool stopPossible() const noexcept

Test if this token is connected to a stop source.

Returns:

true if stop requests can be observed.

inline auto stopRequested() const noexcept

Test if a cooperative stop was requested.

Returns:

true if the associated stop source requested a stop.

class StopSource

Stop source used by the event system to request a cooperative shutdown.

Public Functions

inline StopSource()

Create a new stop source with its own shared stop state.

inline StopToken getToken() const noexcept

Create a token that observes this source.

Returns:

A stop token connected to this source.

inline bool stopPossible() const noexcept

Test if this source is connected to a shared stop state.

Returns:

true if stop requests are supported.

inline auto stopRequested() const noexcept

Test if a cooperative stop was already requested.

Returns:

true if requestStop() was called before.

inline bool requestStop() noexcept

Request a cooperative stop.

Returns:

true if this call changed the stop state.

class EventThread : public std::enable_shared_from_this<EventThread>

A thread that executes queued invocations via the UI event system.

All public methods are thread-safe.

Public Types

using milliseconds = std::chrono::milliseconds

Duration type used by the event thread API.

using InvocationFn = std::function<void()>

Callback type for invocations without stop support.

using StoppableInvocationFn = std::function<void(StopToken)>

Callback type for invocations that can observe cooperative stop requests.

Public Functions

explicit EventThread(ProtectedTag protectedTag) noexcept

Create an event thread instance.

Parameters:

protectedTag – Internal construction tag.

void invoke(InvocationFn fn)

Queue one invocation on the event thread.

If the thread is already stopping or finished, this call is ignored.

Parameters:

fn – The callback to execute.

void invoke(StoppableInvocationFn fn)

Queue one stoppable invocation on the event thread.

If the thread is already stopping or finished, this call is ignored.

Parameters:

fn – The callback to execute.

void invokeDelayed(InvocationFn fn, milliseconds delay)

Queue one invocation on the event thread after a delay.

If the thread is already stopping or finished, this call is ignored.

Parameters:
  • fn – The callback to execute.

  • delay – The delay before execution.

void invokeDelayed(StoppableInvocationFn fn, milliseconds delay)

Queue one stoppable invocation on the event thread after a delay.

If the thread is already stopping or finished, this call is ignored.

Parameters:
  • fn – The callback to execute.

  • delay – The delay before execution.

void quit()

Request a graceful shutdown.

Delayed work that is not yet due is cancelled immediately.

void abort()

Request an immediate shutdown.

Queued work that has not started yet is discarded.

bool waitForQuit(milliseconds timeout)

Wait for the thread to finish.

Parameters:

timeout – The maximum wait time.

Throws:
  • std::logic_error – If called from the event thread itself before it finished.

  • std::exception – Any stored callback exception after the thread finished.

Returns:

true if the thread finished before the timeout expired.

bool quitAndWait(milliseconds timeout)

Request graceful shutdown and wait for completion.

Parameters:

timeout – The maximum wait time.

Throws:
  • std::logic_error – If called from the event thread itself before it finished.

  • std::exception – Any stored callback exception after the thread finished.

Returns:

true if the thread finished before the timeout expired.

bool isRunning() const

Test if the worker thread is still running.

Returns:

true while the event loop is active.

bool isQuitRequested() const

Test if graceful shutdown was requested.

Returns:

true if quit() was called.

bool isAbortRequested() const

Test if immediate shutdown was requested.

Returns:

true if abort() was called.

bool isFinished() const

Test if the worker thread finished.

Returns:

true if the worker thread ended.

bool hasFailed() const

Test if the worker thread stored a failure.

Returns:

true if one callback threw an exception.

Public Static Functions

static EventThreadPtr create()

Create and start a new event thread.

Returns:

The started event thread.

class KeyPressEvent : public erbsland::cterm::ui::EventData

A key press event.

Public Functions

inline explicit KeyPressEvent(const Key &key)

Create a key press event for the given key.

Parameters:

key – The pressed key.

inline const Key &key() const noexcept

Get the pressed key.

class QuitEvent : public erbsland::cterm::ui::EventData

The data for a quit event.

Public Functions

QuitEvent() = default

Create a quit event with exit code 0.

inline explicit QuitEvent(const int exitCode)

Create a quit event with an explicit exit code.

Parameters:

exitCode – The requested application exit code.

inline int exitCode() const

Get the requested application exit code.

Returns:

The exit code.