ez_tui/components/concrete/forms/inputs/
select_input.rs1use 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#[derive(Debug, MockProps)]
15pub struct SelectInputCpt<FID>
16where
17 FID: EzCptIds,
18{
19 props: Props,
20 #[allow(dead_code)]
21 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 #[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}