Skip to main content

ez_tui/components/concrete/forms/inputs/
select_input.rs

1use crate::components::concrete::forms::inputs::core::{FormFieldValue, InputCpt};
2use crate::{
3    AttrValue, Attribute, Component, EzArgs, EzCptIds, EzEvent, EzMsg, EzState, FormField,
4    FormFieldType, MockComponent, MockProps, Props, State, Theme,
5};
6use crossterm::event::KeyCode;
7use eztui_derive::MockProps;
8use ratatui::buffer::Buffer;
9use ratatui::layout::{Alignment, Rect};
10use ratatui::prelude::{Line, Stylize, Widget};
11use ratatui::widgets::{BorderType, Paragraph};
12use std::fmt::Debug;
13/// A component drawing a [`FormField`] of type [`FormFieldType::Select`].
14#[derive(Debug, MockProps)]
15pub struct SelectInputCpt<FID>
16where
17    FID: EzCptIds,
18{
19    props: Props,
20    #[allow(dead_code)]
21    // I need a phantom if i remove this. So it's better keeping it as i need it in the constructor
22    field: FormField<FID>,
23    values: Vec<String>,
24    selected: usize,
25    focused: usize,
26}
27
28impl<FID> SelectInputCpt<FID>
29where
30    FID: EzCptIds,
31{
32    /// Create a new [`SelectInputCpt`] component with a given field and allowing a single selection
33    #[must_use]
34    pub(crate) fn new(field: FormField<FID>) -> Self {
35        let FormFieldType::Select { values, default } = field.field_type().clone() else {
36            panic!(
37                "Expected FormFieldType::Select, found {:?}",
38                field.field_type()
39            );
40        };
41        let mut props = Props::default();
42        props.set(
43            Attribute::Title,
44            AttrValue::Title((field.name(), Alignment::Left)),
45        );
46        Self {
47            props,
48            field,
49            values,
50            selected: default,
51            focused: default,
52        }
53    }
54}
55
56impl<FID, CID, CA, CS, CM> InputCpt<CID, CA, CS, CM> for SelectInputCpt<FID>
57where
58    FID: EzCptIds,
59    CID: EzCptIds,
60    CA: EzArgs,
61    CS: EzState,
62    CM: EzMsg,
63{
64    fn get_value(&self) -> FormFieldValue {
65        FormFieldValue::Text(
66            self.values
67                .get(self.selected)
68                .map_or(String::default(), std::string::ToString::to_string),
69        )
70    }
71}
72impl<FID> MockComponent for SelectInputCpt<FID>
73where
74    FID: EzCptIds,
75{
76    fn draw(&mut self, area: Rect, buf: &mut Buffer, theme: &Theme) {
77        let mut lines = Vec::new();
78        for (i, value) in self.values.iter().enumerate() {
79            let pre = if i == self.selected { "" } else { "" };
80            let mut span = Line::from(format!("{pre} {value}"))
81                .fg(theme.accent_or_text(i == self.focused).c500);
82            if i == self.selected {
83                span = span.bold();
84            }
85            lines.push(span);
86        }
87
88        Paragraph::new(lines)
89            .block(theme.block(self.props()).border_type(BorderType::Double))
90            .alignment(Alignment::Left)
91            .render(area, buf);
92    }
93}
94
95impl<FID, CID, CA, CS, CM> Component<CID, CA, CS, CM> for SelectInputCpt<FID>
96where
97    FID: EzCptIds,
98    CID: EzCptIds,
99    CA: EzArgs,
100    CS: EzState,
101    CM: EzMsg,
102{
103    fn on_event(
104        &mut self,
105        event: EzEvent<CID, CM>,
106        _state: &mut State<CS>,
107    ) -> Vec<EzEvent<CID, CM>> {
108        if let EzEvent::Keyboard(kev) = event {
109            if let KeyCode::Up = kev.code {
110                if self.focused == 0 {
111                    self.focused = self.values.len() - 1;
112                } else {
113                    self.focused -= 1;
114                }
115            } else if let KeyCode::Down = kev.code {
116                if self.focused == self.values.len() - 1 {
117                    self.focused = 0;
118                } else {
119                    self.focused += 1;
120                }
121            } else if let KeyCode::Char(' ') = kev.code {
122                self.selected = self.focused;
123            }
124        }
125
126        vec![]
127    }
128
129    fn focusable(&self) -> bool {
130        true
131    }
132}