pub enum UiEvent {
Show 43 variants
Accelerator(AcceleratorId, Modifiers),
KeyPressed {
key: Key,
modifiers: Modifiers,
repeat: bool,
},
CharTyped(char),
MouseDown {
widget: Option<WidgetId>,
button: MouseButton,
position: Point,
modifiers: Modifiers,
},
MouseUp {
widget: Option<WidgetId>,
button: MouseButton,
position: Point,
},
MouseMoved {
position: Point,
buttons: ButtonMask,
},
MouseEntered {
widget: WidgetId,
},
MouseLeft {
widget: WidgetId,
},
DoubleClick {
widget: Option<WidgetId>,
position: Point,
},
Scroll {
widget: Option<WidgetId>,
delta: ScrollDelta,
position: Point,
},
WindowResized {
viewport: Viewport,
},
WindowClose,
WindowFocused(bool),
DpiChanged(f32),
WindowStateChanged {
maximized: bool,
fullscreen: bool,
minimized: bool,
},
FilesDropped {
paths: Vec<PathBuf>,
position: Point,
},
ClipboardPaste(String),
OpenRequested {
urls: Vec<String>,
files: Vec<PathBuf>,
},
TextCopied(String),
FocusChanged(Option<WidgetId>),
SystemThemeChanged(SystemTheme),
DisplaysChanged,
ScrollOffsetChanged {
widget: WidgetId,
new_offset: usize,
},
TextSelectionChanged {
region: WidgetId,
anchor: Point,
focus: Point,
},
SplitDividerDragged {
tree: WidgetId,
split_index: usize,
new_ratio: f32,
},
MenuActivated(WidgetId),
ContextMenuItemActivated(WidgetId),
ContextMenuDismissed,
TrayClicked {
button: MouseButton,
},
NotificationActivated {
tag: Option<String>,
action: Option<WidgetId>,
},
Tree(WidgetId, TreeEvent),
List(WidgetId, ListViewEvent),
Form(WidgetId, FormEvent),
Palette(WidgetId, PaletteEvent),
TabBar(WidgetId, TabBarEvent),
StatusBar(WidgetId, StatusBarEvent),
ActivityBar(WidgetId, ActivityBarEvent),
Terminal(WidgetId, TerminalEvent),
TextDisplay(WidgetId, TextDisplayEvent),
Chart(WidgetId, ChartEvent),
DataTable(WidgetId, DataTableEvent),
BackendNative(BackendNativeEvent),
User(UserPayload),
}Expand description
Everything a user (or platform) can do that an app might care about.
Produced by Backend::poll_events every
frame; consumed by app dispatch code.
Variants§
Accelerator(AcceleratorId, Modifiers)
A declared accelerator fired. The backend matched a
KeyBinding to one of the app’s registered
accelerators and is reporting the ID.
KeyPressed
A raw key was pressed. Routes to the focused widget. Apps that only
want keybindings should prefer Accelerator — the backend handles
match-and-dispatch for them.
CharTyped(char)
An IME committed the composed text for one character. Routes
to the focused text-input widget, same as Self::KeyPressed.
This is reserved exclusively for the output of an IME
composition sequence (dead-key accents, Japanese/Chinese/Korean
input, emoji picker commit) — text a user produced through
multiple keystrokes/UI interactions that resolve to one commit,
which has no sensible KeyPressed translation of its own.
A future IME backend must not emit both CharTyped and
KeyPressed{Key::Char} for the same keystroke: real IME
composition consumes the raw keydown while a composition is in
progress (nothing reaches KeyPressed for it), and only the
final commit produces CharTyped. Emitting both would double-
insert on any consumer that (correctly, per its own doc) listens
to both.
MouseDown
MouseUp
MouseMoved
MouseEntered
Optional capability — hover state is a real, likely-future desktop need (tooltip auto-show, hover highlighting) that has no substitute mechanism. A backend that never emits this is fully conformant.
MouseLeft
See Self::MouseEntered — same optional-capability status,
same rationale, always emitted/not-emitted as a pair.
DoubleClick
Scroll
Scroll-wheel event. Routes to the widget under the cursor, not to the focused widget. This is the native convention on every major desktop platform.
WindowResized
WindowClose
Required capability on every windowed (non-TUI) backend —
the app’s only hook to observe or veto an OS-level window close
(the “×” button, Alt-F4, window-manager close). Not applicable
to TUI: a terminal has no OS window to close independently of
the process exiting. The app’s Reaction gates whether the close
proceeds: Exit allows it, anything else vetoes it.
WindowFocused(bool)
DpiChanged(f32)
Optional capability — Win emits this from WM_DPICHANGED;
GTK observes the DrawingArea’s notify::scale-factor signal
and macOS observes NSWindowDidChangeBackingPropertiesNotification,
both firing on a live runtime DPI change (monitor move, external
monitor plug/unplug) as well as at startup; TUI is always
scale == 1.0, so it never applies there. crate::runtime::preprocess_event
forces EventOutcome::Redraw for this variant regardless of what
AppLogic::handle itself returns, so a repaint (and therefore a
re-measure of any DPI-dependent layout) always follows receipt.
WindowStateChanged
Optional capability — the OS toggled one of the window’s tri-state
chrome flags (maximize/restore, fullscreen enter/exit,
minimize/restore) other than through [Backend::toggle_window_maximize]
or WindowControl::set_fullscreen
themselves — a window-manager keyboard shortcut, a title-bar
double-click GTK/macOS handle natively, Cmd+Ctrl+F, etc. Backends
that wire this connect to their native “state changed” signal
and push this with the window’s fresh tri-state snapshot; apps use
it to keep their own “is maximized” UI in sync with state the user
changed outside the app’s own controls. Not applicable to TUI: a
terminal has no OS-level maximize/fullscreen/minimize state
distinct from the alt-screen it already occupies.
FilesDropped
Optional capability — emitted by GTK (gtk::DropTarget on the
DrawingArea), macOS (NSDraggingDestination on QuadraView),
and Windows (WM_DROPFILES) when the user drops OS files onto
the window. Not applicable to TUI: a terminal has no native
drag-and-drop surface. position is in the same native units as
every other pointer event (DIPs for GTK/macOS/Win).
ClipboardPaste(String)
OpenRequested
The OS asked this app to open one or more URLs and/or files. Emitted in two situations that apps handle identically:
- At startup, decoded from
argv(amyapp://...URL or a document path the OS launched the process with — “open with”, a registered URL scheme handler, an OAuth callback redirect). - At runtime, when [
RunConfig::single_instance] is enabled and a second launch of the same app hands its request to the already-running instance instead of starting a new process.
urls and files are populated independently — a single launch
can carry either, both, or (rarely) neither. Deliberately String
rather than a parsed URL type: every value here is copied verbatim
from whatever the OS handed the process with no validation.
files uses PathBuf — same convention as Self::FilesDropped.
Registering as the OS handler for a scheme or extension is a packaging concern — quadraui does not do this for you. This variant only covers the runtime half: receiving the event once the OS has already routed it here.
Routing: broadcast — there is no widget to hit-test or focus to route through.
$ grep -rn 'OpenRequested' ~/src/coord-tui/src ~/src/vimcode/src
(no output — zero hits in both)Zero hits means neither consumer references this variant yet, but
the real question for a new enum variant is whether either
consumer’s existing matches over UiEvent are exhaustive (no
wildcard arm), since those would need to add one:
vimcode’stui_main/shell_app.rs::handle()top-level dispatch (match event { UiEvent::KeyPressed { .. } => .., .., _ => Reaction::Continue }) already ends in a wildcard arm.coord-tui’sapp/events.rs::dispatch_handle()top-level dispatch (match &event { UiEvent::KeyPressed { .. } => .., .., _ => {} }) likewise already ends in a wildcard arm.
Every other UiEvent match in both consumers is either an if let on one specific variant or itself wildcard-terminated. Adding
OpenRequested compiles clean against both develop-tip
checkouts with no consumer changes required.
TextCopied(String)
Text was copied to the clipboard by the TUI runner’s built-in text-selection mechanism (click-drag → Ctrl-C). The payload is the text that was copied.
This event is distinct from Self::ClipboardPaste. Paste
means “insert text into the focused input”; TextCopied is a
one-way notification that lets apps display a confirmation badge
(“Copied!”) without any risk of accidentally inserting text into
an input widget that happens to handle ClipboardPaste.
Routing: broadcast (no widget target — it is not input-focus routed). Apps that don’t need copy confirmation can ignore it.
FocusChanged(Option<WidgetId>)
Keyboard focus moved (or was cleared).
Emitted by the shared runner pipeline
([crate::runtime::preprocess_event]) whenever
crate::focus::FocusManager::focus_next/focus_prev (driven
by Tab/Shift+Tab) or crate::focus::FocusManager::set_focus
actually changes crate::Backend::focus_manager’s
crate::focus::FocusManager::focused value. None means focus
was cleared — no widget focused. A no-op cycle (e.g. Tab in a
single-widget tab order) does not re-fire this event.
Routing: broadcast — apps read the new value directly off the
payload (or off crate::Backend::focus_manager at any later
point) rather than this being routed to a specific widget’s
handler. Only fires for apps that opt in by returning a
non-empty crate::runner::AppLogic::tab_stops.
SystemThemeChanged(SystemTheme)
The OS-level light/dark/accent/high-contrast preference changed
while the app was running — the live counterpart to
PlatformServices::system_theme’s
poll-on-demand query. Payload is the new value; apps that want the
theme at startup call system_theme() once instead of waiting for
this to fire.
No backend emits this yet. Same “declare the gap, don’t fake
it” posture as Self::WindowStateChanged (also zero producers
today): the variant exists so app code and the event vocabulary are
ready before a backend wires the native change notification (GTK’s
Settings::connect_gtk_application_prefer_dark_theme_notify,
macOS KVO on effectiveAppearance, Win’s
WM_SETTINGCHANGE("ImmersiveColorSet")) into it. TUI has no
equivalent OS notification at all — a terminal can’t push, only
answer a query — so it will never emit this variant.
Routing: broadcast, like Self::TextCopied/Self::FocusChanged.
§Downstream impact (CLAUDE.md rule 8)
UiEvent is not #[non_exhaustive], so a consumer that
exhaustively matches every variant with no wildcard arm would
fail to compile the moment this variant landed. Blast-radius grep,
per CLAUDE.md’s mandatory rule 1 (this covers the new
PlatformServices::system_theme method and SystemTheme struct
as well as this variant — all three landed together):
$ grep -rn 'SystemThemeChanged\|SystemTheme\|system_theme' ~/src/coord-tui/src ~/src/vimcode/src
(no output — zero hits in both)Zero hits means neither consumer references any of the three new
items yet, but the real question for a new enum variant is
whether either consumer’s existing matches over UiEvent are
exhaustive (no wildcard arm), since those would need to add one:
vimcode’stui_main/shell_app.rstop-levelUiEventdispatch (shell_app.rs:2403,2689) already ends in a wildcard_ =>arm.coord-tui’sapp/events.rstop-levelUiEventdispatch (events.rs:1390,3973) likewise already ends in a wildcard_ =>arm.
Every other UiEvent match in both consumers is either an if let on one specific variant or itself wildcard-terminated. Adding
SystemThemeChanged compiles clean against both develop-tip
checkouts with no consumer changes required.
DisplaysChanged
The set of connected displays changed — a monitor was plugged in,
unplugged, or its resolution/DPI/work-area changed — while the app
was running. No payload: apps that care call
PlatformServices::displays
again to get the fresh list, the same “event is a poke, not a
snapshot” shape crate::UiEvent::FocusChanged uses for a
pointer-sized payload instead of duplicating state that already has
a getter. DpiChanged stays the narrower, already-wired live
signal for “this window’s own backing scale changed”; this variant
is the broader “the whole display topology moved” one it doesn’t
cover (a monitor unplugged behind the window, say, with the
window’s own scale unaffected).
No backend emits this yet. Same “declare the gap, don’t fake
it” posture as Self::SystemThemeChanged/Self::WindowStateChanged
(also zero producers today): the variant exists so app code and the
event vocabulary are ready before a backend wires the native
change notification (GTK’s gdk::Display::monitors
items-changed signal on the returned gio::ListModel, macOS’s
NSApplicationDidChangeScreenParametersNotification, Win’s
WM_DISPLAYCHANGE) into it. TUI has no equivalent OS notification
— a terminal resize arrives as Self::WindowResized, not a
display-topology change — so it will never emit this variant.
Routing: broadcast, like Self::TextCopied/Self::FocusChanged.
§Downstream impact (CLAUDE.md rule 8)
UiEvent is not #[non_exhaustive], so a consumer that
exhaustively matches every variant with no wildcard arm would
fail to compile the moment this variant landed. Blast-radius grep,
per CLAUDE.md’s mandatory rule 1 (this covers the new
PlatformServices::displays/cursor_screen_point methods and the
Display struct as well as this variant — all landed together):
$ grep -rn 'DisplaysChanged\|PlatformServices::displays\|cursor_screen_point\|backend::Display\b' \
~/src/coord-tui/src ~/src/vimcode/src
(no output — zero hits in both)Zero hits means neither consumer references this variant yet, but
the real question for a new enum variant is whether either
consumer’s existing matches over UiEvent are exhaustive
(no wildcard arm). Both top-level UiEvent dispatches are large
multi-hundred-line matches that already end in a wildcard _ =>
arm. Adding DisplaysChanged compiles clean with no consumer
changes required.
ScrollOffsetChanged
A scrollbar drag or click resolved to a new offset. Generic
over widget type — the widget field carries whatever
WidgetId the app used when calling
crate::DragState::begin with
crate::DragTarget::ScrollbarY. Apps dispatch on widget
and apply new_offset to the corresponding scroll-state
field (palette scroll_offset, tree scroll_top, etc.).
Replaces the old per-primitive ScrollOffsetChanged variants.
TextSelectionChanged
A mouse text-selection drag extended or finalised its range.
Emitted by crate::dispatch::dispatch_mouse_drag while a
crate::DragTarget::TextSelection is active.
anchor is the screen position where the drag started (set
once at click-down; does not change during the drag). focus
is the current cursor position. Both are in the backend’s
native units (TUI: cells; GTK/macOS: pixels).
The range anchor → focus defines what is currently selected —
call crate::dispatch::text_selection_line_range to convert
the pair to a list of (row, col_start, col_end) spans.
The TUI backend applies selection highlights automatically
inside the draw loop; other backends may consume this event to
drive native selection highlight.
If the user releases without moving (plain click), this event
is never emitted; the app receives a plain MouseDown + MouseUp
pair instead.
SplitDividerDragged
A crate::SplitTree divider drag moved. Emitted by
crate::dispatch::dispatch_mouse_drag while a
crate::DragTarget::SplitDivider is active.
tree identifies which tree (apps may host more than one — e.g.
vimcode’s editor-group tree and per-group window tree); apps
dispatch on it and apply the update to the corresponding
SplitTree state. split_index is the pre-order index of the
SplitTree::Split node being resized (see
crate::SplitTreeDivider::split_index). new_ratio is the
resolved, already-clamped 0.0..=1.0 ratio ready to feed
directly to crate::SplitTree::set_ratio_at_index.
MenuActivated(WidgetId)
A menu item was activated via a native menu installer
(Backend::install_menu_bar).
Carries the activated item’s WidgetId regardless of nesting
depth — submenu structure is transparent to the app.
The in-window MenuBar primitive resolves clicks via
crate::MenuBarLayout::hit_test / crate::MenuBarHit::Item
instead; this variant is specifically for system-installed menus
(macOS NSMenu; future Win32 SetMenu).
ContextMenuItemActivated(WidgetId)
A native right-click context menu (shown via
Backend::show_context_menu)
had one of its items activated. Carries the activated item’s
WidgetId. Distinct from Self::MenuActivated so apps can
route menu-bar and context-menu activations through different
handlers when needed.
ContextMenuDismissed
A native right-click context menu was dismissed — either after
activation (immediately following the matching
Self::ContextMenuItemActivated) or by cancel (Escape /
click outside / second right-click). Apps that don’t track
open-menu state can ignore this variant.
TrayClicked
The user clicked the app’s tray/status-bar icon
(crate::backend::TrayService::set_icon) with no menu currently
intercepting the click. Carries which mouse button was pressed —
tray icons commonly bind left-click to “show/focus the main
window” and right-click to something else, so apps need the
distinction the way Self::MouseDown already carries one.
Does not fire while a menu is attached via
crate::backend::TrayService::set_menu on every backend that
implements one today — clicking (either button) opens the menu
instead, and menu selection arrives as
Self::ContextMenuItemActivated, the same variant a right-click
context menu produces (see each backend’s impl TrayService doc,
e.g. macos::tray’s module doc, for the native platform
behaviour this mirrors). Detach the menu (an empty
ContextMenu) to
go back to receiving this variant.
Routing: broadcast, like Self::TextCopied — a tray icon has no
WidgetId of its own to route through.
Not applicable to TUI: crate::backend::Backend::tray is
permanently None there (a terminal has no notification-area
concept — see that method’s doc), so TuiBackend never produces
this variant.
Fields
NotificationActivated
The user activated a system notification sent through
crate::backend::PlatformServices::send_notification — either
the notification body itself (action: None) or one of the
action buttons registered on it via
crate::backend::Notification::with_action (action: Some(id), the same WidgetId passed to that call).
tag carries crate::backend::Notification::tag’s value
through unchanged, so an app that fired several tagged
notifications can tell which one this activation belongs to
without inventing its own out-of-band id scheme. None when the
firing Notification carried no tag.
Not every backend can produce this. Only GTK has a native
click-through channel today — gio::Notification’s action/
default-action GActions, wired in gtk::services (see that
module’s doc). macOS’s unbundled osascript fallback and Win’s
Shell_NotifyIconW balloon fallback have no click-through
mechanism to report through and honestly never emit this variant
— see each backend’s send_notification doc for what a real
implementation there (UNUserNotificationCenter, WinRT
ToastNotificationManager) would need. TUI (and any other
backend with BackendCaps::notifications == false) degrades
notifications to the in-canvas Toast primitive instead (see
compose::notification::notify_or_toast); a toast’s own action
button click resolves through
crate::primitives::toast::ToastStackLayout::hit_test /
crate::primitives::toast::ToastHit from an ordinary
MouseDown/MouseUp, not through this variant.
Routing: broadcast, like Self::TrayClicked — a notification
has no WidgetId of its own to route through (its action
does, carried in the payload instead).
§Downstream impact (CLAUDE.md rule 8)
UiEvent is not #[non_exhaustive], so a consumer that
exhaustively matches every variant with no wildcard arm would
fail to compile the moment this variant landed. Blast-radius grep,
per CLAUDE.md’s mandatory rule 1 (this covers the new
Notification builder methods and notify_or_toast helper as
well as this variant):
$ grep -rn 'NotificationActivated\|send_notification\|backend::Notification' \
~/src/coord-tui/src ~/src/vimcode/src
(no output — zero hits in both; neither consumer calls
PlatformServices::send_notification or constructs Notification today)Zero hits means neither consumer references this variant yet, but
the real question for a new enum variant is whether either
consumer’s existing matches over UiEvent are exhaustive
(no wildcard arm). Both top-level UiEvent dispatches are large
multi-hundred-line matches that already end in a wildcard _ =>
arm. Adding NotificationActivated compiles clean with no
consumer changes required.
Tree(WidgetId, TreeEvent)
List(WidgetId, ListViewEvent)
Form(WidgetId, FormEvent)
Palette(WidgetId, PaletteEvent)
TabBar(WidgetId, TabBarEvent)
StatusBar(WidgetId, StatusBarEvent)
ActivityBar(WidgetId, ActivityBarEvent)
Terminal(WidgetId, TerminalEvent)
TextDisplay(WidgetId, TextDisplayEvent)
Chart(WidgetId, ChartEvent)
DataTable(WidgetId, DataTableEvent)
BackendNative(BackendNativeEvent)
Backend-specific event the crate couldn’t normalise. Apps ignore unless they want to special-case a platform.
User(UserPayload)
An app-defined value delivered from a background thread through
crate::Backend::waker.
waker() returns a Send + Sync closure any thread can call
with a UserPayload; the backend wakes its event loop and
delivers the payload as UiEvent::User to
crate::runner::AppLogic::handle, the same way any other
event reaches the app.
Routing: broadcast — there is no widget to hit-test or focus to
route through. Apps downcast the payload via
UserPayload::downcast_ref against whatever concrete type they
handed to UserPayload::new on the producing thread.
Trait Implementations§
Source§impl<'de> Deserialize<'de> for UiEvent
impl<'de> Deserialize<'de> for UiEvent
Source§fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where
__D: Deserializer<'de>,
fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where
__D: Deserializer<'de>,
impl StructuralPartialEq for UiEvent
Auto Trait Implementations§
impl !RefUnwindSafe for UiEvent
impl !UnwindSafe for UiEvent
impl Freeze for UiEvent
impl Send for UiEvent
impl Sync for UiEvent
impl Unpin for UiEvent
impl UnsafeUnpin for UiEvent
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> DeserializeOwned for Twhere
T: for<'de> Deserialize<'de>,
Source§impl<T> Downcast for Twhere
T: Any,
impl<T> Downcast for Twhere
T: Any,
Source§fn into_any(self: Box<T>) -> Box<dyn Any>
fn into_any(self: Box<T>) -> Box<dyn Any>
Box<dyn Trait> (where Trait: Downcast) to Box<dyn Any>. Box<dyn Any> can
then be further downcast into Box<ConcreteType> where ConcreteType implements Trait.Source§fn into_any_rc(self: Rc<T>) -> Rc<dyn Any>
fn into_any_rc(self: Rc<T>) -> Rc<dyn Any>
Rc<Trait> (where Trait: Downcast) to Rc<Any>. Rc<Any> can then be
further downcast into Rc<ConcreteType> where ConcreteType implements Trait.Source§fn as_any(&self) -> &(dyn Any + 'static)
fn as_any(&self) -> &(dyn Any + 'static)
&Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot
generate &Any’s vtable from &Trait’s.Source§fn as_any_mut(&mut self) -> &mut (dyn Any + 'static)
fn as_any_mut(&mut self) -> &mut (dyn Any + 'static)
&mut Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot
generate &mut Any’s vtable from &mut Trait’s.Source§impl<T> DowncastSync for T
impl<T> DowncastSync for T
Source§impl<T> Instrument for T
impl<T> Instrument for T
Source§fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
Source§fn in_current_span(self) -> Instrumented<Self> ⓘ
fn in_current_span(self) -> Instrumented<Self> ⓘ
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read more