feather_tui/container.rs
1use crate::{
2 callback::Callback, components as cpn, error::{FtuiError, FtuiResult},
3 renderer::Renderer, trigger::Trigger, util::id::IdGenerator
4};
5
6/// `Container` is a data structure used to store and organize UI components,
7/// including `Header`, `Option`, `Text`, `Separator`, and `Selector`.
8/// It is created using a `ContainerBuilder`.
9///
10/// # Usage
11/// - Handle UI events with the `looper` method.
12/// - Render the UI using a `Renderer` (recommended).
13/// - Alternatively, use the `draw` or `draw_fullscreen` methods.
14/// - Access `Option` components by ID using `option` and `option_mut`.
15/// - Access `Text` components by ID using `text` and `text_mut`.
16/// - Navigate using `selector_up`, `selector_down`, and `selector_select`.
17pub struct Container {
18 id_generator: IdGenerator<u16>,
19 header: Option<cpn::Header>,
20 options: Vec<cpn::Option>,
21 texts: Vec<cpn::Text>,
22 separators: Vec<cpn::Separator>,
23 selector: Option<cpn::Selector>,
24 component_count: u16,
25}
26
27impl Container {
28 /// Constructs a new `Container`.
29 ///
30 /// # Returns
31 /// `Container`: A new instance of `Container`.
32 ///
33 /// # Example
34 /// ```rust
35 /// let _ = Container::new();
36 /// ```
37 pub(crate) fn new() -> Container {
38 Container {
39 id_generator: IdGenerator::new(),
40 header: None,
41 options: vec![],
42 texts: vec![],
43 separators: vec![],
44 selector: None,
45 component_count: 0,
46 }
47 }
48
49 /// Updates the container and returns whether a change occurred.
50 ///
51 /// # Returns
52 /// - `Ok(bool)`: Returns whether an update occurred.
53 /// - `Err(FtuiError)`: Returns an error.
54 ///
55 /// # Example
56 /// ```rust
57 /// fn render() {
58 /// todo!();
59 /// }
60 ///
61 /// // Re-render the UI if an update occurred.
62 /// if container.looper()? {
63 /// render();
64 /// }
65 /// ```
66 pub fn looper(&mut self) -> FtuiResult<bool> {
67 if self.options.is_empty() {
68 return Ok(false);
69 }
70
71 self.selector
72 .as_mut()
73 .ok_or(FtuiError::ContainerLooperNoSelector)?
74 .looper(&mut self.options)
75 .or_else(|e| match e {
76 FtuiError::SelectorNoTriggers => Ok(false),
77 _ => Err(e),
78 })
79 }
80
81 pub(crate) fn set_header(&mut self, header: cpn::Header) {
82 self.header = Some(header);
83 self.component_count += 1;
84 }
85
86 pub(crate) fn set_selector(&mut self, selector: cpn::Selector) {
87 self.selector = Some(selector);
88 }
89
90 // Return added Option ID.
91 pub(crate) fn add_option(&mut self, mut option: cpn::Option) -> u16 {
92 let id = self.id_generator.get_id();
93 option.set_id(id);
94 option.set_line(self.component_count);
95
96 if self.options.is_empty() {
97 option.set_selc_on(true);
98 }
99
100 self.options.push(option);
101 self.component_count += 1;
102
103 id
104 }
105
106 // Return added Text ID.
107 pub(crate) fn add_text(&mut self, mut text: cpn::Text) -> u16 {
108 let id = self.id_generator.get_id();
109 text.set_id(id);
110 text.set_line(self.component_count);
111
112 self.texts.push(text);
113 self.component_count += 1;
114
115 id
116 }
117
118 pub(crate) fn add_separator(&mut self, mut separator: cpn::Separator) {
119 separator.set_line(self.component_count);
120 self.separators.push(separator);
121 self.component_count += 1;
122 }
123
124
125 /// Renders the `Container` using a temporary `Renderer`. This method is ideal
126 /// for quick, one-off renderings where performance isn't critical. Internally,
127 /// it creates a new `Renderer` with the given dimensions and uses it to draw
128 /// the `Container`. If you need to render multiple times, consider using a
129 /// persistent `Renderer` for better performance.
130 ///
131 ///
132 /// # Parameters
133 /// - `width`: The width of the rendering area.
134 /// - `height`: The height of the rendering area.
135 ///
136 /// # Returns
137 /// - `Ok(())`: Returns nothing.
138 /// - `Err(FtuiError)`: Returns an error.
139 ///
140 /// # Example
141 /// ```rust
142 /// // Good for quick one-off rendering like this.
143 /// fn render_ui() -> FtuiResult<()> {
144 /// ContainerBuilder::new()
145 /// .header(...)?
146 /// .text(...)?
147 /// .build()
148 /// .draw(40, 20)?; // Render with dimensions 40x20.
149 ///
150 /// Ok(())
151 /// }
152 /// ```
153 #[inline]
154 pub fn draw(&mut self, width: u16, height: u16) -> FtuiResult<()> {
155 Renderer::new(width, height).simple_draw(self)
156 }
157
158 /// Renders the `Container` using a temporary `Renderer`. This method is ideal
159 /// for quick, one-off renderings where performance isn't critical. Internally,
160 /// it creates a new fullscreen `Renderer` and uses it to draw the `Container`.
161 /// If you need to render multiple times, consider using a persistent `Renderer`
162 /// for better performance.
163 ///
164 /// # Returns
165 /// - `Ok(())`: Returns nothing.
166 /// - `Err(FtuiError)`: Returns an error.
167 ///
168 /// # Example
169 /// ```rust
170 /// // Good for quick one-off rendering like this.
171 /// fn render_ui() -> FtuiResult<()> {
172 /// ContainerBuilder::new()
173 /// .header(...)?
174 /// .text(...)?
175 /// .build()
176 /// .draw_fullscreen()?; // Render in fullscreen.
177 ///
178 /// Ok(())
179 /// }
180 /// ```
181 #[inline]
182 pub fn draw_fullscreen(&mut self) -> FtuiResult<()> {
183 Renderer::fullscreen()?.simple_draw(self)
184 }
185
186 /// Query an `Option` component by its ID (`O(n)` lookup).
187 ///
188 /// # Parameters
189 /// - `id`: The ID of the `Option` component to query.
190 ///
191 /// # Returns
192 /// - `Ok(&Option)`: A reference to the `Option` component.
193 /// - `Err(FtuiError)`: Returns an error.
194 ///
195 /// # Example
196 /// ```rust
197 /// // A mutable `u16` to store the ID of a `Option` component.
198 /// let mut option_id = 0;
199 ///
200 /// let container = ContainerBuilder::new()
201 /// .option_id(..., &mut option_id)?
202 /// .build();
203 ///
204 /// // Query the option by its ID.
205 /// container.option(option_id)?;
206 /// ```
207 #[inline]
208 pub fn option(&self, id: u16) -> FtuiResult<&cpn::Option> {
209 self.options.iter()
210 .find(|option| option.id() == id)
211 .ok_or(FtuiError::ContainerNoComponentById)
212 }
213
214 /// Query an `Option` component by its ID (`O(n)` lookup).
215 ///
216 /// # Parameters
217 /// - `id`: The ID of the `Option` component to query.
218 ///
219 /// # Returns
220 /// - `Ok(&Option)`: A mutable reference to the `Option` component.
221 /// - `Err(FtuiError)`: Returns an error.
222 ///
223 /// # Example
224 /// ```rust
225 /// // A mutable `u16` to store the ID of a `Option` component.
226 /// let mut option_id = 0;
227 ///
228 /// let container = ContainerBuilder::new()
229 /// .option_id(..., &mut option_id)?
230 /// .build();
231 ///
232 /// // Query the option by its ID.
233 /// container.option_mut(option_id)?;
234 /// ```
235 #[inline]
236 pub fn option_mut(&mut self, id: u16) -> FtuiResult<&mut cpn::Option> {
237 self.options.iter_mut()
238 .find(|option| option.id() == id)
239 .ok_or(FtuiError::ContainerNoComponentById)
240 }
241
242 /// Query an `Text` component by its ID (`O(n)` lookup).
243 ///
244 /// # Parameters
245 /// - `id`: The ID of the `Text` component to query.
246 ///
247 /// # Returns
248 /// - `Ok(&Option)`: A reference to the `Text` component.
249 /// - `Err(FtuiError)`: Returns an error.
250 ///
251 /// # Example
252 /// ```rust
253 /// // A mutable `u16` to store the ID of a `Text` component.
254 /// let mut text_id: u16 = 0;
255 ///
256 /// let container = ContainerBuilder::new()
257 /// .text_id(..., &mut text_id)?
258 /// .build();
259 ///
260 /// // Query the option by its ID.
261 /// container.text(text_id)?;
262 /// ```
263 #[inline]
264 pub fn text(&self, id: u16) -> FtuiResult<&cpn::Text> {
265 self.texts.iter()
266 .find(|text| text.id() == id)
267 .ok_or(FtuiError::ContainerNoComponentById)
268 }
269
270 /// Query an `Text` component by its ID (`O(n)` lookup).
271 ///
272 /// # Parameters
273 /// - `id`: The ID of the `Text` component to query.
274 ///
275 /// # Returns
276 /// - `Ok(&Option)`: A mutable reference to the `Text` component.
277 /// - `Err(FtuiError)`: Returns an error.
278 ///
279 /// # Example
280 /// ```rust
281 /// // A mutable `u16` to store the ID of a `Text` component.
282 /// let mut text_id: u16 = 0;
283 ///
284 /// let container = ContainerBuilder::new()
285 /// .text_id(..., &mut text_id)?
286 /// .build();
287 ///
288 /// // Query the option by its ID.
289 /// container.text_mut(text_id)?;
290 /// ```
291 #[inline]
292 pub fn text_mut(&mut self, id: u16) -> FtuiResult<&mut cpn::Text> {
293 self.texts.iter_mut()
294 .find(|text| text.id() == id)
295 .ok_or(FtuiError::ContainerNoComponentById)
296 }
297
298 /// Returns a mutable reference to the `Selector` component,
299 /// if the `Container` has one.
300 ///
301 /// # Returns
302 /// - `Ok(&mut Selector)`: A mutable reference to the `Selector` component.
303 /// - `Err(FtuiError)`: Return an error.
304 ///
305 /// # Example
306 /// ```rust
307 /// // Create a container with a `Selector`.
308 /// let mut container = ContainerBuilder::new()
309 /// .selector_no_triggers()
310 /// .build();
311 ///
312 /// // Get a mutable reference to the `Selector`.
313 /// container.selector_mut()?;
314 /// ```
315 #[inline]
316 pub fn selector_mut(&mut self) -> FtuiResult<&mut cpn::Selector> {
317 self.selector.as_mut()
318 .ok_or(FtuiError::ContainerNoSelector)
319 }
320
321 /// Attempts to move the `Selector` up by one position, if possible.
322 ///
323 /// # Returns
324 /// - `Ok(true)`: The selector moved up successfully.
325 /// - `Ok(false)`: The selector could not move (already at the top).
326 /// - `Err(FtuiError)`: Returns an error.
327 ///
328 /// # Example
329 /// ```rust
330 /// // Create a container with two `Option`s component and a `Selector`.
331 /// let mut container = ContainerBuilder::new()
332 /// .option(...)? // This is where the `Selector` will be when initialize.
333 /// .option(...)?
334 /// .selector_no_triggers()
335 /// .build();
336 ///
337 /// // The `Selector` cannot move up since it is at the top.
338 /// assert_eq!(container.selector_up()?, false);
339 /// ```
340 #[inline]
341 pub fn selector_up(&mut self) -> FtuiResult<bool> {
342 Ok(self.selector
343 .as_mut()
344 .ok_or(FtuiError::ContainerNoSelector)?
345 .up(&mut self.options))
346 }
347
348 /// Attempts to move the `Selector` down by one position, if possible.
349 ///
350 /// # Returns
351 /// - `Ok(true)`: The selector moved down successfully.
352 /// - `Ok(false)`: The selector could not move (already at the bottom).
353 /// - `Err(FtuiError)`: Returns an error.
354 ///
355 /// # Example
356 /// ```rust
357 /// // Create a container with two `Option`s component and a `Selector`.
358 /// let mut container = ContainerBuilder::new()
359 /// .option(...)? // This is where the `Selector` will be when initialize.
360 /// .option(...)?
361 /// .selector_no_triggers()
362 /// .build();
363 ///
364 /// // The `Selector` can move up since it is not at the bottom.
365 /// assert_eq!(container.selector_up()?, true);
366 /// ```
367 #[inline]
368 pub fn selector_down(&mut self) -> FtuiResult<bool> {
369 Ok(self.selector
370 .as_mut()
371 .ok_or(FtuiError::ContainerNoSelector)?
372 .down(&mut self.options))
373 }
374
375 /// Attempts to select the `Option` that the `Selector` is currently on.
376 /// This operation should always succeed unless an error occurs internally.
377 ///
378 /// # Returns
379 /// - `Ok(true)`: The selection was successful.
380 /// - `Err(FtuiError)`: An error occurred during selection.
381 ///
382 /// # Example
383 /// ```rust
384 /// // Create a container with on `Option` components and a `Selector`.
385 /// let mut container = ContainerBuilder::new()
386 /// .option(...)? // The `Selector` starts at this `Option`.
387 /// .selector_no_triggers()
388 /// .build();
389 ///
390 /// // Selecting the current `Option` is always possible unless an error occurs.
391 /// assert_eq!(container.selector_select()?, true);
392 /// ```
393 #[inline]
394 pub fn selector_select(&mut self) -> FtuiResult<bool> {
395 Ok(self.selector
396 .as_mut()
397 .ok_or(FtuiError::ContainerNoSelector)?
398 .select(&mut self.options)?)
399 }
400
401 pub(crate) fn component_count(&self) -> u16 {
402 self.component_count
403 }
404
405 pub(crate) fn header(&self) -> &Option<cpn::Header> {
406 &self.header
407 }
408
409 pub(crate) fn options(&self) -> &[cpn::Option] {
410 &self.options
411 }
412
413 pub(crate) fn texts_mut(&mut self) -> &mut [cpn::Text] {
414 &mut self.texts
415 }
416
417 pub(crate) fn separators(&self) -> &[cpn::Separator] {
418 &self.separators
419 }
420}
421
422/// `ContainerBuilder` is used to create `Container` instances using the builder
423/// pattern. This allows for a flexible and readable way to construct complex
424/// containers by chaining method calls.
425///
426/// # Example
427/// ```rust
428/// // Create a container with a header, two options, a separator, some text,
429/// // and a selector.
430/// let container: Container = ContainerBuilder::new()
431/// .header(...)?
432/// .option(...)?
433/// .option(...)?
434/// .separator_normal(...)
435/// .text(...)?
436/// .selector(...)?
437/// .build();
438/// ```
439pub struct ContainerBuilder {
440 container: Container,
441}
442
443impl ContainerBuilder {
444 /// Constructs a new `ContainerBuilder`.
445 ///
446 /// # Return
447 /// `ContainerBuilder`: A new instance of `ContainerBuilder`.
448 ///
449 /// # Example
450 /// ```rust
451 /// let _ = ContainerBuilder::new();
452 /// ```
453 #[inline]
454 pub fn new() -> Self {
455 ContainerBuilder { container: Container::new(), }
456 }
457
458 /// Explicitly sets a `Header` component for the `Container`. Unlike the `header`
459 /// method, which takes a label and internally constructs a `Header`, this
460 /// method allows you to directly provide a preconstructed `Header` component.
461 ///
462 /// # Parameters
463 /// - `header`: A `Header` component.
464 ///
465 /// # Returns
466 /// - `ContainerBuilder`: Returns `self`.
467 ///
468 /// # Example
469 /// ```rust
470 /// // Create a `Header` component.
471 /// let header = Header::new(...)?;
472 ///
473 /// // Set a preconstructed `Header` component.
474 /// ContainerBuilder::new()
475 /// .header_expl(header);
476 /// ```
477 pub fn header_expl(mut self, header: cpn::Header) -> Self {
478 self.container.set_header(header);
479 self
480 }
481
482 /// Sets a `Header` component for the `Container`.
483 ///
484 /// # Parameters
485 /// - `label`: A `&str` representing the text to display in the header.
486 ///
487 /// # Returns
488 /// - `Ok(ContainerBuilder)`: Returns `self`.
489 /// - `Err(FtuiError)`: Returns an error.
490 ///
491 /// # Example
492 /// ```rust
493 /// // Sets a `Header` component with the label "Welcome".
494 /// ContainerBuilder::new()
495 /// .header("Welcome")?;
496 /// ```
497 #[inline]
498 pub fn header(self, label: &str) -> FtuiResult<Self> {
499 Ok(self.header_expl(cpn::Header::new(label)?))
500 }
501
502 /// Explicitly add an `Option` component to the `Container`. Unlike the `option`
503 /// method, which takes a label and an optional callback to internally constructs
504 /// an `Option`, this method allows you to directly provide a preconstructed
505 /// `Option` component.
506 ///
507 /// # Parameters
508 /// - `option`: An `Option` component.
509 ///
510 /// # Returns
511 /// - `ContainerBuilder`: Returns `self`.
512 ///
513 /// # Example
514 /// ```rust
515 /// // Create an `Option` component.
516 /// let option = Option::new(...)?;
517 ///
518 /// // Set a preconstructed `Option` component.
519 /// ContainerBuilder::new()
520 /// .option_expl(option);
521 /// ```
522 pub fn option_expl(mut self, option: cpn::Option) -> Self {
523 self.container.add_option(option);
524 self
525 }
526
527 /// Adds an `Option` component to the `Container`.
528 ///
529 /// # Parameters
530 /// - `label`: A `&str` representing the text displayed for this option.
531 /// - `callback`: An optional `Callback` invoked when the option is selected.
532 ///
533 /// # Returns
534 /// - `Ok(ContainerBuilder)`: Returns `self`.
535 /// - `Err(FtuiError)`: Returns an error.
536 ///
537 /// # Example
538 /// ```rust
539 /// // Add an `Option` component with the label "Option" and no `Callback`.
540 /// ContainerBuilder::new()
541 /// .option("Option", None)?;
542 /// ```
543 #[inline]
544 pub fn option(
545 self, label: &str, callback: impl Into<Option<Callback>>
546 ) -> FtuiResult<Self> {
547 Ok(self.option_expl(cpn::Option::new(label, callback)?))
548 }
549
550 /// Explicitly add an `Option` component to the `Container` and stores its ID.
551 /// Unlike the `option_id` method, which takes a label and an optional callback
552 /// to internally constructs an `Option`, this method allows you to directly
553 /// provide a preconstructed `Option` component.
554 ///
555 /// # Parameters
556 /// - `option`: An `Option` component.
557 /// - `store_id`: A `&mut u16` to store the created `Option` component ID.
558 ///
559 /// # Returns
560 /// - `Ok(ContainerBuilder)`: Returns `self`.
561 /// - `Err(FtuiError)`: Returns an error.
562 ///
563 /// # Example
564 /// ```rust
565 /// let mut id = 0u16;
566 ///
567 /// // Create an `Option` component.
568 /// let option = Option::new(...)?;
569 ///
570 /// // Add the create `Option` component and storing the generated ID in `id`.
571 /// ContainerBuilder::new()
572 /// .option_id(option, &mut id)?;
573 /// ```
574 pub fn option_id_expl(
575 mut self, option: cpn::Option, store_id: &mut u16
576 ) -> Self {
577 *store_id = self.container.add_option(option);
578 self
579 }
580
581 /// Adds an `Option` component to the `Container` and stores its ID.
582 ///
583 /// # Parameters
584 /// - `label`: The text displayed for this option.
585 /// - `callback`: An optional `Callback` invoked when the option is selected.
586 /// - `store_id`: A `&mut u16` to store the created `Option` component ID.
587 ///
588 /// # Returns
589 /// - `Ok(ContainerBuilder)`: Returns `self`.
590 /// - `Err(FtuiError)`: Returns an error.
591 ///
592 /// # Example
593 /// ```rust
594 /// let mut id = 0u16;
595 ///
596 /// // Add an `Option` labeled "Option" with no `Callback`,
597 /// // storing the generated ID in `id`.
598 /// ContainerBuilder::new()
599 /// .option_id("Option", None, &mut id)?;
600 /// ```
601 #[inline]
602 pub fn option_id(
603 self,
604 label: &str, callback: impl Into<Option<Callback>>, store_id: &mut u16
605 ) -> FtuiResult<Self> {
606 Ok(self.option_id_expl(cpn::Option::new(label, callback)?, store_id))
607 }
608
609 /// Explicitly add a `Text` component to the `Container`. Unlike the `text`
610 /// method, which takes a label and flags to internally constructs an `Text`,
611 /// this method allows you to directly provide a preconstructed `Text` component.
612 ///
613 /// # Parameters
614 /// - `text`: A `Text` component.
615 ///
616 /// # Returns
617 /// - `ContainerBuilder`: Returns `self`.
618 ///
619 /// # Example
620 /// ```rust
621 /// // Create an `Text` component.
622 /// let text = Text::new(...)?;
623 ///
624 /// // Set a preconstructed `Text` component.
625 /// ContainerBuilder::new()
626 /// .text_expl(text);
627 /// ```
628 pub fn text_expl(mut self, text: cpn::Text) -> Self {
629 self.container.add_text(text);
630 self
631 }
632
633 /// Adds a `Text` component to the `Container`.
634 ///
635 /// # Parameters
636 /// - `label`: A `&str` representing the text to display.
637 /// - `flags`: A set of `TextFlags`, combined using the bitwise OR operator.
638 ///
639 /// # Notes
640 /// - This is what bitwise OR operator look like -> `flag1 | flag2 | flag3 ...`
641 ///
642 /// # Returns
643 /// - `Ok(ContainerBuilder)`: Returns `self`.
644 /// - `Err(FtuiError)`: Returns an error.
645 ///
646 /// # Example
647 /// ```rust
648 /// // Create a `Text` component labeled "Text", right-aligned and with
649 /// // a magenta background.
650 /// ContainerBuilder::new()
651 /// .text("Text", TextFlags::ALIGN_RIGHT | TextFlags::COLOR_MAGENTA_BACK)?;
652 /// ```
653 #[inline]
654 pub fn text(
655 self, label: &str, flags: impl Into<Option<cpn::TextFlags>>
656 ) -> FtuiResult<Self> {
657 Ok(self.text_expl(cpn::Text::new(label, flags)?))
658 }
659
660 /// Explicitly add a `Text` component to the `Container` and stores its ID.
661 /// Unlike the `text_id` method, which takes a label and flags to internally
662 /// constructs an `Text`, this method allows you to directly provide a
663 /// preconstructed `Text` component.
664 ///
665 /// # Parameters
666 /// - `text`: An `Text` component.
667 /// - `store_id`: A `&mut u16` to store the created `Option` component ID.
668 ///
669 /// # Returns
670 /// - `ContainerBuilder`: Returns `self`.
671 ///
672 /// # Example
673 /// ```rust
674 /// let mut id = 0u16;
675 ///
676 /// // Create an `Text` component.
677 /// let text = Text::new(...)?;
678 ///
679 /// // Add the create `Text` component and storing the generated ID in `id`.
680 /// ContainerBuilder::new()
681 /// .text_id_expl(text, &mut id)?;
682 /// ```
683 pub fn text_id_expl(mut self, text: cpn::Text, store_id: &mut u16) -> Self {
684 *store_id = self.container.add_text(text);
685 self
686 }
687
688 /// Adds a `Text` component to the `Container` and stores its ID.
689 ///
690 /// # Parameters
691 /// - `label`: A `&str` representing the text to display.
692 /// - `flags`: A set of `TextFlags`, combined using the bitwise OR operator.
693 /// - `store_id`: A `&mut u16` to store the created `Text` component ID.
694 ///
695 /// # Notes
696 /// - This is what bitwise OR operator look like -> `flag1 | flag2 | flag3 ...`
697 ///
698 /// # Returns
699 /// - `Ok(ContainerBuilder)`: Returns `self`.
700 /// - `Err(FtuiError)`: Returns an error.
701 ///
702 /// # Example
703 /// ```rust
704 /// let mut id = 0u16;
705 ///
706 /// // Create a `Text` component labeled "Text", right-aligned and with
707 /// // a magenta background. storing the generated ID in `id`.
708 /// ContainerBuilder::new()
709 /// .text(
710 /// "Text",
711 /// TextFlags::ALIGN_RIGHT | TextFlags::COLOR_MAGENTA_BACK, &mut id)?;
712 /// ```
713 #[inline]
714 pub fn text_id(
715 self,
716 label: &str, flags: impl Into<Option<cpn::TextFlags>>, store_id: &mut u16
717 ) -> FtuiResult<Self> {
718 Ok(self.text_id_expl(cpn::Text::new(label, flags)?, store_id))
719 }
720
721 /// Explicitly add a normal `Separator` component to the `Container`.
722 /// Unlike the `Separator` method, which takes a style and internally
723 /// constructs a `Separator`, this method allows you to directly provide a
724 /// preconstructed `Separator` component.
725 ///
726 /// # Parameters
727 /// - `separator`: A `Separator` component.
728 ///
729 /// # Returns
730 /// - `ContainerBuilder`: Returns `self`.
731 ///
732 /// # Example
733 /// ```rust
734 /// // Create a normal `Separator` component.
735 /// let separator = Separator::normal(...)?;
736 ///
737 /// // Set a preconstructed `Header` component.
738 /// ContainerBuilder::new()
739 /// .separator_normal_expl(separator);
740 /// ```
741 pub fn separator_normal_expl(mut self, separator: cpn::Separator) -> Self {
742 self.container.add_separator(separator);
743 self
744 }
745
746 /// Add a standard (non-dotted) `Separator` with the given style.
747 ///
748 /// # Parameters
749 /// - `style`: The visual style of the separator, specified as a `SeparatorStyle`.
750 ///
751 /// # Returns
752 /// - `ContainerBuilder`: Returns `self`.
753 ///
754 /// # Example
755 /// ```rust
756 /// // Add a normal separator with a solid style.
757 /// ContainerBuilder::new()
758 /// separator_normal(SeparatorStyle::Solid);
759 /// ```
760 #[inline]
761 pub fn separator_normal(self, style: cpn::SeparatorStyle) -> Self {
762 self.separator_normal_expl(cpn::Separator::normal(style))
763 }
764
765 /// Explicitly add a dotted `Separator` component to the `Container`.
766 /// Unlike the `Separator` method, which takes a style and internally
767 /// constructs a `Separator`, this method allows you to directly provide a
768 /// preconstructed `Separator` component.
769 ///
770 /// # Parameters
771 /// - `separator`: A `Separator` component.
772 ///
773 /// # Returns
774 /// - `ContainerBuilder`: Returns `self`.
775 ///
776 /// # Example
777 /// ```rust
778 /// // Create a dotted `Separator` component.
779 /// let separator = Separator::dotted(...)?;
780 ///
781 /// // Set a preconstructed `Header` component.
782 /// ContainerBuilder::new()
783 /// .separator_dotted_expl(separator);
784 /// ```
785 pub fn separator_dotted_expl(mut self, separator: cpn::Separator) -> Self {
786 self.container.add_separator(separator);
787 self
788 }
789
790 /// Add a dotted `Separator` with the given style.
791 ///
792 /// # Parameters
793 /// - `style`: The visual style of the separator, specified as a `SeparatorStyle`.
794 ///
795 /// # Returns
796 /// - `ContainerBuilder`: Returns `self`.
797 ///
798 /// # Example
799 /// ```rust
800 /// // Add a dotted separator with a solid style.
801 /// ContainerBuilder::new()
802 /// separator_dotted(SeparatorStyle::Solid);
803 /// ```
804 #[inline]
805 pub fn separator_dotted(self, style: cpn::SeparatorStyle) -> Self {
806 self.separator_dotted_expl(cpn::Separator::dotted(style))
807 }
808
809 /// Explicitly sets a `Selector` component for the `Container`. Unlike the `selector`
810 /// method, which takes triggers and internally constructs a `selector`, this
811 /// method allows you to directly provide a preconstructed `Selector` component.
812 ///
813 /// # Parameters
814 /// - `selector`: A `Selector` component.
815 ///
816 /// # Returns
817 /// - `ContainerBuilder`: Returns `self`.
818 ///
819 /// # Example
820 /// ```rust
821 /// // Create a `Header` component.
822 /// let selector = Selector::new(...)?;
823 ///
824 /// // Set a preconstructed `Selector` component.
825 /// ContainerBuilder::new()
826 /// .selector_expl(selector);
827 /// ```
828 pub fn selector_expl(mut self, selector: cpn::Selector) -> Self {
829 self.container.set_selector(selector);
830 self
831 }
832
833 /// Set a `Selector` for the `Container` to handle user navigation.
834 ///
835 /// # Parameters
836 /// - `up_trig`: A `Trigger` that moves the selector up when activated.
837 /// - `down_trig`: A `Trigger` that moves the selector down when activated.
838 /// - `selc_trig`: A `Trigger` that confirms the selection when activated.
839 ///
840 /// # Returns
841 /// - `ContainerBuilder`: Returns `self`.
842 ///
843 /// # Example
844 /// ```rust
845 /// // A `Trigger` that always activate.
846 /// tui::trg_new_trigger_func!(always_true_trigger, _arg, {
847 /// Ok(true)
848 /// });
849 ///
850 /// // A `Trigger` that never activate .
851 /// tui::trg_new_trigger_func!(always_false_trigger, _arg, {
852 /// Ok(false)
853 /// });
854 ///
855 /// // Set a `Selector` that always moves down.
856 /// ContainerBuilder::new()
857 /// .selector(
858 /// Trigger::no_arg(always_false_trigger),
859 /// Trigger::no_arg(always_true_trigger),
860 /// Trigger::no_arg(always_false_trigger),
861 /// );
862 /// ```
863 #[inline]
864 pub fn selector(
865 self, up_trig: Trigger, down_trig: Trigger, selc_trig: Trigger
866 ) -> Self {
867 self.selector_expl(cpn::Selector::new(up_trig, down_trig, selc_trig))
868 }
869
870 /// Sets a `Selector` with no `Trigger`s for the `Container`.
871 ///
872 /// In this case, navigation must be handled manually using the following methods:
873 /// - `Container::selector_up`
874 /// - `Container::selector_down`
875 /// - `Container::selector_select`
876 ///
877 /// # Returns
878 /// - `ContainerBuilder`: Returns `self`.
879 ///
880 /// # Example
881 /// ```rust
882 /// // Set a `Selector` with no `Trigger`s.
883 /// ContainerBuilder::new()
884 /// .selector_no_triggers();
885 /// ```
886 pub fn selector_no_triggers(mut self) -> Self {
887 self.container.set_selector(cpn::Selector::no_triggers());
888 self
889 }
890
891 /// Finalizes the construction of a `Container`. This method should be called
892 /// after all desired components have been added using the builder pattern.
893 /// It consumes `self` and returns the completed `Container`.
894 ///
895 /// # Returns
896 /// - `Container`: Returns the created `Container`.
897 ///
898 /// # Example
899 /// ```rust
900 /// let container: Container = ContainerBuilder::new()
901 /// .header(...)?
902 /// .option(...)?
903 /// .option(...)?
904 /// .separator_normal(...)
905 /// .text(...)?
906 /// .selector(...)?
907 /// .build(); // Finalize and retrieve the constructed container.
908 /// ```
909 pub fn build(self) -> Container {
910 self.container
911 }
912}