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}