1use saudade::{Color, Event, EventCtx, Painter, PopupRequest, Rect, Theme, Widget};
15
16type Place = Box<dyn Fn(Rect) -> Rect>;
18
19struct Child {
20 widget: Box<dyn Widget>,
21 place: Place,
22}
23
24pub struct Shell {
25 bounds: Rect,
26 background: Option<Color>,
27 children: Vec<Child>,
28 overlays: Vec<Box<dyn Widget>>,
29 captured: Option<usize>,
30 focused: Option<usize>,
31}
32
33impl Shell {
34 pub fn new() -> Self {
35 Self {
36 bounds: Rect::new(0, 0, 0, 0),
37 background: Some(Color::LIGHT_GRAY),
38 children: Vec::new(),
39 overlays: Vec::new(),
40 captured: None,
41 focused: None,
42 }
43 }
44
45 pub fn no_background(mut self) -> Self {
49 self.background = None;
50 self
51 }
52
53 pub fn add(
56 mut self,
57 widget: impl Widget + 'static,
58 place: impl Fn(Rect) -> Rect + 'static,
59 ) -> Self {
60 self.children.push(Child {
61 widget: Box::new(widget),
62 place: Box::new(place),
63 });
64 self
65 }
66
67 pub fn add_overlay(mut self, widget: impl Widget + 'static) -> Self {
69 self.overlays.push(Box::new(widget));
70 self
71 }
72
73 pub fn focus_child(&mut self, index: usize) -> bool {
76 let Some(child) = self.children.get(index) else {
77 return false;
78 };
79 if !child.widget.focusable() {
80 return false;
81 }
82 if let Some(old) = self.focused
83 && old != index
84 && let Some(c) = self.children.get_mut(old)
85 {
86 c.widget.set_focused(false);
87 }
88 let focused = self.children[index].widget.focus_first();
89 if focused {
90 self.focused = Some(index);
91 }
92 focused
93 }
94
95 fn active_overlay(&self) -> Option<usize> {
96 self.overlays.iter().position(|o| o.captures_pointer())
97 }
98
99 fn choose_target(&self, event: &Event) -> Option<usize> {
100 if event.is_keyboard() {
101 return self.focused;
102 }
103 if let Some(idx) = self.captured {
104 return Some(idx);
105 }
106 let pos = event.position()?;
107 (0..self.children.len())
108 .rev()
109 .find(|&i| self.children[i].widget.bounds().contains(pos))
110 }
111
112 fn change_focus(&mut self, new_focus: Option<usize>, ctx: &mut EventCtx) {
113 if new_focus == self.focused {
114 return;
115 }
116 if let Some(old) = self.focused
117 && let Some(c) = self.children.get_mut(old)
118 {
119 c.widget.set_focused(false);
120 }
121 if let Some(new) = new_focus
122 && let Some(c) = self.children.get_mut(new)
123 {
124 c.widget.focus_first();
125 }
126 self.focused = new_focus;
127 ctx.request_paint();
128 }
129
130 fn focusable_count(&self) -> usize {
131 self.children
132 .iter()
133 .filter(|c| c.widget.focusable())
134 .count()
135 }
136
137 fn cycle_focus(&mut self, dir: i32, ctx: &mut EventCtx) -> bool {
138 let candidates: Vec<usize> = (0..self.children.len())
139 .filter(|&i| self.children[i].widget.focusable())
140 .collect();
141 if candidates.is_empty() {
142 return false;
143 }
144 let cur_pos = self
145 .focused
146 .and_then(|c| candidates.iter().position(|&i| i == c));
147 let n = candidates.len() as i32;
148 let next = match cur_pos {
149 None => {
150 if dir > 0 {
151 candidates[0]
152 } else {
153 candidates[(n - 1) as usize]
154 }
155 }
156 Some(p) => candidates[((p as i32 + dir).rem_euclid(n)) as usize],
157 };
158 if Some(next) == self.focused {
159 return false;
160 }
161 self.change_focus(Some(next), ctx);
162 true
163 }
164}
165
166impl Default for Shell {
167 fn default() -> Self {
168 Self::new()
169 }
170}
171
172impl Widget for Shell {
173 fn bounds(&self) -> Rect {
174 self.bounds
175 }
176
177 fn layout(&mut self, bounds: Rect) {
178 self.bounds = bounds;
179 for child in &mut self.children {
180 let rect = (child.place)(bounds);
181 child.widget.layout(rect);
182 }
183 for overlay in &mut self.overlays {
184 overlay.layout(bounds);
185 }
186 }
187
188 fn paint(&mut self, painter: &mut Painter, theme: &Theme) {
189 if let Some(background) = self.background {
190 painter.fill_rect(self.bounds, background);
191 }
192 for child in &mut self.children {
193 child.widget.paint(painter, theme);
194 }
195 for child in &mut self.children {
196 child.widget.paint_overlay(painter, theme);
197 }
198 for overlay in &mut self.overlays {
199 overlay.paint(painter, theme);
200 overlay.paint_overlay(painter, theme);
201 }
202 }
203
204 fn paint_overlay(&mut self, painter: &mut Painter, theme: &Theme) {
205 for child in &mut self.children {
206 child.widget.paint_overlay(painter, theme);
207 }
208 for overlay in &mut self.overlays {
209 overlay.paint_overlay(painter, theme);
210 }
211 }
212
213 fn event(&mut self, event: &Event, ctx: &mut EventCtx) {
214 if let Some(idx) = self.active_overlay() {
215 self.overlays[idx].event(event, ctx);
216 return;
217 }
218
219 if !event.is_keyboard() && event.position().is_none() && self.captured.is_none() {
220 for child in &mut self.children {
221 child.widget.event(event, ctx);
222 }
223 return;
224 }
225
226 if event.is_keyboard() {
227 let mut accelerator_blocking = false;
228 for (idx, child) in self.children.iter_mut().enumerate() {
229 if child.widget.accepts_accelerators() && Some(idx) != self.focused {
230 child.widget.event(event, ctx);
231 if ctx.is_consumed() {
232 return;
233 }
234 if child.widget.captures_pointer() {
235 accelerator_blocking = true;
236 }
237 }
238 }
239 if accelerator_blocking {
240 return;
241 }
242
243 match tab_action(event) {
244 Some(TabKind::Cycle(dir)) => {
245 if self.cycle_focus(dir, ctx) {
246 return;
247 }
248 }
249 Some(TabKind::Swallow) if self.focusable_count() >= 2 => return,
250 _ => {}
251 }
252 }
253
254 let Some(idx) = self.choose_target(event) else {
255 return;
256 };
257
258 let captured_was_set = self.captured == Some(idx);
259 {
260 let child = &mut self.children[idx];
261 child.widget.event(event, ctx);
262 if !event.is_keyboard() {
263 if child.widget.captures_pointer() {
264 self.captured = Some(idx);
265 } else if captured_was_set {
266 self.captured = None;
267 }
268 }
269 }
270
271 if ctx.is_focus_requested() {
272 ctx.clear_focus_flags();
273 self.change_focus(Some(idx), ctx);
274 } else if ctx.is_focus_released() {
275 ctx.clear_focus_flags();
276 if self.focused == Some(idx) {
277 self.change_focus(None, ctx);
278 }
279 }
280 }
281
282 fn captures_pointer(&self) -> bool {
283 self.captured.is_some() || self.active_overlay().is_some()
284 }
285
286 fn focusable(&self) -> bool {
287 self.children.iter().any(|c| c.widget.focusable())
288 }
289
290 fn focus_first(&mut self) -> bool {
291 for (idx, child) in self.children.iter_mut().enumerate() {
292 if child.widget.focus_first() {
293 self.focused = Some(idx);
294 return true;
295 }
296 }
297 false
298 }
299
300 fn popup_request(&self) -> Option<PopupRequest> {
301 for overlay in &self.overlays {
302 if let Some(req) = overlay.popup_request() {
303 return Some(req);
304 }
305 }
306 for child in &self.children {
307 if let Some(req) = child.widget.popup_request() {
308 return Some(req);
309 }
310 }
311 None
312 }
313
314 fn wants_ticks(&self) -> bool {
315 self.children.iter().any(|c| c.widget.wants_ticks())
316 || self.overlays.iter().any(|o| o.wants_ticks())
317 }
318}
319
320enum TabKind {
322 Cycle(i32),
323 Swallow,
324}
325
326fn tab_action(event: &Event) -> Option<TabKind> {
327 use saudade::{Key, NamedKey};
328 match event {
329 Event::KeyDown {
330 key: Key::Named(NamedKey::Tab),
331 modifiers,
332 } if !modifiers.control && !modifiers.alt && !modifiers.logo => {
333 Some(TabKind::Cycle(if modifiers.shift { -1 } else { 1 }))
334 }
335 Event::Char {
336 ch: '\t',
337 modifiers,
338 } if !modifiers.control && !modifiers.alt && !modifiers.logo => Some(TabKind::Swallow),
339 _ => None,
340 }
341}