Skip to main content

lgui_core/core/component/runtime/
input.rs

1use super::*;
2
3impl UiRuntime {
4    pub fn handle_input(&mut self, tree: &HostTree, input: InputEvent) -> RuntimeOutput {
5        let focus_traversal = match &input {
6            InputEvent::Keyboard(event)
7                if event.state == KeyState::Down
8                    && event.key == LogicalKey::Named(NamedKey::Tab) =>
9            {
10                Some(event.modifiers.shift())
11            }
12            _ => None,
13        };
14        let mut events = self.events.dispatch(tree, input);
15        let action_events = Vec::new();
16        let mut default_actions = Vec::new();
17        events.retain(|event| {
18            match event {
19                UiEvent::Wheel { hit, delta } => {
20                    let Some(action) = hit.action.as_ref() else {
21                        return true;
22                    };
23                    let action = action.clone().payload(delta.y.to_string());
24                    let target = hit.action_target.as_ref().unwrap_or(&hit.id);
25                    default_actions.push(UiDefaultAction {
26                        event_target: hit.id.clone(),
27                        action_target: target.clone(),
28                        action,
29                    });
30                }
31                UiEvent::Clicked(hit) => {
32                    if let Some(action) = hit.action.as_ref() {
33                        let target = hit.action_target.as_ref().unwrap_or(&hit.id);
34                        default_actions.push(UiDefaultAction {
35                            event_target: hit.id.clone(),
36                            action_target: target.clone(),
37                            action: action.clone(),
38                        });
39                    }
40                }
41                UiEvent::TextInput { target, text } => {
42                    default_actions.push(UiDefaultAction {
43                        event_target: target.clone(),
44                        action_target: target.clone(),
45                        action: UiAction::new("text.input").payload(text.clone()),
46                    });
47                }
48                UiEvent::SemanticValue { target, value } => {
49                    default_actions.push(UiDefaultAction {
50                        event_target: target.clone(),
51                        action_target: target.clone(),
52                        action: UiAction::new("semantic.set_value").payload(value.clone()),
53                    });
54                }
55                UiEvent::SemanticAction { target, action } => {
56                    let id = match action {
57                        super::SemanticAction::Increment => "semantic.increment",
58                        super::SemanticAction::Decrement => "semantic.decrement",
59                        super::SemanticAction::ScrollIntoView => "semantic.scroll_into_view",
60                        super::SemanticAction::ScrollUp => "semantic.scroll_up",
61                        super::SemanticAction::ScrollDown => "semantic.scroll_down",
62                        super::SemanticAction::ScrollLeft => "semantic.scroll_left",
63                        super::SemanticAction::ScrollRight => "semantic.scroll_right",
64                        super::SemanticAction::SetTextSelection => "semantic.set_text_selection",
65                        super::SemanticAction::Click
66                        | super::SemanticAction::Focus
67                        | super::SemanticAction::Blur
68                        | super::SemanticAction::SetValue => return true,
69                    };
70                    default_actions.push(UiDefaultAction {
71                        event_target: target.clone(),
72                        action_target: target.clone(),
73                        action: UiAction::new(id),
74                    });
75                }
76                UiEvent::Keyboard { target, event } if event.state == KeyState::Down => {
77                    let action = match &event.key {
78                        LogicalKey::Named(NamedKey::Backspace) => {
79                            Some(UiAction::new("text.backspace"))
80                        }
81                        LogicalKey::Named(NamedKey::ArrowLeft) => Some(
82                            UiAction::new("text.move.left").payload(if event.modifiers.shift() {
83                                "extend"
84                            } else {
85                                "collapse"
86                            }),
87                        ),
88                        LogicalKey::Named(NamedKey::ArrowRight) => Some(
89                            UiAction::new("text.move.right").payload(if event.modifiers.shift() {
90                                "extend"
91                            } else {
92                                "collapse"
93                            }),
94                        ),
95                        LogicalKey::Named(NamedKey::ArrowUp) => Some(
96                            UiAction::new("text.move.up").payload(if event.modifiers.shift() {
97                                "extend"
98                            } else {
99                                "collapse"
100                            }),
101                        ),
102                        LogicalKey::Named(NamedKey::ArrowDown) => Some(
103                            UiAction::new("text.move.down").payload(if event.modifiers.shift() {
104                                "extend"
105                            } else {
106                                "collapse"
107                            }),
108                        ),
109                        LogicalKey::Named(NamedKey::Enter) => {
110                            Some(UiAction::new("text.input").payload("\n"))
111                        }
112                        LogicalKey::Character(key)
113                            if event.modifiers.ctrl() && key.eq_ignore_ascii_case("a") =>
114                        {
115                            Some(UiAction::new("text.select.all"))
116                        }
117                        LogicalKey::Character(key)
118                            if event.modifiers.ctrl() && key.eq_ignore_ascii_case("c") =>
119                        {
120                            Some(UiAction::new("text.copy"))
121                        }
122                        LogicalKey::Character(key)
123                            if event.modifiers.ctrl() && key.eq_ignore_ascii_case("v") =>
124                        {
125                            Some(UiAction::new("text.paste"))
126                        }
127                        _ => None,
128                    };
129                    if let Some(action) = action {
130                        default_actions.push(UiDefaultAction {
131                            event_target: target.clone(),
132                            action_target: target.clone(),
133                            action,
134                        });
135                    }
136                }
137                UiEvent::Keyboard { .. } => {}
138                UiEvent::PointerPressed { hit, pointer } => {
139                    let point = pointer.point;
140                    let payload = format!("{},{}", point.x - hit.rect.left, point.y - hit.rect.top);
141                    let target = hit.action_target.as_ref().unwrap_or(&hit.id);
142                    if self.component_states.contains(target) {
143                        default_actions.push(UiDefaultAction {
144                            event_target: hit.id.clone(),
145                            action_target: target.clone(),
146                            action: UiAction::new(super::POINTER_DOWN_ACTION).payload(payload),
147                        });
148                    }
149                }
150                UiEvent::PointerDragged { hit, pointer } => {
151                    let point = pointer.point;
152                    let payload = format!("{},{}", point.x - hit.rect.left, point.y - hit.rect.top);
153                    let target = hit.action_target.as_ref().unwrap_or(&hit.id);
154                    if self.component_states.contains(target) {
155                        default_actions.push(UiDefaultAction {
156                            event_target: hit.id.clone(),
157                            action_target: target.clone(),
158                            action: UiAction::new(super::POINTER_DRAG_ACTION).payload(payload),
159                        });
160                    }
161                }
162                UiEvent::PointerReleased { hit, pointer } => {
163                    let point = pointer.point;
164                    let payload = format!("{},{}", point.x - hit.rect.left, point.y - hit.rect.top);
165                    let target = hit.action_target.as_ref().unwrap_or(&hit.id);
166                    if self.component_states.contains(target) {
167                        default_actions.push(UiDefaultAction {
168                            event_target: hit.id.clone(),
169                            action_target: target.clone(),
170                            action: UiAction::new(super::POINTER_UP_ACTION).payload(payload),
171                        });
172                    }
173                }
174                _ => {}
175            }
176            true
177        });
178        if let Some(reverse) = focus_traversal {
179            let event_target = self
180                .events
181                .state()
182                .focused
183                .or_else(|| tree.active_focus_scope_id())
184                .or_else(|| tree.focusable_hits().into_iter().next().map(|hit| hit.id));
185            if let Some(event_target) = event_target {
186                default_actions.push(UiDefaultAction {
187                    event_target: event_target.clone(),
188                    action_target: event_target,
189                    action: UiAction::new(FOCUS_TRAVERSAL_ACTION).payload(if reverse {
190                        "reverse"
191                    } else {
192                        "forward"
193                    }),
194                });
195            }
196        }
197        let handler_events = events
198            .iter()
199            .flat_map(|event| tree.handler_events(event))
200            .collect();
201        for event in events.iter().cloned() {
202            self.dirty.mark_event(event);
203        }
204        // Components may derive visuals directly from `interaction_flags` without declaring an
205        // animation. Hover, press and focus changes therefore invalidate their component owners
206        // independently from animation target updates. Raw pointer coordinates remain excluded.
207        self.mark_component_owners(tree, events.iter().flat_map(interaction_state_target_ids));
208        let animation_changed =
209            apply_events_to_animations(tree, &mut self.animations, events.iter().cloned());
210        if animation_changed {
211            self.mark_component_owners(tree, events.iter().flat_map(event_target_ids));
212        }
213        let dirty_bounds = self.dirty.take().bounds(tree);
214        RuntimeOutput {
215            events,
216            handler_events,
217            action_events,
218            default_actions,
219            dirty_bounds,
220            animation_changed,
221            route_changed: false,
222        }
223    }
224}
225
226pub(super) fn event_target_ids(event: &UiEvent) -> Vec<&UiId> {
227    match event {
228        UiEvent::HoverChanged { previous, current }
229        | UiEvent::PressedChanged { previous, current } => previous
230            .iter()
231            .chain(current.iter().map(|hit| &hit.id))
232            .collect(),
233        UiEvent::Clicked(hit)
234        | UiEvent::Wheel { hit, .. }
235        | UiEvent::PointerPressed { hit, .. }
236        | UiEvent::PointerMoved { hit, .. }
237        | UiEvent::PointerDragged { hit, .. }
238        | UiEvent::PointerReleased { hit, .. } => vec![&hit.id],
239        UiEvent::TextInput { target, .. }
240        | UiEvent::ImeStarted { target }
241        | UiEvent::ImeUpdated { target, .. }
242        | UiEvent::ImeEnded { target }
243        | UiEvent::Keyboard { target, .. }
244        | UiEvent::SemanticValue { target, .. }
245        | UiEvent::SemanticAction { target, .. } => vec![target],
246        UiEvent::FocusChanged { current, previous } => previous
247            .iter()
248            .chain(current.iter().map(|hit| &hit.id))
249            .collect(),
250        UiEvent::PointerLeft { previous } => previous.iter().collect(),
251    }
252}
253
254pub(super) fn interaction_state_target_ids(event: &UiEvent) -> Vec<&UiId> {
255    match event {
256        UiEvent::HoverChanged { previous, current }
257        | UiEvent::PressedChanged { previous, current } => previous
258            .iter()
259            .chain(current.iter().map(|hit| &hit.id))
260            .collect(),
261        UiEvent::FocusChanged { previous, current } => previous
262            .iter()
263            .chain(current.iter().map(|hit| &hit.id))
264            .collect(),
265        UiEvent::PointerLeft { previous } => previous.iter().collect(),
266        UiEvent::Clicked(_)
267        | UiEvent::Wheel { .. }
268        | UiEvent::TextInput { .. }
269        | UiEvent::ImeStarted { .. }
270        | UiEvent::ImeUpdated { .. }
271        | UiEvent::ImeEnded { .. }
272        | UiEvent::Keyboard { .. }
273        | UiEvent::PointerPressed { .. }
274        | UiEvent::PointerMoved { .. }
275        | UiEvent::PointerDragged { .. }
276        | UiEvent::PointerReleased { .. } => Vec::new(),
277        UiEvent::SemanticValue { .. } | UiEvent::SemanticAction { .. } => Vec::new(),
278    }
279}