1use crate::core::{Container, FocusCore};
2use crate::{Focus, FocusFlag, HasFocus, Navigation};
3use fxhash::FxBuildHasher;
4use ratatui_core::layout::Rect;
5use std::cell::Cell;
6use std::collections::HashSet;
7use std::ops::Range;
8
9macro_rules! focus_debug {
10 ($core:expr, $($arg:tt)+) => {
11 if $core.log.get() {
12 log::log!(log::Level::Debug, $($arg)+);
13 }
14 }
15}
16
17macro_rules! focus_fail {
18 ($core:expr, $($arg:tt)+) => {
19 if $core.log.get() {
20 log::log!(log::Level::Debug, $($arg)+);
21 }
22 if $core.insta_panic.get() {
23 panic!($($arg)+)
24 }
25 }
26}
27
28#[derive(Debug, Default)]
30pub struct FocusBuilder {
31 last: FocusCore,
32
33 log: Cell<bool>,
34 insta_panic: Cell<bool>,
35
36 z_base: u16,
44
45 focus_ids: HashSet<usize, FxBuildHasher>,
47 focus_flags: Vec<FocusFlag>,
48 duplicate: Vec<bool>,
49 areas: Vec<(Rect, u16)>,
50 navigable: Vec<Navigation>,
51 container_ids: HashSet<usize, FxBuildHasher>,
52 containers: Vec<(Container, Range<usize>)>,
53}
54
55impl FocusBuilder {
56 pub fn new(last: Option<Focus>) -> FocusBuilder {
64 if let Some(mut last) = last {
65 last.last.clear();
67
68 Self {
69 last: last.core,
70 log: Default::default(),
71 insta_panic: Default::default(),
72 z_base: 0,
73 focus_ids: last.last.focus_ids,
74 focus_flags: last.last.focus_flags,
75 duplicate: last.last.duplicate,
76 areas: last.last.areas,
77 navigable: last.last.navigable,
78 container_ids: last.last.container_ids,
79 containers: last.last.containers,
80 }
81 } else {
82 Self {
83 last: FocusCore::default(),
84 log: Default::default(),
85 insta_panic: Default::default(),
86 z_base: Default::default(),
87 focus_ids: Default::default(),
88 focus_flags: Default::default(),
89 duplicate: Default::default(),
90 areas: Default::default(),
91 navigable: Default::default(),
92 container_ids: Default::default(),
93 containers: Default::default(),
94 }
95 }
96 }
97
98 pub fn log_build_for(container: &dyn HasFocus) -> Focus {
100 let mut b = FocusBuilder::new(None);
101 b.enable_log();
102 b.widget(container);
103 b.build()
104 }
105
106 pub fn build_for(container: &dyn HasFocus) -> Focus {
119 let mut b = FocusBuilder::new(None);
120 b.widget(container);
121 b.build()
122 }
123
124 pub fn log_rebuild_for(container: &dyn HasFocus, old: Option<Focus>) -> Focus {
126 let mut b = FocusBuilder::new(old);
127 b.enable_log();
128 b.widget(container);
129 b.build()
130 }
131
132 pub fn rebuild_for(container: &dyn HasFocus, old: Option<Focus>) -> Focus {
139 let mut b = FocusBuilder::new(old);
140 b.widget(container);
141 b.build()
142 }
143
144 pub fn enable_log(&self) {
146 self.log.set(true);
147 }
148
149 pub fn disable_log(&self) {
151 self.log.set(false);
152 }
153
154 pub fn enable_panic(&self) {
156 self.insta_panic.set(true);
157 }
158
159 pub fn disable_panic(&self) {
161 self.insta_panic.set(false);
162 }
163
164 pub fn widget(&mut self, widget: &dyn HasFocus) -> &mut Self {
166 widget.build(self);
167 self
168 }
169
170 #[allow(clippy::collapsible_else_if)]
179 pub fn widget_navigate(&mut self, widget: &dyn HasFocus, navigation: Navigation) -> &mut Self {
180 widget.build_nav(navigation, self);
181
182 let widget_flag = widget.focus();
183 if let Some(idx) = self.focus_flags.iter().position(|v| *v == widget_flag) {
185 focus_debug!(
186 self,
187 "override navigation for {:?} with {:?}",
188 widget_flag,
189 navigation
190 );
191
192 self.navigable[idx] = navigation;
193 } else {
194 if self.container_ids.contains(&widget_flag.widget_id()) {
195 focus_fail!(
196 self,
197 "FAIL to override navigation for {:?}. This is a container.",
198 widget_flag,
199 );
200 } else {
201 focus_fail!(
202 self,
203 "FAIL to override navigation for {:?}. Widget doesn't use this focus-flag",
204 widget_flag,
205 );
206 }
207 }
208
209 self
210 }
211
212 #[inline]
214 pub fn widgets<const N: usize>(&mut self, widgets: [&dyn HasFocus; N]) -> &mut Self {
215 for widget in widgets {
216 widget.build(self);
217 }
218 self
219 }
220
221 #[must_use]
234 pub fn start(&mut self, container: &dyn HasFocus) -> FocusFlag {
235 self.start_with_flags(container.focus(), container.area(), container.area_z())
236 }
237
238 pub fn end(&mut self, tag: FocusFlag) {
240 focus_debug!(self, "end container {:?}", tag);
241 assert!(self.container_ids.contains(&tag.widget_id()));
242
243 for (c, r) in self.containers.iter_mut().rev() {
244 if c.container_flag != tag {
245 if !c.complete {
246 panic!("FocusBuilder: Unclosed container {:?}", c.container_flag);
247 }
248 } else {
249 r.end = self.focus_flags.len();
250 c.complete = true;
251
252 focus_debug!(self, "container range {:?}", r);
253
254 self.z_base -= c.delta_z;
255
256 break;
257 }
258 }
259 }
260
261 pub fn leaf_widget(&mut self, widget: &dyn HasFocus) -> &mut Self {
274 self.leaf_with_flags(
275 widget.focus(),
276 widget.area(),
277 widget.area_z(),
278 widget.navigable(),
279 );
280 self
281 }
282
283 #[deprecated(since = "2.0.2", note = "use leaf_with_flags instead")]
284 pub fn widget_with_flags(
285 &mut self,
286 focus: FocusFlag,
287 area: Rect,
288 area_z: u16,
289 navigable: Navigation,
290 ) {
291 self.leaf_with_flags(focus, area, area_z, navigable)
292 }
293
294 pub fn leaf_with_flags(
308 &mut self,
309 focus: FocusFlag,
310 area: Rect,
311 area_z: u16,
312 navigable: Navigation,
313 ) {
314 let duplicate = self.focus_ids.contains(&focus.widget_id());
315
316 if duplicate {
319 if !matches!(navigable, Navigation::Mouse | Navigation::None) {
320 focus_debug!(self, "{:#?}", self);
321 panic!(
322 "Duplicate flag is only allowed if the second call uses Navigation::Mouse|Navigation::None. Was {:?}.",
323 focus
324 )
325 }
326 }
327
328 focus_debug!(self, "widget {:?}", focus);
329
330 self.focus_ids.insert(focus.widget_id());
331 self.focus_flags.push(focus);
332 self.duplicate.push(duplicate);
333 self.areas.push((area, self.z_base + area_z));
334 self.navigable.push(navigable);
335 }
336
337 #[must_use]
346 pub fn start_with_flags(
347 &mut self,
348 container_flag: FocusFlag,
349 area: Rect,
350 area_z: u16,
351 ) -> FocusFlag {
352 focus_debug!(self, "start container {:?}", container_flag);
353
354 assert!(!self.container_ids.contains(&container_flag.widget_id()));
356
357 self.z_base += area_z;
358
359 let len = self.focus_flags.len();
360 self.container_ids.insert(container_flag.widget_id());
361 self.containers.push((
362 Container {
363 container_flag: container_flag.clone(),
364 area: (area, self.z_base),
365 delta_z: area_z,
366 complete: false,
367 },
368 len..len,
369 ));
370
371 container_flag
372 }
373
374 pub fn build(mut self) -> Focus {
380 for v in &self.last.focus_flags {
382 if !self.focus_ids.contains(&v.widget_id()) {
383 v.clear();
384 }
385 }
386 for (v, _) in &self.last.containers {
387 let have_container = self
388 .containers
389 .iter()
390 .any(|(c, _)| v.container_flag == c.container_flag);
391 if !have_container {
392 v.container_flag.clear();
393 }
394 }
395 self.last.clear();
396
397 for (c, _) in self.containers.iter_mut().rev() {
399 if !c.complete {
400 panic!("FocusBuilder: Unclosed container {:?}", c.container_flag);
401 }
402 }
403
404 let log = self.last.log.get();
405 let insta_panic = self.last.insta_panic.get();
406
407 Focus {
408 last: self.last,
409 core: FocusCore {
410 log: Cell::new(log),
411 insta_panic: Cell::new(insta_panic),
412 focus_ids: self.focus_ids,
413 focus_flags: self.focus_flags,
414 duplicate: self.duplicate,
415 areas: self.areas,
416 navigable: self.navigable,
417 container_ids: self.container_ids,
418 containers: self.containers,
419 },
420 }
421 }
422}