| name | makepad-2.0-events |
| description | CRITICAL: Use for Makepad 2.0 event and action handling. Triggers on:
makepad event, makepad action, MatchEvent, handle_event, handle_actions,
on_click, on_render, on_return, on_startup, script_eval!, script_apply_eval!,
button clicked, text changed, slider changed, checkbox toggled,
Hit, FingerDown, FingerUp, KeyDown, KeyUp, Focus, ids!,
TextCopy, TextCut, SelectionHandleDrag, PopupDismissed, clipboard, selection,
IME, ImeAction, popup window events, video inputs, camera events,
事件, 动作, 点击, 输入, 回调, 交互, 事件处理, 剪贴板, 选择, 弹出窗口
|
Makepad 2.0 Event & Action System
Overview
Makepad 2.0 uses a two-layer event system:
-
Splash Layer -- Inline event handlers written directly in script_mod! Splash code
(on_click, on_render, on_return, on_startup). These handle UI interactions
declaratively inside the script, close to the widget definitions.
-
Rust Layer -- The MatchEvent trait with handle_actions, handle_timer,
handle_http_response, etc. These handle business logic, external I/O, and
anything that needs full Rust power.
Both layers communicate through two bridge macros:
script_eval!(cx, { ... }) -- Execute Splash code from Rust (update state, trigger renders)
script_apply_eval!(cx, widget_ref, { ... }) -- Patch widget properties from Rust at runtime
1. Splash Inline Event Handlers
Event handlers are attached directly to widgets inside script_mod! blocks. They use
closure syntax with || for no arguments or |arg| for callbacks that receive a value.
on_click -- Button/widget click
Fires when the user clicks a button or clickable widget. No arguments for plain buttons,
or |checked| for CheckBox which passes the new boolean state.
// Plain button click
add_button := Button{
text: "Add"
on_click: ||{
let text = ui.todo_input.text()
if text != "" {
add_todo(text, "")
ui.todo_input.set_text("")
}
}
}
// CheckBox click with checked state argument
check.on_click: |checked| toggle_todo(i, checked)
// Inline delete with closure capturing loop variable
delete.on_click: || delete_todo(i)
// Calling another widget's click programmatically
clear_done := ButtonFlatter{
text: "Clear completed"
on_click: ||{
todos.retain(|todo| !todo.done)
ui.todo_list.render()
}
}
on_render -- Dynamic rendering
Fires when .render() is called on the target view. This is the primary mechanism for
dynamic content. The body replaces the previous draw content of the view.
main_view := View{
width: Fill
height: Fill
on_render: ||{
counter_label := Label{
text: "Count: " + state.counter
draw_text.text_style.font_size: 24
}
}
}
// List rendering with for loop and per-item event handlers
todo_list := ScrollYView{
width: Fill height: Fill
new_batch: true
on_render: ||{
if todos.len() == 0
EmptyState{}
else for i, todo in todos {
TodoItem{
label.text: todo.text
check.active: todo.done
check.on_click: |checked| toggle_todo(i, checked)
delete.on_click: || delete_todo(i)
}
}
}
EmptyState{}
}
Key point: on_render is NOT called automatically. You must call ui.widget_name.render()
to trigger it. The new_batch: true property on a view tells the system to clear previous
draw content before re-rendering.
on_return -- TextInput enter key
Fires when the user presses Enter/Return inside a TextInput. Commonly used to submit forms.
todo_input := TextInput{
width: Fill height: 9. * theme.space_1
empty_text: "What needs to be done?"
on_return: || ui.add_button.on_click()
}
on_startup -- App startup
Fires once when the application starts. Defined at the Root level. Commonly used
to trigger initial renders.
ui: Root{
on_startup: ||{
ui.main_view.render()
}
main_window := Window{
// ...
}
}
Event handler capabilities
Inside event handlers you can:
- Call Splash functions:
add_todo(text, "dev")
- Read widget values:
let text = ui.todo_input.text()
- Set widget values:
ui.todo_input.set_text("")
- Trigger re-renders:
ui.todo_list.render()
- Trigger other widget clicks:
ui.add_button.on_click()
- Modify state variables:
state.counter += 1
- Use array methods:
todos.push({text: "new", done: false})
- Use control flow:
if text != "" { ... }
2. Rust Event Handling -- MatchEvent Trait
The MatchEvent trait is the Rust-side event dispatcher. It receives platform events
and widget actions through a set of handler methods.
Core trait definition (from draw/src/match_event.rs)
pub trait MatchEvent {
fn handle_startup(&mut self, _cx: &mut Cx) {}
fn handle_shutdown(&mut self, _cx: &mut Cx) {}
fn handle_foreground(&mut self, _cx: &mut Cx) {}
fn handle_background(&mut self, _cx: &mut Cx) {}
fn handle_pause(&mut self, _cx: &mut Cx) {}
fn handle_resume(&mut self, _cx: &mut Cx) {}
fn handle_window_got_focus(&mut self, _cx: &mut Cx, _window_id: &WindowId) {}
fn handle_window_lost_focus(&mut self, _cx: &mut Cx, _window_id: &WindowId) {}
fn handle_next_frame(&mut self, _cx: &mut Cx, _e: &NextFrameEvent) {}
fn handle_action(&mut self, _cx: &mut Cx, _e: &Action) {}
fn handle_actions(&mut self, cx: &mut Cx, actions: &Actions) {
for action in actions {
self.handle_action(cx, action);
}
}
fn handle_key_down(&mut self, _cx: &mut Cx, _e: &KeyEvent) {}
fn handle_key_up(&mut self, _cx: &mut Cx, _e: &KeyEvent) {}
fn handle_back_pressed(&mut self, _cx: &mut Cx) -> bool { false }
fn handle_timer(&mut self, _cx: &mut Cx, _e: &TimerEvent) {}
fn handle_draw(&mut self, _cx: &mut Cx, _e: &DrawEvent) {}
fn handle_draw_2d(&mut self, _cx: &mut Cx2d) {}
fn handle_http_response(&mut self, _cx: &mut Cx, _request_id: LiveId, _response: &HttpResponse) {}
fn handle_http_request_error(&mut self, _cx: &mut Cx, _request_id: LiveId, _err: &HttpError) {}
fn handle_http_progress(&mut self, _cx: &mut Cx, _request_id: LiveId, _progress: &HttpProgress) {}
fn handle_http_stream(&mut self, _cx: &mut Cx, _request_id: LiveId, _data: &HttpResponse) {}
fn handle_http_stream_complete(&mut self, _cx: &mut Cx, _request_id: LiveId, _data: &HttpResponse) {}
fn handle_signal(&mut self, _cx: &mut Cx) {}
fn handle_audio_devices(&mut self, _cx: &mut Cx, _e: &AudioDevicesEvent) {}
fn handle_midi_ports(&mut self, _cx: &mut Cx, _e: &MidiPortsEvent) {}
fn handle_video_inputs(&mut self, _cx: &mut Cx, _e: &VideoInputsEvent) {}
}
Standard App boilerplate (required)
Every Makepad 2.0 app needs this Rust structure:
use makepad_widgets::*;
app_main!(App);
script_mod! {
}
impl App {
fn run(vm: &mut ScriptVm) -> Self {
crate::makepad_widgets::script_mod(vm);
App::from_script_mod(vm, self::script_mod)
}
}
#[derive(Script, ScriptHook)]
pub struct App {
#[live]
ui: WidgetRef,
}
impl MatchEvent for App {
fn handle_actions(&mut self, cx: &mut Cx, actions: &Actions) {
}
}
impl AppMain for App {
fn handle_event(&mut self, cx: &mut Cx, event: &Event) {
self.match_event(cx, event);
self.ui.handle_event(cx, event, &mut Scope::empty());
}
}
CRITICAL: handle_event must call BOTH self.match_event(cx, event) (to dispatch
to the MatchEvent handlers) AND self.ui.handle_event(cx, event, &mut Scope::empty())
(to propagate events to widgets).
3. Widget Action API
Access widgets from Rust using self.ui.widget_type(cx, ids!(name)), then query their
action state by passing the &Actions reference.
Button
.clicked(actions) -> bool
.pressed(actions) -> bool
.long_pressed(actions) -> bool
.released(actions) -> bool
.clicked_modifiers(actions) -> Option<KeyModifiers>
.pressed_modifiers(actions) -> Option<KeyModifiers>
.released_modifiers(actions) -> Option<KeyModifiers>
TextInput
.changed(actions) -> Option<String>
.returned(actions) -> Option<(String, KeyModifiers)>
.escaped(actions) -> bool
.key_down_unhandled(actions) -> Option<KeyEvent>
.selected_text() -> String
CheckBox
.changed(actions) -> Option<bool>
DropDown
.selected(actions) -> Option<usize>
.changed(actions) -> Option<usize>
.changed_label(actions) -> Option<String>
.selected_item() -> usize
.selected_label() -> String
Slider
.slided(actions) -> Option<f64>
.end_slide(actions) -> Option<f64>
.value() -> Option<f64>
RadioButton / RadioButtonGroup
.clicked(actions) -> bool
.selected(cx, actions) -> Option<usize>
LinkLabel
.clicked(actions) -> bool
.clicked_modifiers(actions) -> Option<KeyModifiers>
Complete example
impl MatchEvent for App {
fn handle_actions(&mut self, cx: &mut Cx, actions: &Actions) {
if self.ui.button(cx, ids!(increment_button)).clicked(actions) {
script_eval!(cx, {
mod.state.counter += 1
ui.main_view.render()
});
}
if let Some(text) = self.ui.text_input(cx, ids!(search_input)).changed(actions) {
self.perform_search(cx, &text);
}
if let Some(checked) = self.ui.check_box(cx, ids!(dark_mode)).changed(actions) {
self.set_theme(cx, checked);
}
if let Some(index) = self.ui.drop_down(cx, ids!(language)).selected(actions) {
self.change_language(cx, index);
}
}
}
4. script_eval! Macro -- Rust to Splash Communication
script_eval! executes Splash code from within Rust handlers. It is the primary
bridge for updating Splash state and triggering UI re-renders from Rust.
Signature: script_eval!(cx_or_vm, { splash_code })
The first argument can be &mut Cx (inside event handlers) or &mut ScriptVm
(during initialization).
if self.ui.button(cx, ids!(increment_button)).clicked(actions) {
script_eval!(cx, {
mod.state.counter += 1
ui.main_view.render()
});
}
let rust_string = "Hello from Rust";
script_eval!(cx, {
mod.value = #(rust_string)
});
impl App {
fn run(vm: &mut ScriptVm) -> Self {
crate::makepad_widgets::theme_mod(vm);
script_eval!(vm, {
mod.theme = mod.themes.light
});
crate::makepad_widgets::widgets_mod(vm);
App::from_script_mod(vm, self::script_mod)
}
}
Value interpolation with #(expr)
Inside script_eval!, use #(rust_expression) to inject Rust values into Splash:
let count = 42_u64;
let message = "items found".to_string();
script_eval!(cx, {
mod.state.count = #(count)
mod.state.message = #(message)
ui.results_view.render()
});
5. script_apply_eval! Macro -- Runtime Property Patching
script_apply_eval! patches widget properties at runtime from Rust. Unlike
script_eval! which runs general Splash code, this targets a specific widget
reference and applies property changes directly.
Signature: script_apply_eval!(cx, widget_ref, { property_patches })
let height = 500.0_f64;
script_apply_eval!(cx, item, {
page_view: { height: #(height) }
});
let margin = Inset { top: 10.0, bottom: 10.0, left: 5.0, right: 5.0 };
script_apply_eval!(cx, content_view, {
margin: #(margin)
});
script_apply_eval!(cx, content_view, {
width: #(Size::fit())
});
let bg_color = vec4(0.2, 0.3, 0.4, 1.0);
let triangle_height = 8.0_f64;
script_apply_eval!(cx, content, {
draw_bg +: {
triangle_height: #(triangle_height)
background_color: #(bg_color)
}
});
6. Widget Reference Access from Rust
Accessing widgets by ID
Use self.ui.widget_type(cx, ids!(name)) to get a typed reference:
self.ui.button(cx, ids!(increment_button))
self.ui.label(cx, ids!(status_label))
self.ui.text_input(cx, ids!(search_input))
self.ui.check_box(cx, ids!(dark_mode))
self.ui.drop_down(cx, ids!(language))
self.ui.slider(cx, ids!(volume))
self.ui.view(cx, ids!(content))
self.ui.radio_button(cx, ids!(option_a))
Nested widget access with ids!
Use dot notation to access nested widgets:
self.ui.label(cx, ids!(container.inner.child_label))
self.ui.button(cx, ids!(toolbar.save_button))
Generic widget access
self.ui.widget(cx, ids!(my_widget))
Common widget setter methods (called from Rust)
self.ui.label(cx, ids!(my_label)).set_text(cx, "Hello");
self.ui.text_input(cx, ids!(my_input)).set_text(cx, "default value");
if let Some(mut inner) = self.ui.check_box(cx, ids!(my_check)).borrow_mut() {
inner.set_active(cx, true);
}
self.ui.drop_down(cx, ids!(my_dd)).set_selected_item(cx, 2);
self.ui.drop_down(cx, ids!(my_dd)).set_labels(cx, vec!["A".into(), "B".into()]);
self.ui.slider(cx, ids!(my_slider)).set_value(cx, 0.5);
self.ui.widget(cx, ids!(my_widget)).redraw(cx);
self.ui.button(cx, ids!(my_button)).set_visible(cx, false);
7. Widget Methods from Splash
Inside script_mod! Splash code, access widgets through the ui prefix:
// Trigger a re-render of a view's on_render handler
ui.main_view.render()
// Get text from a TextInput
let text = ui.todo_input.text()
// Set text on a TextInput
ui.todo_input.set_text("")
// Trigger a click on another widget
ui.add_button.on_click()
8. Hit Events (Low-Level)
For custom widgets that need raw input handling, the Hit enum provides low-level
events. Access them through event.hits(cx, area) in a widget's handle_event.
pub enum Hit {
KeyFocus(KeyFocusEvent),
KeyFocusLost(KeyFocusEvent),
KeyDown(KeyEvent),
KeyUp(KeyEvent),
TextInput(TextInputEvent),
TextRangeReplace(TextRangeReplaceEvent),
TextCopy(TextClipboardEvent),
TextCut(TextClipboardEvent),
ImeAction(ImeActionEvent),
FingerDown(FingerDownEvent),
FingerMove(FingerMoveEvent),
FingerUp(FingerUpEvent),
FingerScroll(FingerScrollEvent),
FingerLongPress(FingerLongPressEvent),
FingerHoverIn(FingerHoverEvent),
FingerHoverOver(FingerHoverEvent),
FingerHoverOut(FingerHoverEvent),
Trigger(TriggerHitEvent),
Nothing,
}
Usage in custom widgets
impl Widget for MyCustomWidget {
fn handle_event(&mut self, cx: &mut Cx, event: &Event, _scope: &mut Scope) {
match event.hits(cx, self.draw_bg.area()) {
Hit::FingerDown(fd) => {
cx.set_key_focus(self.draw_bg.area());
}
Hit::FingerUp(fu) => {
if fu.is_over {
let uid = self.widget_uid();
cx.widget_action(uid, MyWidgetAction::Clicked);
}
}
Hit::FingerMove(fm) => {
}
Hit::FingerHoverIn(_) => {
self.animator_play(cx, ids!(hover.on));
}
Hit::FingerHoverOut(_) => {
self.animator_play(cx, ids!(hover.off));
}
Hit::KeyDown(ke) => {
match ke.key_code {
KeyCode::ReturnKey => { }
KeyCode::Escape => { }
_ => {}
}
}
_ => {}
}
}
}
9. Timer Events
Timers are created via Cx methods and received in handle_timer.
Creating timers
let timer: Timer = cx.start_timeout(2.0);
let timer: Timer = cx.start_interval(0.5);
cx.stop_timer(timer);
Handling timer events
#[derive(Script, ScriptHook)]
pub struct App {
#[live]
ui: WidgetRef,
#[rust]
poll_timer: Timer,
}
impl MatchEvent for App {
fn handle_startup(&mut self, cx: &mut Cx) {
self.poll_timer = cx.start_interval(30.0);
}
fn handle_timer(&mut self, cx: &mut Cx, event: &TimerEvent) {
if self.poll_timer.is_timer(event).is_some() {
self.do_periodic_work(cx);
}
}
}
10. HTTP / Network Events
Making HTTP requests from Rust
let request_id = live_id!(my_request);
let mut req = HttpRequest::new(url, HttpMethod::Get);
req.set_header("Content-Type", "application/json");
req.set_body(body_bytes);
cx.http_request(request_id, req);
Handling HTTP responses
impl MatchEvent for App {
fn handle_http_response(&mut self, cx: &mut Cx, request_id: LiveId, response: &HttpResponse) {
if request_id == live_id!(my_request) {
let body = &response.body;
}
}
fn handle_http_request_error(&mut self, cx: &mut Cx, request_id: LiveId, err: &HttpError) {
if request_id == live_id!(my_request) {
log!("Request failed: {}", err.message);
}
}
fn handle_http_progress(&mut self, cx: &mut Cx, request_id: LiveId, progress: &HttpProgress) {
if request_id == live_id!(my_request) {
let pct = progress.loaded as f64 / progress.total as f64;
}
}
fn handle_http_stream(&mut self, cx: &mut Cx, request_id: LiveId, data: &HttpResponse) {
}
fn handle_http_stream_complete(&mut self, cx: &mut Cx, request_id: LiveId, data: &HttpResponse) {
}
}
Making HTTP requests from Splash
let req = net.HttpRequest{
url: "https://api.example.com/data"
method: net.HttpMethod.GET
headers: {"Content-Type": "application/json"}
}
net.http_request(req) do net.HttpEvents{
on_response: |res| {
// handle response
let data = res.body.to_string().parse_json()
}
on_error: |e| {
// handle error
}
}
// Streaming HTTP request
let req = net.HttpRequest{
url: "https://api.openai.com/v1/chat/completions"
method: net.HttpMethod.POST
headers: {"Content-Type": "application/json"}
is_streaming: true
body: {model: "gpt-4" messages: [{role: "user" content: "Hello"}]}
}
net.http_request(req) do net.HttpEvents{
on_stream: fn(res){
// Process each streaming chunk
let chunk = res.body.to_string()
}
on_complete: fn(res){
// Stream finished
}
}
11. Event Flow Diagram
User Input (click/key/mouse)
|
v
Platform Event Loop (Cx)
|
v
AppMain::handle_event(&mut self, cx, event)
|
+--> self.match_event(cx, event)
| |
| +--> MatchEvent::handle_startup / handle_shutdown / ...
| +--> MatchEvent::handle_actions(cx, actions)
| | |
| | +--> self.ui.button(cx, ids!(name)).clicked(actions)
| | +--> script_eval!(cx, { ... })
| | |
| | v
| | ScriptVm::eval -> updates Splash state
| | |
| | +--> ui.view.render() -> schedules re-draw
| |
| +--> MatchEvent::handle_timer(cx, event)
| +--> MatchEvent::handle_http_response(cx, id, response)
| +--> MatchEvent::handle_key_down(cx, event)
|
+--> self.ui.handle_event(cx, event, scope)
|
+--> Widget tree event propagation
+--> Hit detection (FingerDown/Up/Move)
+--> Splash on_click / on_return handlers execute
+--> on_render handlers execute during draw
+--> Widget actions emitted -> feed back into Actions
12. Complete Patterns
Pattern 1: Counter (button click -> state update -> re-render)
use makepad_widgets::*;
app_main!(App);
script_mod! {
use mod.prelude.widgets.*
let state = {
counter: 0
}
mod.state = state
startup() do #(App::script_component(vm)){
ui: Root{
on_startup: ||{
ui.main_view.render()
}
main_window := Window{
window.inner_size: vec2(420, 220)
body +: {
main_view := View{
width: Fill
height: Fill
flow: Down
spacing: 12
align: Center
on_render: ||{
counter_label := Label{
text: "Count: " + state.counter
draw_text.text_style.font_size: 24
}
}
}
increment_button := Button{
text: "Increment"
}
}
}
}
}
}
impl App {
fn run(vm: &mut ScriptVm) -> Self {
crate::makepad_widgets::script_mod(vm);
App::from_script_mod(vm, self::script_mod)
}
}
#[derive(Script, ScriptHook)]
pub struct App {
#[live]
ui: WidgetRef,
}
impl MatchEvent for App {
fn handle_actions(&mut self, cx: &mut Cx, actions: &Actions) {
if self.ui.button(cx, ids!(increment_button)).clicked(actions) {
script_eval!(cx, {
mod.state.counter += 1
ui.main_view.render()
});
}
}
}
impl AppMain for App {
fn handle_event(&mut self, cx: &mut Cx, event: &Event) {
self.match_event(cx, event);
self.ui.handle_event(cx, event, &mut Scope::empty());
}
}
Pattern 2: Form (text input -> validation -> submission)
// Inside script_mod!
fn submit_form(){
let name = ui.name_input.text()
let email = ui.email_input.text()
if name == "" {
ui.error_label.set_text("Name is required")
} else if email == "" {
ui.error_label.set_text("Email is required")
} else {
// Process form...
ui.error_label.set_text("")
ui.name_input.set_text("")
ui.email_input.set_text("")
}
}
// In the UI tree:
name_input := TextInput{
empty_text: "Your name"
}
email_input := TextInput{
empty_text: "your@email.com"
on_return: || submit_form()
}
submit_button := Button{
text: "Submit"
on_click: || submit_form()
}
error_label := Label{
text: ""
draw_text.color: #f00
}
Pattern 3: List with per-item events
let items = []
items.push({name: "Item A", selected: false})
items.push({name: "Item B", selected: false})
fn toggle_item(index, checked){
items[index].selected = checked
}
fn delete_item(index){
items.remove(index)
ui.item_list.render()
}
// In UI:
item_list := ScrollYView{
width: Fill height: Fill
new_batch: true
on_render: ||{
for i, item in items {
RoundedView{
width: Fill height: Fit
flow: Right
check := CheckBox{
active: item.selected
on_click: |checked| toggle_item(i, checked)
}
Label{text: item.name}
Button{
text: "Delete"
on_click: || delete_item(i)
}
}
}
}
}
Pattern 4: Cross-layer communication (Splash state + Rust logic)
impl MatchEvent for App {
fn handle_actions(&mut self, cx: &mut Cx, actions: &Actions) {
if self.ui.button(cx, ids!(fetch_button)).clicked(actions) {
let request_id = live_id!(fetch_data);
let req = HttpRequest::new("https://api.example.com/data", HttpMethod::Get);
cx.http_request(request_id, req);
}
}
fn handle_http_response(&mut self, cx: &mut Cx, request_id: LiveId, response: &HttpResponse) {
if request_id == live_id!(fetch_data) {
let data = String::from_utf8_lossy(&response.body).to_string();
script_eval!(cx, {
mod.state.data = #(data)
mod.state.loading = false
ui.results_view.render()
});
}
}
}
Source Files Reference
| File | Purpose |
|---|
draw/src/match_event.rs | MatchEvent trait definition |
widgets/src/widget_match_event.rs | WidgetMatchEvent trait (for custom widgets) |
platform/src/event/event.rs | Event enum, Hit enum, Timer |
platform/src/event/finger.rs | Finger/pointer event types |
platform/script/src/lib.rs | script_eval! macro definition |
platform/script/derive/src/lib.rs | script_apply_eval! proc macro |
widgets/src/button.rs | Button action API |
widgets/src/text_input.rs | TextInput action API |
widgets/src/check_box.rs | CheckBox action API |
widgets/src/drop_down.rs | DropDown action API |
widgets/src/slider.rs | Slider action API |
widgets/src/radio_button.rs | RadioButton action API |
examples/counter/src/app.rs | Counter example (minimal) |
examples/todo/src/app.rs | Todo example (comprehensive) |
examples/git/src/app.rs | Git example (HTTP, timers) |
examples/camera/src/ | Camera example (media events) |
examples/text_selection/src/ | Text selection example |
13. New Events (March 2026)
Selection & Clipboard Events
New Hit variants for text selection and clipboard operations:
Hit::TextCopy(TextClipboardEvent)
Hit::TextCut(TextClipboardEvent)
Mobile Selection Handles: Native selection handles on iOS/Android emit:
Event::SelectionHandleDrag
Long-press on text selects a word and shows the native clipboard toolbar. TextFlow widget integrates clipboard actions automatically.
Popup Window Events
For popup windows (context menus, dropdowns):
Event::WindowClosed
Event::PopupDismissed
WindowClosed is always emitted before PopupDismissed. Apps must handle PopupDismissed for explicit-close semantics.
IME Events
IME (Input Method Editor) support for CJK text input:
Hit::ImeAction(ImeActionEvent)
Hit::TextRangeReplace(TextRangeReplaceEvent)
Linux X11 IME: Full IME support added for X11. IME popup window positioning fixed to avoid appearing without TextInput focus.
Video & Media Events
fn handle_video_inputs(&mut self, _cx: &mut Cx, _e: &VideoInputsEvent) {}
Video input device enumeration events from the media plugin system. Camera and video playback events route through the media plugin architecture.
Primary Selection (Linux)
Linux primary selection (middle-click paste):
- Wayland:
zwp_primary_selection_device_manager_v1 protocol
- X11: PRIMARY atom handling
- Integrated with TextInput and TextFlow widgets automatically