Skip to main content

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

1use crate::components::concrete::forms::inputs::core::{FormFieldValue, InputCpt};
2use crate::components::concrete::forms::inputs::number_buffer::NumberBuffer;
3use crate::{
4    AttrValue, Attribute, Component, EzArgs, EzCptIds, EzEvent, EzMsg, EzState, FormField,
5    FormFieldType, MockComponent, MockProps, Props, State, Theme,
6};
7use crossterm::event::KeyCode;
8use eztui_derive::MockProps;
9use ratatui::buffer::Buffer;
10use ratatui::layout::{Alignment, Rect};
11use ratatui::prelude::{Stylize, Widget};
12use ratatui::text::{Line, Span};
13use ratatui::widgets::{BorderType, Paragraph};
14use std::fmt::Debug;
15/// A component drawing a [`FormField`] of type [`FormFieldType::Range`].
16#[derive(Debug, MockProps)]
17pub struct RangeInputCpt<FID>
18where
19    FID: EzCptIds,
20{
21    props: Props,
22    #[allow(dead_code)]
23    // I need a phantom if i remove this. So it's better keeping it as i need it in the constructor
24    field: FormField<FID>,
25    editing_first: bool,
26    buffers: (NumberBuffer, NumberBuffer),
27}
28
29impl<FID> RangeInputCpt<FID>
30where
31    FID: EzCptIds,
32{
33    /// Create a new [`RangeInputCpt`] component with a given form.
34    #[must_use]
35    pub(crate) fn new(field: FormField<FID>) -> Self {
36        assert_eq!(field.field_type(), &FormFieldType::Range {});
37        let mut props = Props::default();
38        props.set(
39            Attribute::Title,
40            AttrValue::Title((field.name(), Alignment::Left)),
41        );
42        Self {
43            props,
44            field,
45            editing_first: true,
46            buffers: (NumberBuffer::default(), NumberBuffer::default()),
47        }
48    }
49}
50
51impl<FID, CID, CA, CS, CM> InputCpt<CID, CA, CS, CM> for RangeInputCpt<FID>
52where
53    FID: EzCptIds,
54    CID: EzCptIds,
55    CA: EzArgs,
56    CS: EzState,
57    CM: EzMsg,
58{
59    fn get_value(&self) -> FormFieldValue {
60        FormFieldValue::Range((self.buffers.0.parsed(), self.buffers.1.parsed()))
61    }
62}
63impl<FID> MockComponent for RangeInputCpt<FID>
64where
65    FID: EzCptIds,
66{
67    fn draw(&mut self, area: Rect, buf: &mut Buffer, theme: &Theme) {
68        let mut a = Span::raw(self.buffers.0.parsed().to_string());
69        let mut b = Span::raw(self.buffers.1.parsed().to_string());
70        if self.editing_first {
71            a = a.bold();
72        } else {
73            b = b.bold();
74        }
75
76        let spans = vec![a, Span::raw(" .. "), b];
77        Paragraph::new(Line::from(spans))
78            .block(theme.block(self.props()).border_type(BorderType::Double))
79            .render(area, buf);
80    }
81}
82
83impl<FID, CID, CA, CS, CM> Component<CID, CA, CS, CM> for RangeInputCpt<FID>
84where
85    FID: EzCptIds,
86    CID: EzCptIds,
87    CA: EzArgs,
88    CS: EzState,
89    CM: EzMsg,
90{
91    fn on_event(
92        &mut self,
93        event: EzEvent<CID, CM>,
94        _state: &mut State<CS>,
95    ) -> Vec<EzEvent<CID, CM>> {
96        if let EzEvent::Keyboard(kev) = event {
97            if let KeyCode::Char(' ') = kev.code {
98                self.editing_first = !self.editing_first;
99            } else if self.editing_first {
100                self.buffers.0.handle(kev);
101            } else {
102                self.buffers.1.handle(kev);
103            }
104        }
105
106        vec![]
107    }
108
109    fn focusable(&self) -> bool {
110        true
111    }
112}