Skip to main content

oxiui_core/
response.rs

1//! Response structs returned by the extended [`UiCtx`](crate::UiCtx) widgets.
2//!
3//! ## The `supported` contract
4//!
5//! Every extended widget method on [`UiCtx`](crate::UiCtx) (`text_input`,
6//! `checkbox`, `slider`, …) ships with a **default implementation that returns
7//! a response whose `supported` field is `false`**. An adapter that has not
8//! overridden a given widget therefore reports `supported == false` to the
9//! caller, instead of silently drawing nothing and lying about success. Callers
10//! can branch on `supported` to fall back gracefully (e.g. render their own
11//! control, or warn). Each response type carries a zero/identity state for its
12//! payload alongside the flag, and an [`unsupported`](CheckboxResponse::unsupported)
13//! constructor the defaults use.
14
15/// Result of a [`text_input`](crate::UiCtx::text_input) widget.
16#[derive(Clone, Debug, Default, PartialEq)]
17pub struct TextInputResponse {
18    /// Whether the text changed this frame.
19    pub changed: bool,
20    /// The current text contents.
21    pub text: String,
22    /// Whether the active adapter actually rendered this widget.
23    pub supported: bool,
24    /// Whether this text input currently has keyboard focus.
25    ///
26    /// # Headless-approximate note
27    ///
28    /// This field is a best-effort approximation. Backends that lack a real
29    /// focus mechanism (e.g. headless state-machine contexts) always report
30    /// `false`. Backends that wire iced focus events may set this accurately
31    /// in a future milestone.
32    pub focused: bool,
33}
34
35impl TextInputResponse {
36    /// The "not implemented by this adapter" response: empty text, not changed,
37    /// `supported = false`.
38    pub fn unsupported() -> Self {
39        Self {
40            changed: false,
41            text: String::new(),
42            supported: false,
43            focused: false,
44        }
45    }
46
47    /// A supported response carrying the current `text` and `changed` flag.
48    ///
49    /// `focused` defaults to `false` (headless-approximate). Use
50    /// [`TextInputResponse::supported_focused`] if you have accurate focus state.
51    pub fn supported(text: impl Into<String>, changed: bool) -> Self {
52        Self {
53            changed,
54            text: text.into(),
55            supported: true,
56            focused: false,
57        }
58    }
59
60    /// A supported response with an explicit `focused` state.
61    pub fn supported_focused(text: impl Into<String>, changed: bool, focused: bool) -> Self {
62        Self {
63            changed,
64            text: text.into(),
65            supported: true,
66            focused,
67        }
68    }
69}
70
71/// Result of a [`checkbox`](crate::UiCtx::checkbox) widget.
72#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
73pub struct CheckboxResponse {
74    /// Whether the checked state toggled this frame.
75    pub changed: bool,
76    /// The current checked state.
77    pub checked: bool,
78    /// Whether the active adapter actually rendered this widget.
79    pub supported: bool,
80}
81
82impl CheckboxResponse {
83    /// The "not implemented by this adapter" response.
84    pub fn unsupported() -> Self {
85        Self {
86            changed: false,
87            checked: false,
88            supported: false,
89        }
90    }
91
92    /// A supported response carrying the current `checked` and `changed` flags.
93    pub fn supported(checked: bool, changed: bool) -> Self {
94        Self {
95            changed,
96            checked,
97            supported: true,
98        }
99    }
100}
101
102/// Result of a [`slider`](crate::UiCtx::slider) widget.
103#[derive(Clone, Copy, Debug, Default, PartialEq)]
104pub struct SliderResponse {
105    /// Whether the value changed this frame.
106    pub changed: bool,
107    /// The current value.
108    pub value: f64,
109    /// Whether the active adapter actually rendered this widget.
110    pub supported: bool,
111}
112
113impl SliderResponse {
114    /// The "not implemented by this adapter" response: value `0.0`, not changed.
115    pub fn unsupported() -> Self {
116        Self {
117            changed: false,
118            value: 0.0,
119            supported: false,
120        }
121    }
122
123    /// A supported response carrying the current `value` and `changed` flag.
124    pub fn supported(value: f64, changed: bool) -> Self {
125        Self {
126            changed,
127            value,
128            supported: true,
129        }
130    }
131}
132
133/// Result of a [`dropdown`](crate::UiCtx::dropdown) widget.
134#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
135pub struct DropdownResponse {
136    /// Whether the selection changed this frame.
137    pub changed: bool,
138    /// The index of the currently selected option.
139    pub selected: usize,
140    /// Whether the active adapter actually rendered this widget.
141    pub supported: bool,
142}
143
144impl DropdownResponse {
145    /// The "not implemented by this adapter" response: selection `0`, not changed.
146    pub fn unsupported() -> Self {
147        Self {
148            changed: false,
149            selected: 0,
150            supported: false,
151        }
152    }
153
154    /// A supported response carrying the current `selected` and `changed` flag.
155    pub fn supported(selected: usize, changed: bool) -> Self {
156        Self {
157            changed,
158            selected,
159            supported: true,
160        }
161    }
162}
163
164/// A generic response for widgets with no interaction payload
165/// (`separator`, `spacer`, `image`, `tooltip`, `popup`, `modal`, `scroll_area`).
166///
167/// Carries only whether the adapter rendered it.
168#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
169pub struct WidgetResponse {
170    /// Whether the active adapter actually rendered this widget.
171    pub supported: bool,
172}
173
174impl WidgetResponse {
175    /// The "not implemented by this adapter" response (`supported = false`).
176    pub fn unsupported() -> Self {
177        Self { supported: false }
178    }
179
180    /// A response indicating the widget was rendered (`supported = true`).
181    pub fn supported() -> Self {
182        Self { supported: true }
183    }
184}
185
186#[cfg(test)]
187mod tests {
188    use super::*;
189
190    #[test]
191    fn unsupported_constructors_set_flag_false() {
192        assert!(!TextInputResponse::unsupported().supported);
193        assert!(!CheckboxResponse::unsupported().supported);
194        assert!(!SliderResponse::unsupported().supported);
195        assert!(!DropdownResponse::unsupported().supported);
196        assert!(!WidgetResponse::unsupported().supported);
197    }
198
199    #[test]
200    fn unsupported_payloads_are_zeroed() {
201        assert_eq!(TextInputResponse::unsupported().text, "");
202        assert!(!TextInputResponse::unsupported().changed);
203        assert!(!CheckboxResponse::unsupported().checked);
204        assert_eq!(SliderResponse::unsupported().value, 0.0);
205        assert_eq!(DropdownResponse::unsupported().selected, 0);
206    }
207
208    #[test]
209    fn supported_constructors_carry_payload() {
210        let t = TextInputResponse::supported("hi", true);
211        assert!(t.supported && t.changed && t.text == "hi");
212        let c = CheckboxResponse::supported(true, true);
213        assert!(c.supported && c.checked && c.changed);
214        let s = SliderResponse::supported(0.5, false);
215        assert!(s.supported && !s.changed && (s.value - 0.5).abs() < 1e-9);
216        let d = DropdownResponse::supported(3, true);
217        assert!(d.supported && d.changed && d.selected == 3);
218        assert!(WidgetResponse::supported().supported);
219    }
220
221    #[test]
222    fn defaults_match_unsupported_for_flag() {
223        // Derived Default leaves supported=false, matching unsupported()'s flag.
224        assert_eq!(
225            CheckboxResponse::default().supported,
226            CheckboxResponse::unsupported().supported
227        );
228        assert_eq!(WidgetResponse::default(), WidgetResponse::unsupported());
229    }
230}