Skip to main content

toddy_core/protocol/
outgoing.rs

1//! Outgoing wire messages: events and response types.
2//!
3//! [`OutgoingEvent`] is the main event struct emitted by the renderer.
4//! Response types ([`EffectResponse`], [`QueryResponse`], etc.) are
5//! serialized in reply to incoming messages.
6
7use serde::Serialize;
8use serde_json::Value;
9
10/// An event written to stdout by the renderer.
11///
12/// All events share a flat struct with optional fields. There are two
13/// constructor patterns:
14///
15/// - **Widget events** (click, input, toggle, etc.) use `id` to identify
16///   the source widget. Built via [`bare`](Self::bare).
17/// - **Subscription events** (key_press, cursor_moved, window_opened,
18///   etc.) use `tag` to identify the subscription that requested them.
19///   Built via [`tagged`](Self::tagged). The `id` field is empty.
20///
21/// Extension authors emit custom events via
22/// [`extension_event`](Self::extension_event).
23#[derive(Debug, Serialize)]
24pub struct OutgoingEvent {
25    /// Always `"event"`.
26    #[serde(rename = "type")]
27    pub message_type: &'static str,
28    /// Session that produced this event.
29    pub session: String,
30    /// Event type (e.g. `"click"`, `"key_press"`, `"window_opened"`).
31    pub family: String,
32    /// Source widget node ID (widget events) or empty (subscription events).
33    pub id: String,
34    /// Primary value payload (e.g. input text, slider value, selected option).
35    #[serde(skip_serializing_if = "Option::is_none")]
36    pub value: Option<Value>,
37    /// Subscription tag identifying which subscription requested this event.
38    #[serde(skip_serializing_if = "Option::is_none")]
39    pub tag: Option<String>,
40    /// Keyboard modifier state at the time of the event.
41    #[serde(skip_serializing_if = "Option::is_none")]
42    pub modifiers: Option<KeyModifiers>,
43    /// Flexible extra data for events that carry additional fields beyond
44    /// the standard id/value/tag/modifiers shape.
45    #[serde(skip_serializing_if = "Option::is_none")]
46    pub data: Option<Value>,
47    /// Whether the event was captured (consumed) by an iced widget before
48    /// reaching the subscription listener. Present on keyboard, mouse,
49    /// touch, and IME events; absent on widget-level events.
50    #[serde(skip_serializing_if = "Option::is_none")]
51    pub captured: Option<bool>,
52}
53
54impl OutgoingEvent {
55    /// Mark the event with its capture status.
56    pub fn with_captured(mut self, captured: bool) -> Self {
57        self.captured = Some(captured);
58        self
59    }
60
61    /// Set the session ID for this event.
62    pub fn with_session(mut self, session: impl Into<String>) -> Self {
63        self.session = session.into();
64        self
65    }
66}
67
68/// Serializable representation of keyboard modifiers.
69#[derive(Debug, Serialize)]
70pub struct KeyModifiers {
71    pub shift: bool,
72    pub ctrl: bool,
73    pub alt: bool,
74    pub logo: bool,
75    pub command: bool,
76}
77
78// ---------------------------------------------------------------------------
79// Widget events (click, input, toggle, slide, select, submit)
80// ---------------------------------------------------------------------------
81
82impl OutgoingEvent {
83    /// Helper to build a bare event with only the common fields.
84    fn bare(family: impl Into<String>, id: String) -> Self {
85        Self {
86            message_type: "event",
87            session: String::new(),
88            family: family.into(),
89            id,
90            value: None,
91            tag: None,
92            modifiers: None,
93            data: None,
94            captured: None,
95        }
96    }
97
98    /// Helper to build a subscription-tagged event with no widget id.
99    fn tagged(family: impl Into<String>, tag: String) -> Self {
100        Self {
101            message_type: "event",
102            session: String::new(),
103            family: family.into(),
104            id: String::new(),
105            value: None,
106            tag: Some(tag),
107            modifiers: None,
108            data: None,
109            captured: None,
110        }
111    }
112
113    /// Generic widget event with a family string and optional data payload.
114    /// Used for on_open, on_close, sort, and other events.
115    pub fn generic(family: impl Into<String>, id: String, data: Option<Value>) -> Self {
116        Self {
117            data,
118            ..Self::bare(family, id)
119        }
120    }
121
122    /// Convenience constructor for extension-emitted events.
123    pub fn extension_event(family: String, id: String, data: Option<Value>) -> Self {
124        Self::generic(family, id, data)
125    }
126
127    pub fn click(id: String) -> Self {
128        Self::bare("click", id)
129    }
130
131    pub fn input(id: String, value: String) -> Self {
132        Self {
133            value: Some(Value::String(value)),
134            ..Self::bare("input", id)
135        }
136    }
137
138    pub fn submit(id: String, value: String) -> Self {
139        Self {
140            value: Some(Value::String(value)),
141            ..Self::bare("submit", id)
142        }
143    }
144
145    pub fn toggle(id: String, checked: bool) -> Self {
146        Self {
147            value: Some(Value::Bool(checked)),
148            ..Self::bare("toggle", id)
149        }
150    }
151
152    pub fn slide(id: String, value: f64) -> Self {
153        Self {
154            value: Some(serde_json::json!(sanitize_f64(value))),
155            ..Self::bare("slide", id)
156        }
157    }
158
159    pub fn slide_release(id: String, value: f64) -> Self {
160        Self {
161            value: Some(serde_json::json!(sanitize_f64(value))),
162            ..Self::bare("slide_release", id)
163        }
164    }
165
166    pub fn select(id: String, value: String) -> Self {
167        Self {
168            value: Some(Value::String(value)),
169            ..Self::bare("select", id)
170        }
171    }
172
173    // -----------------------------------------------------------------------
174    // Keyboard events
175    // -----------------------------------------------------------------------
176
177    pub fn key_press(tag: String, data: &crate::message::KeyEventData) -> Self {
178        Self {
179            modifiers: Some(crate::message::serialize_modifiers(data.modifiers)),
180            value: Some(Value::String(crate::message::serialize_key(&data.key))),
181            data: Some(serde_json::json!({
182                "modified_key": crate::message::serialize_key(&data.modified_key),
183                "physical_key": crate::message::serialize_physical_key(&data.physical_key),
184                "location": crate::message::serialize_location(&data.location),
185                "text": data.text.as_deref(),
186                "repeat": data.repeat,
187            })),
188            ..Self::tagged("key_press", tag)
189        }
190    }
191
192    pub fn key_release(tag: String, data: &crate::message::KeyEventData) -> Self {
193        Self {
194            modifiers: Some(crate::message::serialize_modifiers(data.modifiers)),
195            value: Some(Value::String(crate::message::serialize_key(&data.key))),
196            data: Some(serde_json::json!({
197                "modified_key": crate::message::serialize_key(&data.modified_key),
198                "physical_key": crate::message::serialize_physical_key(&data.physical_key),
199                "location": crate::message::serialize_location(&data.location),
200            })),
201            ..Self::tagged("key_release", tag)
202        }
203    }
204
205    pub fn modifiers_changed(tag: String, modifiers: KeyModifiers) -> Self {
206        Self {
207            modifiers: Some(modifiers),
208            ..Self::tagged("modifiers_changed", tag)
209        }
210    }
211
212    // -----------------------------------------------------------------------
213    // Mouse events
214    // -----------------------------------------------------------------------
215
216    pub fn cursor_moved(tag: String, x: f32, y: f32) -> Self {
217        Self {
218            data: Some(serde_json::json!({"x": sanitize_f32(x), "y": sanitize_f32(y)})),
219            ..Self::tagged("cursor_moved", tag)
220        }
221    }
222
223    pub fn cursor_entered(tag: String) -> Self {
224        Self::tagged("cursor_entered", tag)
225    }
226
227    pub fn cursor_left(tag: String) -> Self {
228        Self::tagged("cursor_left", tag)
229    }
230
231    pub fn button_pressed(tag: String, button: String) -> Self {
232        Self {
233            value: Some(Value::String(button)),
234            ..Self::tagged("button_pressed", tag)
235        }
236    }
237
238    pub fn button_released(tag: String, button: String) -> Self {
239        Self {
240            value: Some(Value::String(button)),
241            ..Self::tagged("button_released", tag)
242        }
243    }
244
245    pub fn wheel_scrolled(tag: String, delta_x: f32, delta_y: f32, unit: &str) -> Self {
246        Self {
247            data: Some(serde_json::json!({
248                "delta_x": sanitize_f32(delta_x),
249                "delta_y": sanitize_f32(delta_y),
250                "unit": unit,
251            })),
252            ..Self::tagged("wheel_scrolled", tag)
253        }
254    }
255
256    // -----------------------------------------------------------------------
257    // Touch events
258    // -----------------------------------------------------------------------
259
260    fn touch_event(family: &str, tag: String, finger_id: u64, x: f32, y: f32) -> Self {
261        Self {
262            data: Some(serde_json::json!({
263                "finger_id": finger_id,
264                "x": sanitize_f32(x),
265                "y": sanitize_f32(y),
266            })),
267            ..Self::tagged(family, tag)
268        }
269    }
270
271    pub fn finger_pressed(tag: String, finger_id: u64, x: f32, y: f32) -> Self {
272        Self::touch_event("finger_pressed", tag, finger_id, x, y)
273    }
274
275    pub fn finger_moved(tag: String, finger_id: u64, x: f32, y: f32) -> Self {
276        Self::touch_event("finger_moved", tag, finger_id, x, y)
277    }
278
279    pub fn finger_lifted(tag: String, finger_id: u64, x: f32, y: f32) -> Self {
280        Self::touch_event("finger_lifted", tag, finger_id, x, y)
281    }
282
283    pub fn finger_lost(tag: String, finger_id: u64, x: f32, y: f32) -> Self {
284        Self::touch_event("finger_lost", tag, finger_id, x, y)
285    }
286
287    // -----------------------------------------------------------------------
288    // IME events
289    // -----------------------------------------------------------------------
290
291    pub fn ime_opened(tag: String) -> Self {
292        Self {
293            data: Some(serde_json::json!({"kind": "opened"})),
294            ..Self::tagged("ime", tag)
295        }
296    }
297
298    pub fn ime_preedit(tag: String, text: String, cursor: Option<std::ops::Range<usize>>) -> Self {
299        let cursor_val = cursor
300            .map(|r| serde_json::json!({"start": r.start, "end": r.end}))
301            .unwrap_or(serde_json::Value::Null);
302        Self {
303            data: Some(serde_json::json!({"kind": "preedit", "text": text, "cursor": cursor_val})),
304            ..Self::tagged("ime", tag)
305        }
306    }
307
308    pub fn ime_commit(tag: String, text: String) -> Self {
309        Self {
310            data: Some(serde_json::json!({"kind": "commit", "text": text})),
311            ..Self::tagged("ime", tag)
312        }
313    }
314
315    pub fn ime_closed(tag: String) -> Self {
316        Self {
317            data: Some(serde_json::json!({"kind": "closed"})),
318            ..Self::tagged("ime", tag)
319        }
320    }
321
322    // -----------------------------------------------------------------------
323    // Window lifecycle events
324    // -----------------------------------------------------------------------
325
326    pub fn window_opened(
327        tag: String,
328        window_id: String,
329        position: Option<(f32, f32)>,
330        width: f32,
331        height: f32,
332        scale_factor: f32,
333    ) -> Self {
334        let pos =
335            position.map(|(x, y)| serde_json::json!({"x": sanitize_f32(x), "y": sanitize_f32(y)}));
336        Self {
337            data: Some(serde_json::json!({
338                "window_id": window_id,
339                "position": pos,
340                "width": sanitize_f32(width),
341                "height": sanitize_f32(height),
342                "scale_factor": sanitize_f32(scale_factor),
343            })),
344            ..Self::tagged("window_opened", tag)
345        }
346    }
347
348    /// Window event carrying only a window_id in its data payload.
349    fn window_event(family: &str, tag: String, window_id: String) -> Self {
350        Self {
351            data: Some(serde_json::json!({"window_id": window_id})),
352            ..Self::tagged(family, tag)
353        }
354    }
355
356    pub fn window_closed(tag: String, window_id: String) -> Self {
357        Self::window_event("window_closed", tag, window_id)
358    }
359
360    pub fn window_close_requested(tag: String, window_id: String) -> Self {
361        Self::window_event("window_close_requested", tag, window_id)
362    }
363
364    pub fn window_moved(tag: String, window_id: String, x: f32, y: f32) -> Self {
365        Self {
366            data: Some(serde_json::json!({
367                "window_id": window_id,
368                "x": sanitize_f32(x),
369                "y": sanitize_f32(y),
370            })),
371            ..Self::tagged("window_moved", tag)
372        }
373    }
374
375    pub fn window_resized(tag: String, window_id: String, width: f32, height: f32) -> Self {
376        Self {
377            data: Some(serde_json::json!({
378                "window_id": window_id,
379                "width": sanitize_f32(width),
380                "height": sanitize_f32(height),
381            })),
382            ..Self::tagged("window_resized", tag)
383        }
384    }
385
386    pub fn window_focused(tag: String, window_id: String) -> Self {
387        Self::window_event("window_focused", tag, window_id)
388    }
389
390    pub fn window_unfocused(tag: String, window_id: String) -> Self {
391        Self::window_event("window_unfocused", tag, window_id)
392    }
393
394    pub fn window_rescaled(tag: String, window_id: String, scale_factor: f32) -> Self {
395        Self {
396            data: Some(serde_json::json!({
397                "window_id": window_id,
398                "scale_factor": sanitize_f32(scale_factor),
399            })),
400            ..Self::tagged("window_rescaled", tag)
401        }
402    }
403
404    pub fn file_hovered(tag: String, window_id: String, path: String) -> Self {
405        Self {
406            data: Some(serde_json::json!({
407                "window_id": window_id,
408                "path": path,
409            })),
410            ..Self::tagged("file_hovered", tag)
411        }
412    }
413
414    pub fn file_dropped(tag: String, window_id: String, path: String) -> Self {
415        Self {
416            data: Some(serde_json::json!({
417                "window_id": window_id,
418                "path": path,
419            })),
420            ..Self::tagged("file_dropped", tag)
421        }
422    }
423
424    pub fn files_hovered_left(tag: String, window_id: String) -> Self {
425        Self::window_event("files_hovered_left", tag, window_id)
426    }
427
428    // -----------------------------------------------------------------------
429    // Animation / theme / system events
430    // -----------------------------------------------------------------------
431
432    pub fn animation_frame(tag: String, timestamp_millis: u128) -> Self {
433        Self {
434            data: Some(serde_json::json!({"timestamp": timestamp_millis})),
435            ..Self::tagged("animation_frame", tag)
436        }
437    }
438
439    pub fn theme_changed(tag: String, mode: String) -> Self {
440        Self {
441            value: Some(Value::String(mode)),
442            ..Self::tagged("theme_changed", tag)
443        }
444    }
445
446    // -----------------------------------------------------------------------
447    // Sensor events
448    // -----------------------------------------------------------------------
449
450    pub fn sensor_resize(id: String, width: f32, height: f32) -> Self {
451        Self {
452            data: Some(
453                serde_json::json!({"width": sanitize_f32(width), "height": sanitize_f32(height)}),
454            ),
455            ..Self::bare("sensor_resize", id)
456        }
457    }
458
459    // -----------------------------------------------------------------------
460    // Canvas events
461    // -----------------------------------------------------------------------
462
463    pub fn canvas_press(id: String, x: f32, y: f32, button: String) -> Self {
464        Self {
465            data: Some(
466                serde_json::json!({"x": sanitize_f32(x), "y": sanitize_f32(y), "button": button}),
467            ),
468            ..Self::bare("canvas_press", id)
469        }
470    }
471
472    pub fn canvas_release(id: String, x: f32, y: f32, button: String) -> Self {
473        Self {
474            data: Some(
475                serde_json::json!({"x": sanitize_f32(x), "y": sanitize_f32(y), "button": button}),
476            ),
477            ..Self::bare("canvas_release", id)
478        }
479    }
480
481    pub fn canvas_move(id: String, x: f32, y: f32) -> Self {
482        Self {
483            data: Some(serde_json::json!({"x": sanitize_f32(x), "y": sanitize_f32(y)})),
484            ..Self::bare("canvas_move", id)
485        }
486    }
487
488    pub fn canvas_scroll(id: String, x: f32, y: f32, delta_x: f32, delta_y: f32) -> Self {
489        Self {
490            data: Some(
491                serde_json::json!({"x": sanitize_f32(x), "y": sanitize_f32(y), "delta_x": sanitize_f32(delta_x), "delta_y": sanitize_f32(delta_y)}),
492            ),
493            ..Self::bare("canvas_scroll", id)
494        }
495    }
496
497    // -----------------------------------------------------------------------
498    // MouseArea events
499    // -----------------------------------------------------------------------
500
501    pub fn mouse_right_press(id: String) -> Self {
502        Self::bare("mouse_right_press", id)
503    }
504
505    pub fn mouse_right_release(id: String) -> Self {
506        Self::bare("mouse_right_release", id)
507    }
508
509    pub fn mouse_middle_press(id: String) -> Self {
510        Self::bare("mouse_middle_press", id)
511    }
512
513    pub fn mouse_middle_release(id: String) -> Self {
514        Self::bare("mouse_middle_release", id)
515    }
516
517    pub fn mouse_double_click(id: String) -> Self {
518        Self::bare("mouse_double_click", id)
519    }
520
521    pub fn mouse_enter(id: String) -> Self {
522        Self::bare("mouse_enter", id)
523    }
524
525    pub fn mouse_exit(id: String) -> Self {
526        Self::bare("mouse_exit", id)
527    }
528
529    pub fn mouse_area_move(id: String, x: f32, y: f32) -> Self {
530        Self {
531            data: Some(serde_json::json!({"x": sanitize_f32(x), "y": sanitize_f32(y)})),
532            ..Self::bare("mouse_move", id)
533        }
534    }
535
536    pub fn mouse_area_scroll(id: String, delta_x: f32, delta_y: f32) -> Self {
537        Self {
538            data: Some(
539                serde_json::json!({"delta_x": sanitize_f32(delta_x), "delta_y": sanitize_f32(delta_y)}),
540            ),
541            ..Self::bare("mouse_scroll", id)
542        }
543    }
544
545    // -----------------------------------------------------------------------
546    // PaneGrid events
547    // -----------------------------------------------------------------------
548
549    pub fn pane_resized(id: String, split: String, ratio: f32) -> Self {
550        Self {
551            data: Some(serde_json::json!({"split": split, "ratio": sanitize_f32(ratio)})),
552            ..Self::bare("pane_resized", id)
553        }
554    }
555
556    pub fn pane_dragged(
557        id: String,
558        kind: &str,
559        pane: String,
560        target: Option<String>,
561        region: Option<&str>,
562        edge: Option<&str>,
563    ) -> Self {
564        let mut data = serde_json::json!({"action": kind, "pane": pane});
565        if let Some(t) = target {
566            data["target"] = serde_json::json!(t);
567        }
568        if let Some(r) = region {
569            data["region"] = serde_json::json!(r);
570        }
571        if let Some(e) = edge {
572            data["edge"] = serde_json::json!(e);
573        }
574        Self {
575            data: Some(data),
576            ..Self::bare("pane_dragged", id)
577        }
578    }
579
580    pub fn pane_clicked(id: String, pane: String) -> Self {
581        Self {
582            data: Some(serde_json::json!({"pane": pane})),
583            ..Self::bare("pane_clicked", id)
584        }
585    }
586
587    pub fn pane_focus_cycle(id: String, pane: String) -> Self {
588        Self {
589            data: Some(serde_json::json!({"pane": pane})),
590            ..Self::bare("pane_focus_cycle", id)
591        }
592    }
593
594    // -----------------------------------------------------------------------
595    // TextInput paste event
596    // -----------------------------------------------------------------------
597
598    pub fn paste(id: String, text: String) -> Self {
599        Self {
600            value: Some(Value::String(text)),
601            ..Self::bare("paste", id)
602        }
603    }
604
605    // -----------------------------------------------------------------------
606    // Scripting key events (no full KeyEventData available)
607    // -----------------------------------------------------------------------
608
609    /// Key press event from scripting (no full KeyEventData).
610    pub fn scripting_key_press(key: String, modifiers_json: Value) -> Self {
611        Self {
612            value: Some(Value::String(key)),
613            data: Some(serde_json::json!({"modifiers": modifiers_json})),
614            ..Self::bare("key_press", String::new())
615        }
616    }
617
618    /// Key release event from scripting (no full KeyEventData).
619    pub fn scripting_key_release(key: String, modifiers_json: Value) -> Self {
620        Self {
621            value: Some(Value::String(key)),
622            data: Some(serde_json::json!({"modifiers": modifiers_json})),
623            ..Self::bare("key_release", String::new())
624        }
625    }
626
627    /// Cursor moved event from scripting.
628    pub fn scripting_cursor_moved(x: f64, y: f64) -> Self {
629        Self {
630            data: Some(serde_json::json!({"x": x, "y": y})),
631            ..Self::bare("cursor_moved", String::new())
632        }
633    }
634
635    /// Scroll event from scripting.
636    pub fn scripting_scroll(delta_x: f64, delta_y: f64) -> Self {
637        Self {
638            data: Some(serde_json::json!({"delta_x": delta_x, "delta_y": delta_y})),
639            ..Self::bare("scroll", String::new())
640        }
641    }
642
643    // -----------------------------------------------------------------------
644    // ComboBox option hovered event
645    // -----------------------------------------------------------------------
646
647    pub fn option_hovered(id: String, value: String) -> Self {
648        Self {
649            value: Some(Value::String(value)),
650            ..Self::bare("option_hovered", id)
651        }
652    }
653
654    // -----------------------------------------------------------------------
655    // Scrollable events
656    // -----------------------------------------------------------------------
657
658    #[allow(clippy::too_many_arguments)]
659    pub fn scroll(
660        id: String,
661        abs_x: f32,
662        abs_y: f32,
663        rel_x: f32,
664        rel_y: f32,
665        bounds_w: f32,
666        bounds_h: f32,
667        content_w: f32,
668        content_h: f32,
669    ) -> Self {
670        Self {
671            data: Some(serde_json::json!({
672                "absolute_x": sanitize_f32(abs_x), "absolute_y": sanitize_f32(abs_y),
673                "relative_x": sanitize_f32(rel_x), "relative_y": sanitize_f32(rel_y),
674                "bounds_width": sanitize_f32(bounds_w), "bounds_height": sanitize_f32(bounds_h),
675                "content_width": sanitize_f32(content_w), "content_height": sanitize_f32(content_h),
676            })),
677            ..Self::bare("scroll", id)
678        }
679    }
680}
681
682// ---------------------------------------------------------------------------
683// Helpers
684// ---------------------------------------------------------------------------
685
686/// Replace non-finite f32 with 0.0 for safe JSON serialization.
687fn sanitize_f32(v: f32) -> f32 {
688    if v.is_finite() {
689        v
690    } else {
691        log::warn!("non-finite f32 ({v}) replaced with 0.0 in outgoing event");
692        0.0
693    }
694}
695
696/// Replace non-finite f64 with 0.0 for safe JSON serialization.
697fn sanitize_f64(v: f64) -> f64 {
698    if v.is_finite() {
699        v
700    } else {
701        log::warn!("non-finite f64 ({v}) replaced with 0.0 in outgoing event");
702        0.0
703    }
704}
705
706// ---------------------------------------------------------------------------
707// Response types (serialized to stdout in reply to incoming messages)
708// ---------------------------------------------------------------------------
709
710/// Response to an effect request, written to stdout as JSONL.
711#[derive(Debug, Serialize)]
712pub struct EffectResponse {
713    #[serde(rename = "type")]
714    pub message_type: &'static str,
715    pub session: String,
716    pub id: String,
717    pub status: &'static str,
718    #[serde(skip_serializing_if = "Option::is_none")]
719    pub result: Option<Value>,
720    #[serde(skip_serializing_if = "Option::is_none")]
721    pub error: Option<String>,
722}
723
724impl EffectResponse {
725    /// The effect completed successfully with the given result.
726    pub fn ok(id: String, result: Value) -> Self {
727        Self {
728            message_type: "effect_response",
729            session: String::new(),
730            id,
731            status: "ok",
732            result: Some(result),
733            error: None,
734        }
735    }
736
737    /// The effect failed with the given reason.
738    pub fn error(id: String, reason: String) -> Self {
739        Self {
740            message_type: "effect_response",
741            session: String::new(),
742            id,
743            status: "error",
744            result: None,
745            error: Some(reason),
746        }
747    }
748
749    /// The requested effect kind is not supported.
750    pub fn unsupported(id: String) -> Self {
751        Self::error(id, "unsupported".to_string())
752    }
753
754    /// The user cancelled the operation (e.g. closed a file dialog).
755    /// Distinct from `error` -- cancellation is a normal user action,
756    /// not a failure.
757    pub fn cancelled(id: String) -> Self {
758        Self {
759            message_type: "effect_response",
760            session: String::new(),
761            id,
762            status: "cancelled",
763            result: None,
764            error: None,
765        }
766    }
767
768    /// Set the session ID for this response.
769    pub fn with_session(mut self, session: impl Into<String>) -> Self {
770        self.session = session.into();
771        self
772    }
773}
774
775/// Response to a Query message.
776#[derive(Debug, Serialize)]
777pub struct QueryResponse {
778    #[serde(rename = "type")]
779    pub message_type: &'static str,
780    pub session: String,
781    pub id: String,
782    pub target: String,
783    pub data: Value,
784}
785
786impl QueryResponse {
787    pub fn new(id: String, target: String, data: Value) -> Self {
788        Self {
789            message_type: "query_response",
790            session: String::new(),
791            id,
792            target,
793            data,
794        }
795    }
796
797    /// Set the session ID for this response.
798    pub fn with_session(mut self, session: impl Into<String>) -> Self {
799        self.session = session.into();
800        self
801    }
802}
803
804/// Response to an Interact message.
805#[derive(Debug, Serialize)]
806pub struct InteractResponse {
807    #[serde(rename = "type")]
808    pub message_type: &'static str,
809    pub session: String,
810    pub id: String,
811    pub events: Vec<OutgoingEvent>,
812}
813
814impl InteractResponse {
815    pub fn new(id: String, events: Vec<OutgoingEvent>) -> Self {
816        Self {
817            message_type: "interact_response",
818            session: String::new(),
819            id,
820            events,
821        }
822    }
823
824    /// Set the session ID for this response and all contained events.
825    pub fn with_session(mut self, session: impl Into<String>) -> Self {
826        let session = session.into();
827        for event in &mut self.events {
828            event.session.clone_from(&session);
829        }
830        self.session = session;
831        self
832    }
833}
834
835/// Response to a TreeHash message.
836///
837/// Tree hashes capture structural tree data (hash of JSON tree). No pixel data.
838/// For pixel data, see the `screenshot_response` message type.
839#[derive(Debug, Serialize)]
840#[allow(dead_code)]
841pub struct TreeHashResponse {
842    #[serde(rename = "type")]
843    pub message_type: &'static str,
844    pub session: String,
845    pub id: String,
846    pub name: String,
847    pub hash: String,
848}
849
850#[allow(dead_code)]
851impl TreeHashResponse {
852    pub fn new(id: String, name: String, hash: String) -> Self {
853        Self {
854            message_type: "tree_hash_response",
855            session: String::new(),
856            id,
857            name,
858            hash,
859        }
860    }
861
862    /// Set the session ID for this response.
863    pub fn with_session(mut self, session: impl Into<String>) -> Self {
864        self.session = session.into();
865        self
866    }
867}
868
869/// Response to a Reset message.
870#[derive(Debug, Serialize)]
871pub struct ResetResponse {
872    #[serde(rename = "type")]
873    pub message_type: &'static str,
874    pub session: String,
875    pub id: String,
876    pub status: &'static str,
877}
878
879impl ResetResponse {
880    pub fn ok(id: String) -> Self {
881        Self {
882            message_type: "reset_response",
883            session: String::new(),
884            id,
885            status: "ok",
886        }
887    }
888
889    /// Set the session ID for this response.
890    pub fn with_session(mut self, session: impl Into<String>) -> Self {
891        self.session = session.into();
892        self
893    }
894}
895
896#[cfg(test)]
897mod tests {
898    use super::*;
899    use serde_json::json;
900
901    // -----------------------------------------------------------------------
902    // OutgoingEvent serialization -- widget events
903    // -----------------------------------------------------------------------
904
905    #[test]
906    fn serialize_click_event() {
907        let evt = OutgoingEvent::click("btn1".to_string());
908        let json = serde_json::to_value(&evt).unwrap();
909        assert_eq!(json["type"], "event");
910        assert_eq!(json["family"], "click");
911        assert_eq!(json["id"], "btn1");
912        assert!(json.get("value").is_none());
913        assert!(json.get("tag").is_none());
914        assert!(json.get("modifiers").is_none());
915    }
916
917    #[test]
918    fn serialize_input_event() {
919        let evt = OutgoingEvent::input("inp1".to_string(), "hello".to_string());
920        let json = serde_json::to_value(&evt).unwrap();
921        assert_eq!(json["family"], "input");
922        assert_eq!(json["id"], "inp1");
923        assert_eq!(json["value"], "hello");
924    }
925
926    #[test]
927    fn serialize_submit_event() {
928        let evt = OutgoingEvent::submit("form1".to_string(), "data".to_string());
929        let json = serde_json::to_value(&evt).unwrap();
930        assert_eq!(json["family"], "submit");
931        assert_eq!(json["value"], "data");
932    }
933
934    #[test]
935    fn serialize_toggle_event_true() {
936        let evt = OutgoingEvent::toggle("chk1".to_string(), true);
937        let json = serde_json::to_value(&evt).unwrap();
938        assert_eq!(json["family"], "toggle");
939        assert_eq!(json["value"], true);
940    }
941
942    #[test]
943    fn serialize_toggle_event_false() {
944        let evt = OutgoingEvent::toggle("chk1".to_string(), false);
945        let json = serde_json::to_value(&evt).unwrap();
946        assert_eq!(json["value"], false);
947    }
948
949    #[test]
950    fn serialize_slide_event() {
951        let evt = OutgoingEvent::slide("slider1".to_string(), 0.75);
952        let json = serde_json::to_value(&evt).unwrap();
953        assert_eq!(json["family"], "slide");
954        assert_eq!(json["value"], 0.75);
955    }
956
957    #[test]
958    fn serialize_slide_release_event() {
959        let evt = OutgoingEvent::slide_release("slider1".to_string(), 0.5);
960        let json = serde_json::to_value(&evt).unwrap();
961        assert_eq!(json["family"], "slide_release");
962        assert_eq!(json["value"], 0.5);
963    }
964
965    #[test]
966    fn serialize_select_event() {
967        let evt = OutgoingEvent::select("picker1".to_string(), "option_b".to_string());
968        let json = serde_json::to_value(&evt).unwrap();
969        assert_eq!(json["family"], "select");
970        assert_eq!(json["value"], "option_b");
971    }
972
973    // -----------------------------------------------------------------------
974    // OutgoingEvent serialization -- keyboard events
975    // -----------------------------------------------------------------------
976
977    fn make_key_event_data(key_str: &str, shift: bool, alt: bool) -> crate::message::KeyEventData {
978        use iced::keyboard;
979        crate::message::KeyEventData {
980            key: if key_str.len() == 1 {
981                keyboard::Key::Character(key_str.into())
982            } else {
983                keyboard::Key::Named(keyboard::key::Named::Escape)
984            },
985            modified_key: if key_str.len() == 1 {
986                keyboard::Key::Character(key_str.to_uppercase().into())
987            } else {
988                keyboard::Key::Named(keyboard::key::Named::Escape)
989            },
990            physical_key: keyboard::key::Physical::Code(keyboard::key::Code::KeyA),
991            location: keyboard::Location::Standard,
992            modifiers: {
993                let mut m = keyboard::Modifiers::empty();
994                if shift {
995                    m |= keyboard::Modifiers::SHIFT;
996                }
997                if alt {
998                    m |= keyboard::Modifiers::ALT;
999                }
1000                m
1001            },
1002            text: if key_str.len() == 1 {
1003                Some(key_str.to_string())
1004            } else {
1005                None
1006            },
1007            repeat: false,
1008            captured: false,
1009        }
1010    }
1011
1012    #[test]
1013    fn serialize_key_press_with_modifiers() {
1014        let data = make_key_event_data("a", true, true);
1015        let evt = OutgoingEvent::key_press("keys".to_string(), &data);
1016        let json = serde_json::to_value(&evt).unwrap();
1017        assert_eq!(json["family"], "key_press");
1018        assert_eq!(json["tag"], "keys");
1019        assert_eq!(json["value"], "a");
1020        assert!(json["id"].as_str().unwrap().is_empty());
1021        assert_eq!(json["modifiers"]["shift"], true);
1022        assert_eq!(json["modifiers"]["ctrl"], false);
1023        assert_eq!(json["modifiers"]["alt"], true);
1024        assert_eq!(json["modifiers"]["logo"], false);
1025        assert_eq!(json["modifiers"]["command"], false);
1026        // New fields (nested under "data")
1027        assert_eq!(json["data"]["modified_key"], "A");
1028        assert_eq!(json["data"]["physical_key"], "KeyA");
1029        assert_eq!(json["data"]["location"], "standard");
1030        assert_eq!(json["data"]["text"], "a");
1031        assert_eq!(json["data"]["repeat"], false);
1032    }
1033
1034    #[test]
1035    fn serialize_key_release() {
1036        let data = make_key_event_data("Escape", false, false);
1037        let evt = OutgoingEvent::key_release("keys".to_string(), &data);
1038        let json = serde_json::to_value(&evt).unwrap();
1039        assert_eq!(json["family"], "key_release");
1040        assert_eq!(json["value"], "Escape");
1041        // key_release should not have text or repeat
1042        assert!(json.get("text").is_none() || json["text"].is_null());
1043    }
1044
1045    #[test]
1046    fn serialize_modifiers_changed() {
1047        let mods = KeyModifiers {
1048            shift: true,
1049            ctrl: true,
1050            alt: false,
1051            logo: false,
1052            command: false,
1053        };
1054        let evt = OutgoingEvent::modifiers_changed("mods".to_string(), mods);
1055        let json = serde_json::to_value(&evt).unwrap();
1056        assert_eq!(json["family"], "modifiers_changed");
1057        assert!(json.get("value").is_none());
1058        assert_eq!(json["modifiers"]["shift"], true);
1059        assert_eq!(json["modifiers"]["ctrl"], true);
1060    }
1061
1062    // -----------------------------------------------------------------------
1063    // OutgoingEvent serialization -- mouse events
1064    // -----------------------------------------------------------------------
1065
1066    #[test]
1067    fn serialize_cursor_moved() {
1068        let evt = OutgoingEvent::cursor_moved("mouse".to_string(), 100.0, 200.0);
1069        let json = serde_json::to_value(&evt).unwrap();
1070        assert_eq!(json["family"], "cursor_moved");
1071        assert_eq!(json["data"]["x"], 100.0);
1072        assert_eq!(json["data"]["y"], 200.0);
1073    }
1074
1075    #[test]
1076    fn serialize_cursor_entered() {
1077        let evt = OutgoingEvent::cursor_entered("mouse".to_string());
1078        let json = serde_json::to_value(&evt).unwrap();
1079        assert_eq!(json["family"], "cursor_entered");
1080        assert_eq!(json["tag"], "mouse");
1081    }
1082
1083    #[test]
1084    fn serialize_cursor_left() {
1085        let evt = OutgoingEvent::cursor_left("mouse".to_string());
1086        let json = serde_json::to_value(&evt).unwrap();
1087        assert_eq!(json["family"], "cursor_left");
1088    }
1089
1090    #[test]
1091    fn serialize_button_pressed() {
1092        let evt = OutgoingEvent::button_pressed("mouse".to_string(), "Left".to_string());
1093        let json = serde_json::to_value(&evt).unwrap();
1094        assert_eq!(json["family"], "button_pressed");
1095        assert_eq!(json["value"], "Left");
1096    }
1097
1098    #[test]
1099    fn serialize_button_released() {
1100        let evt = OutgoingEvent::button_released("mouse".to_string(), "Right".to_string());
1101        let json = serde_json::to_value(&evt).unwrap();
1102        assert_eq!(json["family"], "button_released");
1103        assert_eq!(json["value"], "Right");
1104    }
1105
1106    #[test]
1107    fn serialize_wheel_scrolled() {
1108        let evt = OutgoingEvent::wheel_scrolled("mouse".to_string(), 0.0, -3.0, "line");
1109        let json = serde_json::to_value(&evt).unwrap();
1110        assert_eq!(json["family"], "wheel_scrolled");
1111        assert_eq!(json["data"]["delta_x"], 0.0);
1112        assert_eq!(json["data"]["delta_y"], -3.0);
1113        assert_eq!(json["data"]["unit"], "line");
1114    }
1115
1116    // -----------------------------------------------------------------------
1117    // OutgoingEvent serialization -- touch events
1118    // -----------------------------------------------------------------------
1119
1120    #[test]
1121    fn serialize_finger_pressed() {
1122        let evt = OutgoingEvent::finger_pressed("touch".to_string(), 1, 50.0, 75.0);
1123        let json = serde_json::to_value(&evt).unwrap();
1124        assert_eq!(json["family"], "finger_pressed");
1125        assert_eq!(json["data"]["finger_id"], 1);
1126        assert_eq!(json["data"]["x"], 50.0);
1127        assert_eq!(json["data"]["y"], 75.0);
1128    }
1129
1130    #[test]
1131    fn serialize_finger_moved() {
1132        let evt = OutgoingEvent::finger_moved("touch".to_string(), 2, 60.0, 80.0);
1133        let json = serde_json::to_value(&evt).unwrap();
1134        assert_eq!(json["family"], "finger_moved");
1135        assert_eq!(json["data"]["finger_id"], 2);
1136    }
1137
1138    #[test]
1139    fn serialize_finger_lifted() {
1140        let evt = OutgoingEvent::finger_lifted("touch".to_string(), 1, 55.0, 78.0);
1141        let json = serde_json::to_value(&evt).unwrap();
1142        assert_eq!(json["family"], "finger_lifted");
1143    }
1144
1145    #[test]
1146    fn serialize_finger_lost() {
1147        let evt = OutgoingEvent::finger_lost("touch".to_string(), 3, 0.0, 0.0);
1148        let json = serde_json::to_value(&evt).unwrap();
1149        assert_eq!(json["family"], "finger_lost");
1150        assert_eq!(json["data"]["finger_id"], 3);
1151    }
1152
1153    // -----------------------------------------------------------------------
1154    // OutgoingEvent serialization -- window lifecycle events
1155    // -----------------------------------------------------------------------
1156
1157    #[test]
1158    fn serialize_window_opened_with_position() {
1159        let evt = OutgoingEvent::window_opened(
1160            "win_events".to_string(),
1161            "main".to_string(),
1162            Some((10.0, 20.0)),
1163            800.0,
1164            600.0,
1165            2.0,
1166        );
1167        let json = serde_json::to_value(&evt).unwrap();
1168        assert_eq!(json["family"], "window_opened");
1169        assert_eq!(json["data"]["window_id"], "main");
1170        assert_eq!(json["data"]["width"], 800.0);
1171        assert_eq!(json["data"]["height"], 600.0);
1172        assert_eq!(json["data"]["position"]["x"], 10.0);
1173        assert_eq!(json["data"]["position"]["y"], 20.0);
1174        assert_eq!(json["data"]["scale_factor"], 2.0);
1175    }
1176
1177    #[test]
1178    fn serialize_window_opened_without_position() {
1179        let evt = OutgoingEvent::window_opened(
1180            "win_events".to_string(),
1181            "main".to_string(),
1182            None,
1183            1024.0,
1184            768.0,
1185            1.0,
1186        );
1187        let json = serde_json::to_value(&evt).unwrap();
1188        assert_eq!(json["family"], "window_opened");
1189        assert!(json["data"]["position"].is_null());
1190        assert_eq!(json["data"]["scale_factor"], 1.0);
1191    }
1192
1193    #[test]
1194    fn serialize_window_closed() {
1195        let evt = OutgoingEvent::window_closed("win_events".to_string(), "popup".to_string());
1196        let json = serde_json::to_value(&evt).unwrap();
1197        assert_eq!(json["family"], "window_closed");
1198        assert_eq!(json["data"]["window_id"], "popup");
1199    }
1200
1201    #[test]
1202    fn serialize_window_close_requested() {
1203        let evt =
1204            OutgoingEvent::window_close_requested("win_events".to_string(), "main".to_string());
1205        let json = serde_json::to_value(&evt).unwrap();
1206        assert_eq!(json["family"], "window_close_requested");
1207    }
1208
1209    #[test]
1210    fn serialize_window_moved() {
1211        let evt =
1212            OutgoingEvent::window_moved("win_events".to_string(), "main".to_string(), 50.0, 100.0);
1213        let json = serde_json::to_value(&evt).unwrap();
1214        assert_eq!(json["family"], "window_moved");
1215        assert_eq!(json["data"]["x"], 50.0);
1216        assert_eq!(json["data"]["y"], 100.0);
1217    }
1218
1219    #[test]
1220    fn serialize_window_resized() {
1221        let evt = OutgoingEvent::window_resized(
1222            "win_events".to_string(),
1223            "main".to_string(),
1224            1920.0,
1225            1080.0,
1226        );
1227        let json = serde_json::to_value(&evt).unwrap();
1228        assert_eq!(json["family"], "window_resized");
1229        assert_eq!(json["data"]["width"], 1920.0);
1230    }
1231
1232    #[test]
1233    fn serialize_window_focused() {
1234        let evt = OutgoingEvent::window_focused("win_events".to_string(), "main".to_string());
1235        let json = serde_json::to_value(&evt).unwrap();
1236        assert_eq!(json["family"], "window_focused");
1237    }
1238
1239    #[test]
1240    fn serialize_window_unfocused() {
1241        let evt = OutgoingEvent::window_unfocused("win_events".to_string(), "main".to_string());
1242        let json = serde_json::to_value(&evt).unwrap();
1243        assert_eq!(json["family"], "window_unfocused");
1244    }
1245
1246    #[test]
1247    fn serialize_window_rescaled() {
1248        let evt = OutgoingEvent::window_rescaled("win_events".to_string(), "main".to_string(), 2.0);
1249        let json = serde_json::to_value(&evt).unwrap();
1250        assert_eq!(json["family"], "window_rescaled");
1251        assert_eq!(json["data"]["scale_factor"], 2.0);
1252    }
1253
1254    #[test]
1255    fn serialize_file_hovered() {
1256        let evt = OutgoingEvent::file_hovered(
1257            "win_events".to_string(),
1258            "main".to_string(),
1259            "/tmp/a.txt".to_string(),
1260        );
1261        let json = serde_json::to_value(&evt).unwrap();
1262        assert_eq!(json["family"], "file_hovered");
1263        assert_eq!(json["data"]["path"], "/tmp/a.txt");
1264    }
1265
1266    #[test]
1267    fn serialize_file_dropped() {
1268        let evt = OutgoingEvent::file_dropped(
1269            "win_events".to_string(),
1270            "main".to_string(),
1271            "/tmp/b.txt".to_string(),
1272        );
1273        let json = serde_json::to_value(&evt).unwrap();
1274        assert_eq!(json["family"], "file_dropped");
1275        assert_eq!(json["data"]["path"], "/tmp/b.txt");
1276    }
1277
1278    #[test]
1279    fn serialize_files_hovered_left() {
1280        let evt = OutgoingEvent::files_hovered_left("win_events".to_string(), "main".to_string());
1281        let json = serde_json::to_value(&evt).unwrap();
1282        assert_eq!(json["family"], "files_hovered_left");
1283    }
1284
1285    // -----------------------------------------------------------------------
1286    // OutgoingEvent serialization -- sensor events
1287    // -----------------------------------------------------------------------
1288
1289    #[test]
1290    fn serialize_sensor_resize() {
1291        let evt = OutgoingEvent::sensor_resize("s1".to_string(), 100.0, 200.0);
1292        let json = serde_json::to_value(&evt).unwrap();
1293        assert_eq!(json["family"], "sensor_resize");
1294        assert_eq!(json["id"], "s1");
1295        assert_eq!(json["data"]["width"], 100.0);
1296        assert_eq!(json["data"]["height"], 200.0);
1297    }
1298
1299    // -----------------------------------------------------------------------
1300    // OutgoingEvent serialization -- canvas events
1301    // -----------------------------------------------------------------------
1302
1303    #[test]
1304    fn serialize_canvas_press() {
1305        let evt = OutgoingEvent::canvas_press("c1".to_string(), 10.0, 20.0, "Left".to_string());
1306        let json = serde_json::to_value(&evt).unwrap();
1307        assert_eq!(json["family"], "canvas_press");
1308        assert_eq!(json["data"]["x"], 10.0);
1309        assert_eq!(json["data"]["button"], "Left");
1310    }
1311
1312    #[test]
1313    fn serialize_canvas_release() {
1314        let evt = OutgoingEvent::canvas_release("c1".to_string(), 10.0, 20.0, "Left".to_string());
1315        let json = serde_json::to_value(&evt).unwrap();
1316        assert_eq!(json["family"], "canvas_release");
1317    }
1318
1319    #[test]
1320    fn serialize_canvas_move() {
1321        let evt = OutgoingEvent::canvas_move("c1".to_string(), 30.0, 40.0);
1322        let json = serde_json::to_value(&evt).unwrap();
1323        assert_eq!(json["family"], "canvas_move");
1324        assert_eq!(json["data"]["x"], 30.0);
1325        assert_eq!(json["data"]["y"], 40.0);
1326    }
1327
1328    #[test]
1329    fn serialize_canvas_scroll() {
1330        let evt = OutgoingEvent::canvas_scroll("c1".to_string(), 5.0, 5.0, 0.0, -1.0);
1331        let json = serde_json::to_value(&evt).unwrap();
1332        assert_eq!(json["family"], "canvas_scroll");
1333        assert_eq!(json["data"]["delta_y"], -1.0);
1334    }
1335
1336    // -----------------------------------------------------------------------
1337    // OutgoingEvent serialization -- mouse area events
1338    // -----------------------------------------------------------------------
1339
1340    #[test]
1341    fn serialize_mouse_right_press() {
1342        let evt = OutgoingEvent::mouse_right_press("zone".to_string());
1343        let json = serde_json::to_value(&evt).unwrap();
1344        assert_eq!(json["family"], "mouse_right_press");
1345        assert_eq!(json["id"], "zone");
1346    }
1347
1348    #[test]
1349    fn serialize_mouse_right_release() {
1350        let evt = OutgoingEvent::mouse_right_release("zone".to_string());
1351        let json = serde_json::to_value(&evt).unwrap();
1352        assert_eq!(json["family"], "mouse_right_release");
1353    }
1354
1355    #[test]
1356    fn serialize_mouse_middle_press() {
1357        let evt = OutgoingEvent::mouse_middle_press("zone".to_string());
1358        let json = serde_json::to_value(&evt).unwrap();
1359        assert_eq!(json["family"], "mouse_middle_press");
1360        assert_eq!(json["id"], "zone");
1361    }
1362
1363    #[test]
1364    fn serialize_mouse_middle_release() {
1365        let evt = OutgoingEvent::mouse_middle_release("zone".to_string());
1366        let json = serde_json::to_value(&evt).unwrap();
1367        assert_eq!(json["family"], "mouse_middle_release");
1368    }
1369
1370    #[test]
1371    fn serialize_mouse_double_click() {
1372        let evt = OutgoingEvent::mouse_double_click("zone".to_string());
1373        let json = serde_json::to_value(&evt).unwrap();
1374        assert_eq!(json["family"], "mouse_double_click");
1375    }
1376
1377    #[test]
1378    fn serialize_mouse_enter() {
1379        let evt = OutgoingEvent::mouse_enter("zone".to_string());
1380        let json = serde_json::to_value(&evt).unwrap();
1381        assert_eq!(json["family"], "mouse_enter");
1382    }
1383
1384    #[test]
1385    fn serialize_mouse_exit() {
1386        let evt = OutgoingEvent::mouse_exit("zone".to_string());
1387        let json = serde_json::to_value(&evt).unwrap();
1388        assert_eq!(json["family"], "mouse_exit");
1389    }
1390
1391    #[test]
1392    fn serialize_mouse_area_move() {
1393        let evt = OutgoingEvent::mouse_area_move("zone".to_string(), 10.5, 20.3);
1394        let json = serde_json::to_value(&evt).unwrap();
1395        assert_eq!(json["family"], "mouse_move");
1396        assert_eq!(json["id"], "zone");
1397        let data = &json["data"];
1398        assert!((data["x"].as_f64().unwrap() - 10.5).abs() < 0.01);
1399        assert!((data["y"].as_f64().unwrap() - 20.3).abs() < 0.01);
1400    }
1401
1402    #[test]
1403    fn serialize_mouse_area_scroll() {
1404        let evt = OutgoingEvent::mouse_area_scroll("zone".to_string(), 0.0, -3.0);
1405        let json = serde_json::to_value(&evt).unwrap();
1406        assert_eq!(json["family"], "mouse_scroll");
1407        assert_eq!(json["id"], "zone");
1408        assert_eq!(json["data"]["delta_x"], 0.0);
1409        assert_eq!(json["data"]["delta_y"], -3.0);
1410    }
1411
1412    // -----------------------------------------------------------------------
1413    // OutgoingEvent serialization -- pane grid events
1414    // -----------------------------------------------------------------------
1415
1416    #[test]
1417    fn serialize_pane_resized() {
1418        let evt = OutgoingEvent::pane_resized("pg1".to_string(), "split_0".to_string(), 0.5);
1419        let json = serde_json::to_value(&evt).unwrap();
1420        assert_eq!(json["family"], "pane_resized");
1421        assert_eq!(json["data"]["split"], "split_0");
1422        assert_eq!(json["data"]["ratio"], json!(0.5));
1423    }
1424
1425    #[test]
1426    fn serialize_pane_dragged_dropped() {
1427        let evt = OutgoingEvent::pane_dragged(
1428            "pg1".to_string(),
1429            "dropped",
1430            "pane_a".to_string(),
1431            Some("pane_b".to_string()),
1432            Some("center"),
1433            None,
1434        );
1435        let json = serde_json::to_value(&evt).unwrap();
1436        assert_eq!(json["family"], "pane_dragged");
1437        assert_eq!(json["data"]["action"], "dropped");
1438        assert_eq!(json["data"]["pane"], "pane_a");
1439        assert_eq!(json["data"]["target"], "pane_b");
1440        assert_eq!(json["data"]["region"], "center");
1441    }
1442
1443    #[test]
1444    fn serialize_pane_dragged_picked() {
1445        let evt = OutgoingEvent::pane_dragged(
1446            "pg1".to_string(),
1447            "picked",
1448            "pane_a".to_string(),
1449            None,
1450            None,
1451            None,
1452        );
1453        let json = serde_json::to_value(&evt).unwrap();
1454        assert_eq!(json["data"]["action"], "picked");
1455        assert_eq!(json["data"]["pane"], "pane_a");
1456        assert!(json["data"].get("target").is_none());
1457    }
1458
1459    #[test]
1460    fn serialize_pane_dragged_canceled() {
1461        let evt = OutgoingEvent::pane_dragged(
1462            "pg1".to_string(),
1463            "canceled",
1464            "pane_a".to_string(),
1465            None,
1466            None,
1467            None,
1468        );
1469        let json = serde_json::to_value(&evt).unwrap();
1470        assert_eq!(json["data"]["action"], "canceled");
1471    }
1472
1473    #[test]
1474    fn serialize_pane_focus_cycle() {
1475        let evt = OutgoingEvent::pane_focus_cycle("pg1".to_string(), "pane_a".to_string());
1476        let json = serde_json::to_value(&evt).unwrap();
1477        assert_eq!(json["family"], "pane_focus_cycle");
1478        assert_eq!(json["data"]["pane"], "pane_a");
1479    }
1480
1481    #[test]
1482    fn serialize_pane_clicked() {
1483        let evt = OutgoingEvent::pane_clicked("pg1".to_string(), "pane_x".to_string());
1484        let json = serde_json::to_value(&evt).unwrap();
1485        assert_eq!(json["family"], "pane_clicked");
1486        assert_eq!(json["data"]["pane"], "pane_x");
1487    }
1488
1489    // -----------------------------------------------------------------------
1490    // OutgoingEvent serialization -- animation/theme events
1491    // -----------------------------------------------------------------------
1492
1493    #[test]
1494    fn serialize_animation_frame() {
1495        let evt = OutgoingEvent::animation_frame("anim".to_string(), 16000);
1496        let json = serde_json::to_value(&evt).unwrap();
1497        assert_eq!(json["family"], "animation_frame");
1498        assert_eq!(json["data"]["timestamp"], 16000);
1499    }
1500
1501    #[test]
1502    fn serialize_theme_changed() {
1503        let evt = OutgoingEvent::theme_changed("theme".to_string(), "dark".to_string());
1504        let json = serde_json::to_value(&evt).unwrap();
1505        assert_eq!(json["family"], "theme_changed");
1506        assert_eq!(json["value"], "dark");
1507    }
1508
1509    // -----------------------------------------------------------------------
1510    // Round-trip: serialize then deserialize OutgoingEvent as generic Value
1511    // -----------------------------------------------------------------------
1512
1513    #[test]
1514    fn outgoing_event_roundtrip_all_fields_present() {
1515        let data = make_key_event_data("a", true, false);
1516        let evt = OutgoingEvent::key_press("kb".to_string(), &data);
1517        let serialized = serde_json::to_string(&evt).unwrap();
1518        let parsed: Value = serde_json::from_str(&serialized).unwrap();
1519        assert_eq!(parsed["type"], "event");
1520        assert_eq!(parsed["family"], "key_press");
1521        assert_eq!(parsed["value"], "a");
1522        assert_eq!(parsed["tag"], "kb");
1523        assert_eq!(parsed["modifiers"]["shift"], true);
1524        // Extra fields from KeyEventData (nested under "data")
1525        assert!(parsed["data"].get("modified_key").is_some());
1526        assert!(parsed["data"].get("physical_key").is_some());
1527        assert!(parsed["data"].get("location").is_some());
1528    }
1529
1530    #[test]
1531    fn ime_opened_event() {
1532        let evt = OutgoingEvent::ime_opened("ime_tag".to_string());
1533        let json = serde_json::to_value(&evt).unwrap();
1534        assert_eq!(json["type"], "event");
1535        assert_eq!(json["family"], "ime");
1536        assert_eq!(json["tag"], "ime_tag");
1537        assert_eq!(json["data"]["kind"], "opened");
1538    }
1539
1540    #[test]
1541    fn ime_preedit_event_with_cursor() {
1542        let evt =
1543            OutgoingEvent::ime_preedit("ime_tag".to_string(), "hello".to_string(), Some(2..5));
1544        let json = serde_json::to_value(&evt).unwrap();
1545        assert_eq!(json["family"], "ime");
1546        assert_eq!(json["data"]["kind"], "preedit");
1547        assert_eq!(json["data"]["text"], "hello");
1548        assert_eq!(json["data"]["cursor"]["start"], 2);
1549        assert_eq!(json["data"]["cursor"]["end"], 5);
1550    }
1551
1552    #[test]
1553    fn ime_preedit_event_without_cursor() {
1554        let evt = OutgoingEvent::ime_preedit("ime_tag".to_string(), "hi".to_string(), None);
1555        let json = serde_json::to_value(&evt).unwrap();
1556        assert_eq!(json["data"]["kind"], "preedit");
1557        assert_eq!(json["data"]["text"], "hi");
1558        assert!(json["data"]["cursor"].is_null());
1559    }
1560
1561    #[test]
1562    fn ime_commit_event() {
1563        let evt = OutgoingEvent::ime_commit("ime_tag".to_string(), "final".to_string());
1564        let json = serde_json::to_value(&evt).unwrap();
1565        assert_eq!(json["family"], "ime");
1566        assert_eq!(json["data"]["kind"], "commit");
1567        assert_eq!(json["data"]["text"], "final");
1568    }
1569
1570    #[test]
1571    fn ime_closed_event() {
1572        let evt = OutgoingEvent::ime_closed("ime_tag".to_string());
1573        let json = serde_json::to_value(&evt).unwrap();
1574        assert_eq!(json["family"], "ime");
1575        assert_eq!(json["data"]["kind"], "closed");
1576    }
1577
1578    #[test]
1579    fn outgoing_event_bare_omits_optional_fields() {
1580        let evt = OutgoingEvent::click("b".to_string());
1581        let serialized = serde_json::to_string(&evt).unwrap();
1582        // value, tag, modifiers, captured should all be absent from the JSON string
1583        assert!(!serialized.contains("\"value\""));
1584        assert!(!serialized.contains("\"tag\""));
1585        assert!(!serialized.contains("\"modifiers\""));
1586        assert!(!serialized.contains("\"captured\""));
1587    }
1588
1589    #[test]
1590    fn outgoing_event_with_captured_true() {
1591        let evt = OutgoingEvent::cursor_moved("m".to_string(), 1.0, 2.0).with_captured(true);
1592        let json = serde_json::to_value(&evt).unwrap();
1593        assert_eq!(json["captured"], true);
1594    }
1595
1596    #[test]
1597    fn outgoing_event_with_captured_false() {
1598        let evt =
1599            OutgoingEvent::key_press("kb".to_string(), &make_key_event_data("a", false, false))
1600                .with_captured(false);
1601        let json = serde_json::to_value(&evt).unwrap();
1602        assert_eq!(json["captured"], false);
1603    }
1604
1605    #[test]
1606    fn outgoing_event_without_captured_omits_field() {
1607        let evt = OutgoingEvent::click("btn".to_string());
1608        let json = serde_json::to_value(&evt).unwrap();
1609        assert!(json.get("captured").is_none());
1610    }
1611
1612    // -- float sanitization (outgoing context) --
1613
1614    #[test]
1615    fn outgoing_slide_with_nan_produces_zero() {
1616        let event = OutgoingEvent::slide("s1".to_string(), f64::NAN);
1617        // The value should be 0.0, not NaN
1618        let val = event.value.unwrap();
1619        assert_eq!(val.as_f64(), Some(0.0));
1620    }
1621
1622    #[test]
1623    fn outgoing_cursor_moved_with_infinity_produces_zero() {
1624        let event =
1625            OutgoingEvent::cursor_moved("tag".to_string(), f32::INFINITY, f32::NEG_INFINITY);
1626        let data = event.data.unwrap();
1627        assert_eq!(data["x"].as_f64(), Some(0.0));
1628        assert_eq!(data["y"].as_f64(), Some(0.0));
1629    }
1630
1631    // -- float sanitization --
1632
1633    #[test]
1634    fn sanitize_f32_passes_finite() {
1635        assert_eq!(sanitize_f32(1.5), 1.5);
1636        assert_eq!(sanitize_f32(-0.0), -0.0);
1637        assert_eq!(sanitize_f32(0.0), 0.0);
1638    }
1639
1640    #[test]
1641    fn sanitize_f32_replaces_nan() {
1642        assert_eq!(sanitize_f32(f32::NAN), 0.0);
1643    }
1644
1645    #[test]
1646    fn sanitize_f32_replaces_infinity() {
1647        assert_eq!(sanitize_f32(f32::INFINITY), 0.0);
1648        assert_eq!(sanitize_f32(f32::NEG_INFINITY), 0.0);
1649    }
1650
1651    #[test]
1652    fn sanitize_f64_passes_finite() {
1653        assert_eq!(sanitize_f64(42.0), 42.0);
1654    }
1655
1656    #[test]
1657    fn sanitize_f64_replaces_nan() {
1658        assert_eq!(sanitize_f64(f64::NAN), 0.0);
1659    }
1660
1661    #[test]
1662    fn sanitize_f64_replaces_infinity() {
1663        assert_eq!(sanitize_f64(f64::INFINITY), 0.0);
1664        assert_eq!(sanitize_f64(f64::NEG_INFINITY), 0.0);
1665    }
1666
1667    // -----------------------------------------------------------------------
1668    // EffectResponse serialization
1669    // -----------------------------------------------------------------------
1670
1671    #[test]
1672    fn effect_response_ok() {
1673        let resp = EffectResponse::ok("e1".to_string(), json!("clipboard content"));
1674        let json = serde_json::to_value(&resp).unwrap();
1675        assert_eq!(json["type"], "effect_response");
1676        assert_eq!(json["id"], "e1");
1677        assert_eq!(json["status"], "ok");
1678        assert_eq!(json["result"], "clipboard content");
1679        assert!(json.get("error").is_none());
1680    }
1681
1682    #[test]
1683    fn effect_response_error() {
1684        let resp = EffectResponse::error("e2".to_string(), "not found".to_string());
1685        let json = serde_json::to_value(&resp).unwrap();
1686        assert_eq!(json["type"], "effect_response");
1687        assert_eq!(json["id"], "e2");
1688        assert_eq!(json["status"], "error");
1689        assert_eq!(json["error"], "not found");
1690        assert!(json.get("result").is_none());
1691    }
1692
1693    #[test]
1694    fn effect_response_unsupported() {
1695        let resp = EffectResponse::unsupported("e3".to_string());
1696        let json = serde_json::to_value(&resp).unwrap();
1697        assert_eq!(json["status"], "error");
1698        assert_eq!(json["error"], "unsupported");
1699    }
1700
1701    #[test]
1702    fn effect_response_ok_with_object_result() {
1703        let resp = EffectResponse::ok("e4".to_string(), json!({"files": ["/a.txt", "/b.txt"]}));
1704        let json = serde_json::to_value(&resp).unwrap();
1705        assert_eq!(json["result"]["files"][0], "/a.txt");
1706        assert_eq!(json["result"]["files"][1], "/b.txt");
1707    }
1708}