1use std::io;
6
7use iced::{Point, Task, window};
8
9use plushie_widget_sdk::protocol::OutgoingEvent;
10use plushie_widget_sdk::protocol::OutgoingEventKeyExt;
11use plushie_widget_sdk::runtime::{
12 KeyEventData, Message, serialize_modifiers, serialize_mouse_button, serialize_scroll_delta,
13};
14
15use crate::App;
16use crate::constants::*;
17
18fn path_to_string(path: std::path::PathBuf) -> String {
22 match path.to_str() {
23 Some(s) => s.to_string(),
24 None => {
25 log::warn!(
26 "file path contains non-UTF-8 bytes, using lossy conversion: {}",
27 path.display()
28 );
29 path.to_string_lossy().into_owned()
30 }
31 }
32}
33
34impl App {
35 fn resolve_window_id(&self, iced_id: &window::Id) -> Option<&str> {
39 let resolved = self.windows.get_window_id(iced_id);
40 if resolved.is_none() {
41 log::debug!("event for unknown iced window {:?}, suppressing", iced_id);
42 }
43 resolved
44 }
45
46 pub fn handle_key_pressed(&self, data: KeyEventData, iced_id: window::Id) -> Task<Message> {
47 let Some(window_id) = self.resolve_window_id(&iced_id) else {
48 return Task::none();
49 };
50 self.emit_subscription_for_window(SUB_KEY_PRESS, Some(window_id), data.captured, |tag| {
51 OutgoingEvent::key_press(tag, &data)
52 })
53 }
54
55 pub fn handle_key_released(&self, data: KeyEventData, iced_id: window::Id) -> Task<Message> {
56 let Some(window_id) = self.resolve_window_id(&iced_id) else {
57 return Task::none();
58 };
59 self.emit_subscription_for_window(SUB_KEY_RELEASE, Some(window_id), data.captured, |tag| {
60 OutgoingEvent::key_release(tag, &data)
61 })
62 }
63
64 pub fn handle_modifiers_changed(
65 &mut self,
66 mods: iced::keyboard::Modifiers,
67 iced_id: window::Id,
68 captured: bool,
69 ) -> Task<Message> {
70 self.current_modifiers = mods;
71 let Some(window_id) = self.windows.get_window_id(&iced_id) else {
72 log::debug!("event for unknown iced window {:?}, suppressing", iced_id);
73 return Task::none();
74 };
75 crate::app::coalesce_subscription_into(
76 &self.core,
77 &mut self.emitter,
78 SUB_MODIFIERS_CHANGED,
79 Some(window_id),
80 captured,
81 |tag| OutgoingEvent::modifiers_changed(tag, serialize_modifiers(mods)),
82 )
83 }
84
85 pub fn handle_cursor_moved(
86 &mut self,
87 pos: Point,
88 iced_id: window::Id,
89 captured: bool,
90 ) -> Task<Message> {
91 let Some(window_id) = self.windows.get_window_id(&iced_id) else {
92 log::debug!("event for unknown iced window {:?}, suppressing", iced_id);
93 return Task::none();
94 };
95 crate::app::coalesce_subscription_into(
96 &self.core,
97 &mut self.emitter,
98 SUB_POINTER_MOVE,
99 Some(window_id),
100 captured,
101 |tag| OutgoingEvent::cursor_moved(tag, pos.x, pos.y),
102 )
103 }
104
105 pub fn handle_cursor_entered(&self, iced_id: window::Id, captured: bool) -> Task<Message> {
106 let Some(window_id) = self.resolve_window_id(&iced_id) else {
107 return Task::none();
108 };
109 self.emit_subscription_for_window(SUB_POINTER_MOVE, Some(window_id), captured, |tag| {
110 OutgoingEvent::cursor_entered(tag)
111 })
112 }
113
114 pub fn handle_cursor_left(&self, iced_id: window::Id, captured: bool) -> Task<Message> {
115 let Some(window_id) = self.resolve_window_id(&iced_id) else {
116 return Task::none();
117 };
118 self.emit_subscription_for_window(SUB_POINTER_MOVE, Some(window_id), captured, |tag| {
119 OutgoingEvent::cursor_left(tag)
120 })
121 }
122
123 pub fn handle_mouse_button_pressed(
124 &self,
125 button: iced::mouse::Button,
126 iced_id: window::Id,
127 captured: bool,
128 ) -> Task<Message> {
129 let Some(window_id) = self.resolve_window_id(&iced_id) else {
130 return Task::none();
131 };
132 self.emit_subscription_for_window(SUB_POINTER_BUTTON, Some(window_id), captured, |tag| {
133 OutgoingEvent::button_pressed(tag, serialize_mouse_button(&button))
134 })
135 }
136
137 pub fn handle_mouse_button_released(
138 &self,
139 button: iced::mouse::Button,
140 iced_id: window::Id,
141 captured: bool,
142 ) -> Task<Message> {
143 let Some(window_id) = self.resolve_window_id(&iced_id) else {
144 return Task::none();
145 };
146 self.emit_subscription_for_window(SUB_POINTER_BUTTON, Some(window_id), captured, |tag| {
147 OutgoingEvent::button_released(tag, serialize_mouse_button(&button))
148 })
149 }
150
151 pub fn handle_wheel_scrolled(
152 &mut self,
153 delta: iced::mouse::ScrollDelta,
154 iced_id: window::Id,
155 captured: bool,
156 ) -> Task<Message> {
157 let Some(window_id) = self.windows.get_window_id(&iced_id) else {
158 log::debug!("event for unknown iced window {:?}, suppressing", iced_id);
159 return Task::none();
160 };
161 crate::app::coalesce_subscription_into(
162 &self.core,
163 &mut self.emitter,
164 SUB_POINTER_SCROLL,
165 Some(window_id),
166 captured,
167 |tag| {
168 let (dx, dy, unit) = serialize_scroll_delta(&delta);
169 OutgoingEvent::wheel_scrolled(tag, dx, dy, unit)
170 },
171 )
172 }
173
174 pub fn handle_finger_pressed(
175 &self,
176 finger: iced::touch::Finger,
177 pos: Point,
178 iced_id: window::Id,
179 captured: bool,
180 ) -> Task<Message> {
181 let Some(window_id) = self.resolve_window_id(&iced_id) else {
182 return Task::none();
183 };
184 self.emit_subscription_for_window(SUB_POINTER_TOUCH, Some(window_id), captured, |tag| {
185 OutgoingEvent::finger_pressed(tag, finger.0, pos.x, pos.y)
186 })
187 }
188
189 pub fn handle_finger_moved(
190 &mut self,
191 finger: iced::touch::Finger,
192 pos: Point,
193 iced_id: window::Id,
194 captured: bool,
195 ) -> Task<Message> {
196 let Some(window_id) = self.windows.get_window_id(&iced_id) else {
197 log::debug!("event for unknown iced window {:?}, suppressing", iced_id);
198 return Task::none();
199 };
200 crate::app::coalesce_subscription_into(
201 &self.core,
202 &mut self.emitter,
203 SUB_POINTER_TOUCH,
204 Some(window_id),
205 captured,
206 |tag| OutgoingEvent::finger_moved(tag, finger.0, pos.x, pos.y),
207 )
208 }
209
210 pub fn handle_finger_lifted(
211 &self,
212 finger: iced::touch::Finger,
213 pos: Point,
214 iced_id: window::Id,
215 captured: bool,
216 ) -> Task<Message> {
217 let Some(window_id) = self.resolve_window_id(&iced_id) else {
218 return Task::none();
219 };
220 self.emit_subscription_for_window(SUB_POINTER_TOUCH, Some(window_id), captured, |tag| {
221 OutgoingEvent::finger_lifted(tag, finger.0, pos.x, pos.y)
222 })
223 }
224
225 pub fn handle_finger_lost(
226 &self,
227 finger: iced::touch::Finger,
228 pos: Point,
229 iced_id: window::Id,
230 captured: bool,
231 ) -> Task<Message> {
232 let Some(window_id) = self.resolve_window_id(&iced_id) else {
233 return Task::none();
234 };
235 self.emit_subscription_for_window(SUB_POINTER_TOUCH, Some(window_id), captured, |tag| {
236 OutgoingEvent::finger_lost(tag, finger.0, pos.x, pos.y)
237 })
238 }
239
240 pub fn handle_ime_opened(&self, iced_id: window::Id, captured: bool) -> Task<Message> {
247 let Some(window_id) = self.resolve_window_id(&iced_id) else {
248 return Task::none();
249 };
250 self.emit_subscription_for_window(SUB_IME, Some(window_id), captured, |tag| {
251 OutgoingEvent::ime_opened(tag)
252 })
253 }
254
255 pub fn handle_ime_preedit(
256 &self,
257 text: String,
258 cursor: Option<std::ops::Range<usize>>,
259 iced_id: window::Id,
260 captured: bool,
261 ) -> Task<Message> {
262 let Some(window_id) = self.resolve_window_id(&iced_id) else {
263 return Task::none();
264 };
265 self.emit_subscription_for_window(SUB_IME, Some(window_id), captured, |tag| {
266 OutgoingEvent::ime_preedit(tag, text.as_str(), cursor.clone())
267 })
268 }
269
270 pub fn handle_ime_commit(
271 &self,
272 text: String,
273 iced_id: window::Id,
274 captured: bool,
275 ) -> Task<Message> {
276 let Some(window_id) = self.resolve_window_id(&iced_id) else {
277 return Task::none();
278 };
279 self.emit_subscription_for_window(SUB_IME, Some(window_id), captured, |tag| {
280 OutgoingEvent::ime_commit(tag, text.as_str())
281 })
282 }
283
284 pub fn handle_ime_closed(&self, iced_id: window::Id, captured: bool) -> Task<Message> {
285 let Some(window_id) = self.resolve_window_id(&iced_id) else {
286 return Task::none();
287 };
288 self.emit_subscription_for_window(SUB_IME, Some(window_id), captured, |tag| {
289 OutgoingEvent::ime_closed(tag)
290 })
291 }
292
293 fn emit_window_event(
302 &self,
303 specific_key: Option<&str>,
304 event_fn: impl Fn(&str, &str) -> OutgoingEvent,
305 window_id: String,
306 ) -> io::Result<()> {
307 let wid = Some(window_id.as_str());
308 for entry in self.core.matching_entries(SUB_WINDOW_EVENT, wid) {
310 self.emitter
311 .emit_event(event_fn(entry.tag.as_str(), window_id.as_str()))?;
312 }
313 if let Some(key) = specific_key {
315 for entry in self.core.matching_entries(key, wid) {
316 self.emitter
317 .emit_event(event_fn(entry.tag.as_str(), window_id.as_str()))?;
318 }
319 }
320 Ok(())
321 }
322
323 pub fn handle_window_event(&self, iced_id: window::Id, evt: window::Event) -> Task<Message> {
324 let Some(window_id) = self.resolve_window_id(&iced_id).map(str::to_string) else {
325 return Task::none();
326 };
327 let result: io::Result<()> = (|| {
329 match evt {
330 window::Event::Opened {
331 position,
332 size,
333 scale_factor,
334 } => {
335 let wid = Some(window_id.as_str());
336 let pos = position.map(|p| (p.x, p.y));
337 for entry in self.core.matching_entries(SUB_WINDOW_EVENT, wid) {
338 self.emitter.emit_event(OutgoingEvent::window_opened(
339 entry.tag.as_str(),
340 window_id.as_str(),
341 pos,
342 size.width,
343 size.height,
344 scale_factor,
345 ))?;
346 }
347 for entry in self.core.matching_entries(SUB_WINDOW_OPEN, wid) {
348 self.emitter.emit_event(OutgoingEvent::window_opened(
349 entry.tag.as_str(),
350 window_id.as_str(),
351 pos,
352 size.width,
353 size.height,
354 scale_factor,
355 ))?;
356 }
357 }
358 window::Event::Closed => {
359 let wid = Some(window_id.as_str());
360 for entry in self.core.matching_entries(SUB_WINDOW_EVENT, wid) {
361 self.emitter.emit_event(OutgoingEvent::window_closed(
362 entry.tag.as_str(),
363 window_id.as_str(),
364 ))?;
365 }
366 }
367 window::Event::Moved(point) => {
368 self.emit_window_event(
369 Some(SUB_WINDOW_MOVE),
370 |tag, jid| OutgoingEvent::window_moved(tag, jid, point.x, point.y),
371 window_id,
372 )?;
373 }
374 window::Event::Resized(size) => {
375 self.emit_window_event(
376 Some(SUB_WINDOW_RESIZE),
377 |tag, jid| OutgoingEvent::window_resized(tag, jid, size.width, size.height),
378 window_id,
379 )?;
380 }
381 window::Event::Rescaled(factor) => {
382 let wid = Some(window_id.as_str());
383 for entry in self.core.matching_entries(SUB_WINDOW_EVENT, wid) {
384 self.emitter.emit_event(OutgoingEvent::window_rescaled(
385 entry.tag.as_str(),
386 window_id.as_str(),
387 factor,
388 ))?;
389 }
390 }
391 window::Event::Focused => {
392 self.emit_window_event(
393 Some(SUB_WINDOW_FOCUS),
394 |tag, jid| OutgoingEvent::window_focused(tag, jid),
395 window_id,
396 )?;
397 }
398 window::Event::Unfocused => {
399 self.emit_window_event(
400 Some(SUB_WINDOW_UNFOCUS),
401 |tag, jid| OutgoingEvent::window_unfocused(tag, jid),
402 window_id,
403 )?;
404 }
405 window::Event::FileHovered(path) => {
406 let wid = Some(window_id.as_str());
407 let path_str = path_to_string(path);
408 for entry in self.core.matching_entries(SUB_FILE_DROP, wid) {
409 self.emitter.emit_event(OutgoingEvent::file_hovered(
410 entry.tag.as_str(),
411 window_id.as_str(),
412 path_str.as_str(),
413 ))?;
414 }
415 }
416 window::Event::FileDropped(path) => {
417 let wid = Some(window_id.as_str());
418 let path_str = path_to_string(path);
419 for entry in self.core.matching_entries(SUB_FILE_DROP, wid) {
420 self.emitter.emit_event(OutgoingEvent::file_dropped(
421 entry.tag.as_str(),
422 window_id.as_str(),
423 path_str.as_str(),
424 ))?;
425 }
426 }
427 window::Event::FilesHoveredLeft => {
428 let wid = Some(window_id.as_str());
429 for entry in self.core.matching_entries(SUB_FILE_DROP, wid) {
430 self.emitter.emit_event(OutgoingEvent::files_hovered_left(
431 entry.tag.as_str(),
432 window_id.as_str(),
433 ))?;
434 }
435 }
436 window::Event::CloseRequested => {
437 }
439 window::Event::RedrawRequested(_) => {
440 }
442 }
443 Ok(())
444 })();
445 if let Err(e) = result {
446 log::error!("write error: {e}");
447 return iced::exit();
448 }
449 Task::none()
450 }
451}