rtlibs-tui 0.1.4

rtools library: ratatui widgets
Documentation
use ratatui::layout::Position;

#[derive(Debug, Default)]
pub struct InputNumberState
{
    pub(crate) input: String,
    pub(crate) character_index: usize,
    pub(crate) cursor: Position,
    pub(crate) clear_on_cancel: bool,
    pub(crate) clear_on_confirm: bool,
    pub(crate) max_length: Option<usize>,
    pub(crate) max_value: Option<u32>,
    pub(crate) min_value: Option<u32>,
    pub(crate) right_aligned: bool,
}

impl InputNumberState
{
    pub fn new() -> Self
    {
        Self::default()
    }

    pub fn right_aligned(mut self) -> Self
    {
        self.right_aligned = true;
        self
    }

    pub fn set(
        &mut self,
        value: u32,
    )
    {
        let text = format!("{value}");
        self.clear();
        self.write(text);
    }

    pub fn set_str<S>(
        &mut self,
        value: S,
    ) where
        S: AsRef<str>,
    {
        self.clear();
        self.write(value);
    }

    pub fn max(
        mut self,
        value: u32,
    ) -> Self
    {
        self.max_value = Some(value);
        self
    }

    pub fn min(
        mut self,
        value: u32,
    ) -> Self
    {
        self.min_value = Some(value);
        self
    }

    pub fn with_default(
        mut self,
        value: u32,
    ) -> Self
    {
        if let Some(max_length) = self.max_length
        {
            self.input = format!(
                "{value:0>max_length$}",
                max_length = max_length
            );
        }
        else
        {
            self.input = format!("{value}");
        }
        self
    }

    pub fn with_max_length(
        mut self,
        length: usize,
    ) -> Self
    {
        self.max_length = Some(length);
        self
    }

    pub fn write<S>(
        &mut self,
        text: S,
    ) where
        S: AsRef<str>,
    {
        for c in text
            .as_ref()
            .chars()
        {
            self.enter_char(c);
        }
    }

    pub fn input(&self) -> Option<u32>
    {
        self.input
            .parse()
            .ok()
        // .unwrap_or_default()
    }

    pub fn set_clear_on_cancel(
        &mut self,
        value: bool,
    )
    {
        self.clear_on_cancel = value;
    }

    pub fn with_clear_on_cancel(mut self) -> Self
    {
        self.set_clear_on_cancel(true);
        self
    }

    pub fn clear_on_cancel(&self) -> bool
    {
        self.clear_on_cancel
    }

    pub fn set_clear_on_confirm(
        &mut self,
        value: bool,
    )
    {
        self.clear_on_confirm = value;
    }

    pub fn with_clear_on_confirm(mut self) -> Self
    {
        self.set_clear_on_confirm(true);
        self
    }

    pub fn clear_on_confirm(&self) -> bool
    {
        self.clear_on_confirm
    }

    pub fn cursor(&self) -> Position
    {
        self.cursor
    }

    pub fn clear(&mut self)
    {
        self.input
            .clear();
        self.reset_cursor();
    }

    pub(crate) fn move_cursor_left(&mut self)
    {
        let cursor_moved_left = self
            .character_index
            .saturating_sub(1);
        self.character_index = self.clamp_cursor(cursor_moved_left);
    }

    pub(crate) fn move_cursor_start(&mut self)
    {
        self.character_index = self.clamp_cursor(0);
    }

    pub(crate) fn move_cursor_right(&mut self)
    {
        let cursor_moved_right = self
            .character_index
            .saturating_add(1);
        self.character_index = self.clamp_cursor(cursor_moved_right);
    }

    pub(crate) fn move_cursor_end(&mut self)
    {
        self.character_index = self.clamp_cursor(
            self.input
                .chars()
                .count(),
        )
    }

    pub(crate) fn enter_char(
        &mut self,
        new_char: char,
    )
    {
        if let Some(max_length) = self.max_length
        {
            if self
                .input
                .chars()
                .count()
                >= max_length
            {
                return;
            }
        }

        if (new_char as u8) < 48 || (new_char as u8) > 57
        {
            return;
        }

        let index = self.byte_index();

        if let Some(max) = self.max_value
        {
            let mut test = self
                .input
                .clone();
            test.insert(
                index, new_char,
            );
            let new_value: u32 = test
                .parse()
                .unwrap_or_default();
            if new_value > max
            {
                return;
            }
        }

        self.input
            .insert(
                index, new_char,
            );
        self.move_cursor_right();
    }

    fn byte_index(&mut self) -> usize
    {
        self.input
            .char_indices()
            .map(|(i, _)| i)
            .nth(self.character_index)
            .unwrap_or(
                self.input
                    .len(),
            )
    }

    pub(crate) fn supp_char(&mut self)
    {
        let is_not_cursor_rightmost = self.character_index
            != self
                .input
                .chars()
                .count();
        if is_not_cursor_rightmost
        {
            let before = self
                .input
                .chars()
                .take(self.character_index);
            let after = self
                .input
                .chars()
                .skip(self.character_index + 1);
            self.input = before
                .chain(after)
                .collect();
        }
    }

    pub(crate) fn delete_char(&mut self)
    {
        let is_not_cursor_leftmost = self.character_index != 0;
        if is_not_cursor_leftmost
        {
            let current_index = self.character_index;
            let from_left_to_current_index = current_index - 1;
            let before_char_to_delete = self
                .input
                .chars()
                .take(from_left_to_current_index);
            let after_char_to_delete = self
                .input
                .chars()
                .skip(current_index);
            self.input = before_char_to_delete
                .chain(after_char_to_delete)
                .collect();
            self.move_cursor_left();
        }
    }

    fn clamp_cursor(
        &self,
        new_cursor_pos: usize,
    ) -> usize
    {
        new_cursor_pos.clamp(
            0,
            self.input
                .chars()
                .count(),
        )
    }

    pub fn reset_cursor(&mut self)
    {
        self.character_index = 0;
    }
}