Skip to main content

plushie_renderer_lib/
subscriptions.rs

1//! Builds the iced `Subscription` list based on which events the host has
2//! registered for. Split into per-category builders (keyboard, mouse, touch,
3//! IME, window, system).
4
5use iced::{Subscription, event, system, window};
6
7use plushie_widget_sdk::runtime::{KeyEventData, Message};
8
9use crate::App;
10use crate::constants::*;
11
12impl App {
13    /// Whether any host-registered subscription or any widget-scoped
14    /// subscription is active for `kind`.
15    ///
16    /// The iced-source gating in [`renderer_subscriptions`] uses this
17    /// so widget authors don't have to manually register a host
18    /// subscription just to receive events. When a widget is the sole
19    /// subscriber, iced still composes the underlying subscription and
20    /// [`App::update`] fans the event out to the widget via
21    /// [`WidgetRegistry::dispatch_widget_subscription`].
22    fn has_host_or_widget_subscription(&self, kind: &str) -> bool {
23        self.core.has_subscription(kind) || self.registry.has_widget_subscription(kind)
24    }
25
26    /// Build renderer subscriptions (everything except platform-specific
27    /// input sources like stdin). The binary crate combines this with
28    /// its own input subscription.
29    pub fn renderer_subscriptions(&self) -> Subscription<Message> {
30        let mut subs = vec![
31            // Always listen for window close events so we can clean up maps.
32            window::close_events().map(Message::WindowClosed),
33        ];
34
35        let has_on_event = self.has_host_or_widget_subscription(SUB_EVENT);
36
37        self.keyboard_subscriptions(has_on_event, &mut subs);
38        self.mouse_subscriptions(has_on_event, &mut subs);
39        self.touch_subscriptions(has_on_event, &mut subs);
40        self.ime_subscriptions(has_on_event, &mut subs);
41        self.window_subscriptions(&mut subs);
42        self.system_subscriptions(&mut subs);
43
44        // -- Catch-all event subscription --
45        // Subscribes to all keyboard, mouse, and touch events via a single
46        // listener, re-using existing Message variants. Window events are
47        // handled separately by on_window_event.
48        //
49        // To avoid duplicate event delivery when both on_event and a specific
50        // subscription (e.g. on_key_press) are active, skip event categories
51        // that already have a dedicated subscription listener above.
52        if has_on_event {
53            subs.push(event::listen_with(|evt, status, window| {
54                let captured = status == iced::event::Status::Captured;
55                match evt {
56                    // Keyboard
57                    iced::Event::Keyboard(iced::keyboard::Event::KeyPressed {
58                        key,
59                        modified_key,
60                        physical_key,
61                        location,
62                        modifiers,
63                        text,
64                        repeat,
65                    }) => Some(Message::KeyPressed(
66                        KeyEventData {
67                            key,
68                            modified_key,
69                            physical_key,
70                            location,
71                            modifiers,
72                            text: text.map(|s| s.to_string()),
73                            repeat,
74                            captured,
75                        },
76                        window,
77                    )),
78                    iced::Event::Keyboard(iced::keyboard::Event::KeyReleased {
79                        key,
80                        modified_key,
81                        physical_key,
82                        location,
83                        modifiers,
84                    }) => Some(Message::KeyReleased(
85                        KeyEventData {
86                            key,
87                            modified_key,
88                            physical_key,
89                            location,
90                            modifiers,
91                            text: None,
92                            repeat: false,
93                            captured,
94                        },
95                        window,
96                    )),
97                    iced::Event::Keyboard(iced::keyboard::Event::ModifiersChanged(mods)) => {
98                        Some(Message::ModifiersChanged(mods, window, captured))
99                    }
100                    // Mouse
101                    iced::Event::Mouse(iced::mouse::Event::CursorMoved { position }) => {
102                        Some(Message::CursorMoved(position, window, captured))
103                    }
104                    iced::Event::Mouse(iced::mouse::Event::CursorEntered) => {
105                        Some(Message::CursorEntered(window, captured))
106                    }
107                    iced::Event::Mouse(iced::mouse::Event::CursorLeft) => {
108                        Some(Message::CursorLeft(window, captured))
109                    }
110                    iced::Event::Mouse(iced::mouse::Event::ButtonPressed(button)) => {
111                        Some(Message::MouseButtonPressed(button, window, captured))
112                    }
113                    iced::Event::Mouse(iced::mouse::Event::ButtonReleased(button)) => {
114                        Some(Message::MouseButtonReleased(button, window, captured))
115                    }
116                    iced::Event::Mouse(iced::mouse::Event::WheelScrolled { delta }) => {
117                        Some(Message::WheelScrolled(delta, window, captured))
118                    }
119                    // Touch
120                    iced::Event::Touch(iced::touch::Event::FingerPressed { id, position }) => {
121                        Some(Message::FingerPressed(id, position, window, captured))
122                    }
123                    iced::Event::Touch(iced::touch::Event::FingerMoved { id, position }) => {
124                        Some(Message::FingerMoved(id, position, window, captured))
125                    }
126                    iced::Event::Touch(iced::touch::Event::FingerLifted { id, position }) => {
127                        Some(Message::FingerLifted(id, position, window, captured))
128                    }
129                    iced::Event::Touch(iced::touch::Event::FingerLost { id, position }) => {
130                        Some(Message::FingerLost(id, position, window, captured))
131                    }
132                    // IME
133                    iced::Event::InputMethod(iced::advanced::input_method::Event::Opened) => {
134                        Some(Message::ImeOpened(window, captured))
135                    }
136                    iced::Event::InputMethod(iced::advanced::input_method::Event::Preedit(
137                        text,
138                        cursor,
139                    )) => Some(Message::ImePreedit(text, cursor, window, captured)),
140                    iced::Event::InputMethod(iced::advanced::input_method::Event::Commit(text)) => {
141                        Some(Message::ImeCommit(text, window, captured))
142                    }
143                    iced::Event::InputMethod(iced::advanced::input_method::Event::Closed) => {
144                        Some(Message::ImeClosed(window, captured))
145                    }
146                    // Window events handled by on_window_event
147                    _ => None,
148                }
149            }));
150        }
151
152        Subscription::batch(subs)
153    }
154
155    fn keyboard_subscriptions(&self, has_on_event: bool, subs: &mut Vec<Subscription<Message>>) {
156        // When on_event is active, its catch-all listener already covers keyboard,
157        // mouse, touch, and IME events. Skip specific subscriptions to avoid
158        // duplicate event delivery.
159        if !has_on_event && self.has_host_or_widget_subscription(SUB_KEY_PRESS) {
160            subs.push(event::listen_with(|evt, status, window| {
161                if let iced::Event::Keyboard(iced::keyboard::Event::KeyPressed {
162                    key,
163                    modified_key,
164                    physical_key,
165                    location,
166                    modifiers,
167                    text,
168                    repeat,
169                }) = evt
170                {
171                    Some(Message::KeyPressed(
172                        KeyEventData {
173                            key,
174                            modified_key,
175                            physical_key,
176                            location,
177                            modifiers,
178                            text: text.map(|s| s.to_string()),
179                            repeat,
180                            captured: status == iced::event::Status::Captured,
181                        },
182                        window,
183                    ))
184                } else {
185                    None
186                }
187            }));
188        }
189
190        if !has_on_event && self.has_host_or_widget_subscription(SUB_KEY_RELEASE) {
191            subs.push(event::listen_with(|evt, status, window| {
192                if let iced::Event::Keyboard(iced::keyboard::Event::KeyReleased {
193                    key,
194                    modified_key,
195                    physical_key,
196                    location,
197                    modifiers,
198                }) = evt
199                {
200                    Some(Message::KeyReleased(
201                        KeyEventData {
202                            key,
203                            modified_key,
204                            physical_key,
205                            location,
206                            modifiers,
207                            text: None,
208                            repeat: false,
209                            captured: status == iced::event::Status::Captured,
210                        },
211                        window,
212                    ))
213                } else {
214                    None
215                }
216            }));
217        }
218
219        if !has_on_event && self.has_host_or_widget_subscription(SUB_MODIFIERS_CHANGED) {
220            subs.push(event::listen_with(|evt, status, window| {
221                if let iced::Event::Keyboard(iced::keyboard::Event::ModifiersChanged(mods)) = evt {
222                    Some(Message::ModifiersChanged(
223                        mods,
224                        window,
225                        status == iced::event::Status::Captured,
226                    ))
227                } else {
228                    None
229                }
230            }));
231        }
232    }
233
234    fn mouse_subscriptions(&self, has_on_event: bool, subs: &mut Vec<Subscription<Message>>) {
235        if !has_on_event && self.has_host_or_widget_subscription(SUB_POINTER_MOVE) {
236            subs.push(event::listen_with(|evt, status, window| {
237                let captured = status == iced::event::Status::Captured;
238                match evt {
239                    iced::Event::Mouse(iced::mouse::Event::CursorMoved { position }) => {
240                        Some(Message::CursorMoved(position, window, captured))
241                    }
242                    iced::Event::Mouse(iced::mouse::Event::CursorEntered) => {
243                        Some(Message::CursorEntered(window, captured))
244                    }
245                    iced::Event::Mouse(iced::mouse::Event::CursorLeft) => {
246                        Some(Message::CursorLeft(window, captured))
247                    }
248                    _ => None,
249                }
250            }));
251        }
252
253        if !has_on_event && self.has_host_or_widget_subscription(SUB_POINTER_BUTTON) {
254            subs.push(event::listen_with(|evt, status, window| {
255                let captured = status == iced::event::Status::Captured;
256                match evt {
257                    iced::Event::Mouse(iced::mouse::Event::ButtonPressed(button)) => {
258                        Some(Message::MouseButtonPressed(button, window, captured))
259                    }
260                    iced::Event::Mouse(iced::mouse::Event::ButtonReleased(button)) => {
261                        Some(Message::MouseButtonReleased(button, window, captured))
262                    }
263                    _ => None,
264                }
265            }));
266        }
267
268        if !has_on_event && self.has_host_or_widget_subscription(SUB_POINTER_SCROLL) {
269            subs.push(event::listen_with(|evt, status, window| {
270                if let iced::Event::Mouse(iced::mouse::Event::WheelScrolled { delta }) = evt {
271                    Some(Message::WheelScrolled(
272                        delta,
273                        window,
274                        status == iced::event::Status::Captured,
275                    ))
276                } else {
277                    None
278                }
279            }));
280        }
281    }
282
283    fn touch_subscriptions(&self, has_on_event: bool, subs: &mut Vec<Subscription<Message>>) {
284        if !has_on_event && self.has_host_or_widget_subscription(SUB_POINTER_TOUCH) {
285            subs.push(event::listen_with(|evt, status, window| {
286                let captured = status == iced::event::Status::Captured;
287                match evt {
288                    iced::Event::Touch(iced::touch::Event::FingerPressed { id, position }) => {
289                        Some(Message::FingerPressed(id, position, window, captured))
290                    }
291                    iced::Event::Touch(iced::touch::Event::FingerMoved { id, position }) => {
292                        Some(Message::FingerMoved(id, position, window, captured))
293                    }
294                    iced::Event::Touch(iced::touch::Event::FingerLifted { id, position }) => {
295                        Some(Message::FingerLifted(id, position, window, captured))
296                    }
297                    iced::Event::Touch(iced::touch::Event::FingerLost { id, position }) => {
298                        Some(Message::FingerLost(id, position, window, captured))
299                    }
300                    _ => None,
301                }
302            }));
303        }
304    }
305
306    fn ime_subscriptions(&self, has_on_event: bool, subs: &mut Vec<Subscription<Message>>) {
307        if !has_on_event && self.has_host_or_widget_subscription(SUB_IME) {
308            subs.push(event::listen_with(|evt, status, window| {
309                let captured = status == iced::event::Status::Captured;
310                match evt {
311                    iced::Event::InputMethod(iced::advanced::input_method::Event::Opened) => {
312                        Some(Message::ImeOpened(window, captured))
313                    }
314                    iced::Event::InputMethod(iced::advanced::input_method::Event::Preedit(
315                        text,
316                        cursor,
317                    )) => Some(Message::ImePreedit(text, cursor, window, captured)),
318                    iced::Event::InputMethod(iced::advanced::input_method::Event::Commit(text)) => {
319                        Some(Message::ImeCommit(text, window, captured))
320                    }
321                    iced::Event::InputMethod(iced::advanced::input_method::Event::Closed) => {
322                        Some(Message::ImeClosed(window, captured))
323                    }
324                    _ => None,
325                }
326            }));
327        }
328    }
329
330    fn window_subscriptions(&self, subs: &mut Vec<Subscription<Message>>) {
331        if self.has_any_subscription(&[
332            SUB_WINDOW_EVENT,
333            SUB_WINDOW_OPEN,
334            SUB_WINDOW_MOVE,
335            SUB_WINDOW_RESIZE,
336            SUB_WINDOW_FOCUS,
337            SUB_WINDOW_UNFOCUS,
338            SUB_FILE_DROP,
339        ]) {
340            subs.push(window::events().map(|(id, evt)| Message::WindowEvent(id, evt)));
341        }
342
343        if self.has_host_or_widget_subscription(SUB_WINDOW_CLOSE) {
344            subs.push(window::close_requests().map(Message::WindowCloseRequested));
345        }
346
347        // -- Animation frame subscription --
348        // Active when the SDK subscribes to animation_frame, when any
349        // widget has declared an animation-frame subscription, or when
350        // the renderer has active transitions/springs (zero-traffic
351        // animation).
352        if self.has_host_or_widget_subscription(SUB_ANIMATION_FRAME)
353            || self.transition_manager.has_active()
354        {
355            subs.push(window::frames().map(Message::AnimationFrame));
356        }
357    }
358
359    fn system_subscriptions(&self, subs: &mut Vec<Subscription<Message>>) {
360        // Track system theme changes when any active theme follows system.
361        if self.theme_follows_system
362            || self.windows.any_theme_follows_system()
363            || self.has_host_or_widget_subscription(SUB_THEME_CHANGE)
364        {
365            subs.push(system::theme_changes().map(Message::ThemeChanged));
366        }
367    }
368
369    /// Check if any of the given subscription keys are registered on
370    /// the host side or by any widget.
371    fn has_any_subscription(&self, keys: &[&str]) -> bool {
372        keys.iter().any(|k| self.has_host_or_widget_subscription(k))
373    }
374}