ez_tui/components/concrete/forms/inputs/
text_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 eztui_derive::MockProps;
7use ratatui::buffer::Buffer;
8use ratatui::layout::{Alignment, Rect};
9use ratatui::prelude::Widget;
10use ratatui::widgets::{BorderType, Paragraph};
11use std::fmt::Debug;
12use tui_input::Input;
13use tui_input::backend::crossterm::EventHandler;
14#[derive(Debug, MockProps)]
16pub struct TextInputCpt<FID>
17where
18 FID: EzCptIds,
19{
20 secret: bool,
21 props: Props,
22 #[allow(dead_code)]
23 field: FormField<FID>,
25 input: Input,
26}
27
28impl<FID> TextInputCpt<FID>
29where
30 FID: EzCptIds,
31{
32 #[must_use]
37 pub fn clear(field: FormField<FID>) -> Self {
38 assert_eq!(field.field_type(), &FormFieldType::Text {});
39 Self::init(field, false)
40 }
41 #[must_use]
46 pub fn secret(field: FormField<FID>) -> Self {
47 assert!(matches!(field.field_type(), &FormFieldType::Password {}));
48 Self::init(field, true)
49 }
50
51 fn init(field: FormField<FID>, secret: bool) -> TextInputCpt<FID> {
52 let mut props = Props::default();
53 props.set(
54 Attribute::Title,
55 AttrValue::Title((field.name(), Alignment::Left)),
56 );
57 Self {
58 secret,
59 props,
60 field,
61 input: Input::default(),
62 }
63 }
64}
65
66impl<FID, CID, CA, CS, CM> InputCpt<CID, CA, CS, CM> for TextInputCpt<FID>
67where
68 FID: EzCptIds,
69 CID: EzCptIds,
70 CA: EzArgs,
71 CS: EzState,
72 CM: EzMsg,
73{
74 fn get_value(&self) -> FormFieldValue {
75 FormFieldValue::Text(self.input.value().to_string())
76 }
77}
78impl<FID> MockComponent for TextInputCpt<FID>
79where
80 FID: EzCptIds,
81{
82 fn draw(&mut self, area: Rect, buf: &mut Buffer, theme: &Theme) {
83 let width = area.width.max(3) - 3;
84 let scroll = self.input.visual_scroll(width as usize);
85
86 let displayed_text = if self.secret {
87 self.input.value().chars().map(|_| '*').collect()
88 } else {
89 self.input.value().to_string()
90 };
91 Paragraph::new(displayed_text)
92 .scroll((0, u16::try_from(scroll).unwrap_or_default()))
93 .block(theme.block(self.props()).border_type(BorderType::Double))
94 .render(area, buf);
95 }
96}
97
98impl<FID, CID, CA, CS, CM> Component<CID, CA, CS, CM> for TextInputCpt<FID>
99where
100 FID: EzCptIds,
101 CID: EzCptIds,
102 CA: EzArgs,
103 CS: EzState,
104 CM: EzMsg,
105{
106 fn on_event(
107 &mut self,
108 event: EzEvent<CID, CM>,
109 _state: &mut State<CS>,
110 ) -> Vec<EzEvent<CID, CM>> {
111 if let Some(event) = event.try_original() {
112 self.input.handle_event(&event);
113 }
114 vec![]
115 }
116
117 fn focusable(&self) -> bool {
118 true
119 }
120}