UI Input

The UI framework routes key presses through shared Action objects. An action owns trigger keys, enablement rules, and the callback that runs when the action is accepted. Its user-facing name, detailed description, visibility, and display priority live in HelpData, which you access through action->help().

Actions are attached to surfaces and pages through Surface::actions(). The same ActionPtr can be attached to many surfaces, so application commands such as quit, save, or toggle-help can be implemented once and reused wherever they make sense.

Button surfaces use ButtonAction instead. A ButtonAction is owned by one live button and synchronizes its enabled state into that button’s SurfaceFlags. Create a separate ButtonAction for each button, even when several buttons call the same callback.

Important

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

Usage

Creating Shared Actions

Create an action, assign one or more keys, and attach it to a page or surface.

auto quitAction = ui::Action::create("quit");
quitAction->setKeys({Key::Escape, U'q'});
quitAction->help().setDescription("Quits the application.").setPriority(100);
quitAction->setTriggerFn([] {
    ui::getApplication().quit();
});

page->actions().add(quitAction);

When a key press reaches a surface, the default Surface::onKeyPress() asks the local action container to trigger the first enabled action that matches the key. If the matching action is disabled, dispatch continues so an action on a parent surface or the page may still handle the same key.

Focused Dispatch

The active page is the keyboard boundary. It sends a key press to the focused surface first, then each parent surface, and finally to the page itself. This gives focused controls the first chance to interpret keys without letting a transparent page accidentally leak input into the page below it.

auto saveAction = ui::Action::create("save");
saveAction->addKey(U's');
saveAction->setEnabledFn(
    [document] { return document->hasUnsavedChanges(); },
    ui::Action::EnabledUpdateMode::BeforeRepaint);
saveAction->setTriggerFn([document] {
    document->save();
});

editorPage->actions().add(quitAction);
editorPage->actions().add(saveAction);
detailsPanel->actions().add(saveAction);

The enabled callback is always refreshed immediately before triggering, even when the regular update mode is repaint-driven or polled. This avoids stale enabled state when data changes between two paint passes.

Main and Alternative Keys

Use Keys when an action has several accepted keys. The keys are stored once, in priority order. By default all keys are shown in compact footer help. When some bindings are only alternatives for detailed help, set the number of leading main keys.

auto zoomKeys = Keys{U'+', U']'};
zoomKeys.setMainKeyCount(1);

auto zoomInAction = ui::Action::create("zoom in");
zoomInAction->setKeys(zoomKeys);

The action still accepts both + and ]. The footer displays only [+] zoom in while a future detailed help view can show the alternative too.

Capturing Text Input

Text-entry controls should override onKeyPress() and mark ordinary text keys as handled. Page actions such as quit then do not run while the focused control is editing text.

void SearchField::onKeyPress(ui::KeyPressEvent &event) noexcept {
    if (event.key() == Key::Enter) {
        submit();
        event.setHandled();
        return;
    }
    if (event.key() == Key::Escape) {
        cancel();
        event.setHandled();
        return;
    }
    if (event.key().type() == Key::Character || event.key().type() == Key::Combined) {
        insertText(event.key().combined());
        event.setHandled();
        return;
    }
    Surface::onKeyPress(event);
}

Multi-Key Actions

When several keys perform related work, use one action and branch on the trigger key in the callback. This keeps dispatch simple and keeps the footer help focused on the one command the reader can perform.

auto panAction = ui::Action::create("pan");
panAction->setKeys({Key::Left, Key::Up, Key::Down, Key::Right});
panAction->setTriggerFn([view](const ui::ActionTriggerContext &context) {
    switch (context.key().type()) {
    case Key::Left:
        view->panLeft();
        break;
    case Key::Right:
        view->panRight();
        break;
    case Key::Up:
        view->panUp();
        break;
    case Key::Down:
        view->panDown();
        break;
    default:
        break;
    }
});

mapPage->actions().add(panAction);

Reference

class Action

A shared user-interface action with key triggers, help metadata, and enablement state.

Subclassed by erbsland::cterm::ui::ButtonAction

Public Types

enum class EnabledUpdateMode : uint8_t

When a callback-backed enabled state is refreshed automatically.

Values:

enumerator Manual

Only update when explicitly requested or before triggering.

enumerator BeforeTriggered

Update before key/manual triggering.

enumerator BeforeRepaint

Update before repaint-driven action help collection.

enumerator Polled100ms

Update at most every 100ms when action help is refreshed.

enum class EnabledRefreshReason : uint8_t

Why the enabled callback is being refreshed.

Values:

enumerator Manual

Explicit refresh.

enumerator BeforeTriggered

A trigger is about to be attempted.

enumerator BeforeRepaint

A repaint is about to display the action.

enumerator Polled100ms

The 100ms polling interval elapsed.

using EnabledFn = std::function<bool()>

Callback used to update the enabled state.

using TriggerFn = std::function<void(const ActionTriggerContext&)>

Callback invoked when the action is triggered.

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

Simple callback invoked when the action is triggered.

Public Functions

explicit Action(std::string name)

Create an action with a short help name.

Parameters:

name – The short action name used in generated help.

inline HelpData &help() noexcept

Get mutable help metadata.

inline const HelpData &help() const noexcept

Get immutable help metadata.

inline bool isEnabled() const noexcept

Test if this action is currently enabled.

void setEnabled(bool enabled) noexcept

Manually set the enabled state.

Parameters:

enabled – The new enabled state.

void setEnabledFn(EnabledFn enabledFn, EnabledUpdateMode updateMode = EnabledUpdateMode::BeforeTriggered)

Replace the enabled callback and update mode.

Parameters:
  • enabledFn – The callback that returns the current enabled state.

  • updateMode – The automatic update mode for the callback.

void clearEnabledFn() noexcept

Remove the enabled callback and return to manual enabled state.

inline EnabledUpdateMode enabledUpdateMode() const noexcept

Get the enabled update mode.

void setEnabledUpdateMode(EnabledUpdateMode updateMode) noexcept

Replace the enabled update mode.

Parameters:

updateMode – The new update mode.

inline const Keys &keys() const noexcept

Get the trigger keys.

void setKeys(Keys keys)

Replace all trigger keys.

Parameters:

keys – The new trigger keys.

void setTriggerFn(TriggerFn triggerFn)

Replace the trigger callback.

Parameters:

triggerFn – The new callback.

void setTriggerFn(SimpleTriggerFn triggerFn)

Replace the trigger callback with a simple no-argument callback.

Parameters:

triggerFn – The new callback.

void clearTriggerFn() noexcept

Remove the trigger callback.

bool matchesKey(const Key &key) const noexcept

Test if the action is triggered by a key.

Parameters:

key – The key to test.

Returns:

true if the key is assigned to the action.

bool refreshEnabled(EnabledRefreshReason reason)

Refresh the enabled state if the callback and update mode require it.

Parameters:

reason – The reason for the refresh.

Returns:

true if the enabled callback was evaluated.

bool trigger(const ActionTriggerContext &context)

Trigger this action manually.

The enabled callback is always evaluated before triggering when present.

Parameters:

context – The trigger context.

Returns:

true if the action was enabled and was accepted as triggered.

Public Static Functions

static ActionPtr create(std::string name)

Create an action with a short help name.

Parameters:

name – The short action name used in generated help.

Returns:

The new action.

class ButtonAction : public erbsland::cterm::ui::Action

An action that is owned by one button surface and synchronizes its enabled state.

Public Functions

explicit ButtonAction(std::string name)

Create a button action with a short help name.

Parameters:

name – The short action name used in generated help.

Public Static Functions

static ButtonActionPtr create(std::string name)

Create a button action with a short help name.

Parameters:

name – The short action name used in generated help.

Returns:

The new button action.

class HelpData

Display metadata for a user-interface action.

Public Functions

HelpData() = default

Create empty help data.

explicit HelpData(std::string name)

Create help data with a short display name.

Parameters:

name – The short display name.

inline const std::string &name() const noexcept

Get the short display name.

HelpData &setName(std::string name)

Set the short display name.

Parameters:

name – The short display name.

Returns:

This object.

inline const std::string &description() const noexcept

Get the detailed help description.

inline HelpFormat descriptionFormat() const noexcept

Get the format of the detailed help description.

HelpData &setDescription(std::string description, HelpFormat format = HelpFormat::Text)

Set the detailed help description.

Parameters:
  • description – The detailed help description.

  • format – The format of the detailed help description.

Returns:

This object.

inline int priority() const noexcept

Get the display priority.

HelpData &setPriority(int priority) noexcept

Set the display priority.

Parameters:

priority – The display priority; larger values are displayed first.

Returns:

This object.

inline HelpVisibility visibility() const noexcept

Get the help visibility.

HelpData &setVisibility(HelpVisibility visibility) noexcept

Set the help visibility.

Parameters:

visibility – The help visibility.

Returns:

This object.

bool isVisibleInFooter() const noexcept

Test if this metadata shall be shown in compact footer help.

bool isVisibleOnHelpPage() const noexcept

Test if this metadata shall be shown in detailed help.

enum class erbsland::cterm::ui::HelpVisibility : uint8_t

Visibility of action help metadata.

Values:

enumerator Hidden

Hide this action from generated help.

enumerator Footer

Show this action only in compact footer help.

enumerator HelpPage

Show this action only in detailed help.

enumerator All

Show this action in compact footer help and detailed help.

class Actions

Ordered action container attached to a surface or page.

Public Functions

explicit Actions(SurfaceWeakPtr owner) noexcept

Create an action container for a surface.

Parameters:

owner – The surface that owns this container.

void add(ActionPtr action)

Add an action to this container.

Parameters:

action – The action to attach.

Throws:

std::invalid_argument – If the action is null or already attached here.

void remove(const ActionPtr &action) noexcept

Remove an action from this container.

Parameters:

action – The action to remove.

void clear() noexcept

Remove all actions.

inline bool empty() const noexcept

Test if this container has no actions.

inline std::size_t size() const noexcept

Get the number of actions.

bool contains(const ActionPtr &action) const noexcept

Test if an action is already attached to this container.

void onKeyPress(KeyPressEvent &keyPressEvent)

Handle a key press event using the first enabled matching action.

Parameters:

keyPressEvent – The key press event to handle.

void refreshForRepaint()

Refresh repaint-driven action enablement.

class ActionTriggerContext

Context passed to an action when it is triggered.

Public Functions

explicit ActionTriggerContext(Key key, SurfaceWeakPtr sourceSurface = {}) noexcept

Create a trigger context.

Parameters:
  • key – The key that triggered the action.

  • sourceSurface – The surface whose action container triggered the action.

inline const Key &key() const noexcept

Get the key that triggered the action.

inline const SurfaceWeakPtr &sourceSurface() const noexcept

Get the surface whose action container triggered the action.