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:
or a surface-local
Scheduler
This keeps your UI consistent and avoids subtle race conditions.
Key and Quit Events
The framework currently exposes two primary event payload types:
KeyPressEventfor user inputQuitEventfor terminating the application
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.
-
enumerator NoEvent
-
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
-
class Event
A single event.
-
class EventDriver
Event driver for creating event-driven applications.
All methods in the event driver are thread-safe.
Public Types
Public Functions
-
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:
trueif 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
timeoutis 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:
Processedif one regular event was handled,Idleif no event was queued, orQuitif the driver stopped.
-
void quit(int exitCode = 0)
Quit this event loop with the given exit code.
-
class ProcessResult
The result of processing events.
Public Types
-
void addEvent(Event &&event)
-
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.
-
using milliseconds = std::chrono::milliseconds
-
class ScheduledActionRef
Represents a scheduled action.
-
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:
trueif stop requests can be observed.
-
inline auto stopRequested() const noexcept
Test if a cooperative stop was requested.
- Returns:
trueif the associated stop source requested a stop.
-
StopToken() noexcept = default
-
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:
trueif stop requests are supported.
-
inline auto stopRequested() const noexcept
Test if a cooperative stop was already requested.
- Returns:
trueifrequestStop()was called before.
-
inline bool requestStop() noexcept
Request a cooperative stop.
- Returns:
trueif this call changed the stop state.
-
inline StopSource()
-
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.
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:
trueif 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:
trueif the thread finished before the timeout expired.
-
bool isRunning() const
Test if the worker thread is still running.
- Returns:
truewhile the event loop is active.
-
bool isQuitRequested() const
Test if graceful shutdown was requested.
- Returns:
trueifquit()was called.
-
bool isAbortRequested() const
Test if immediate shutdown was requested.
- Returns:
trueifabort()was called.
-
bool isFinished() const
Test if the worker thread finished.
- Returns:
trueif the worker thread ended.
-
bool hasFailed() const
Test if the worker thread stored a failure.
- Returns:
trueif one callback threw an exception.
Public Static Functions
-
static EventThreadPtr create()
Create and start a new event thread.
- Returns:
The started event thread.
-
using milliseconds = std::chrono::milliseconds
-
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.
-
QuitEvent() = default