tui-canvas 0.8.2

Form/textarea for TUI
Documentation
#[cfg(feature = "gui")]
use ratatui::{
    buffer::Buffer,
    layout::{Alignment, Rect},
    style::{Color, Style},
    text::{Line, Span},
    widgets::{Block, BorderType, Borders, Paragraph, StatefulWidget, Widget},
};

#[cfg(feature = "gui")]
use crate::gui_utils::{
    clip_inline_completion_with_indicator_padded, clip_window_with_indicator_padded,
    compute_h_scroll_with_padding, display_cols_up_to, display_width,
};
#[cfg(feature = "gui")]
use crate::textinput::provider::{TextInputDataProvider, TextInputProvider};

#[cfg(feature = "gui")]
use crate::textinput::state::TextInputState;

#[cfg(feature = "gui")]
#[derive(Debug, Clone)]
pub struct TextInput<'a, P: TextInputDataProvider = TextInputProvider> {
    pub(crate) block: Option<Block<'a>>,
    pub(crate) style: Style,
    pub(crate) suggestion_style: Style,
    pub(crate) border_type: BorderType,
    pub(crate) _provider: std::marker::PhantomData<P>,
}

#[cfg(feature = "gui")]
impl<'a, P: TextInputDataProvider> Default for TextInput<'a, P> {
    fn default() -> Self {
        Self {
            block: Some(
                Block::default()
                    .borders(Borders::ALL)
                    .border_type(BorderType::Rounded),
            ),
            style: Style::default(),
            suggestion_style: Style::default().fg(Color::DarkGray),
            border_type: BorderType::Rounded,
            _provider: std::marker::PhantomData,
        }
    }
}

#[cfg(feature = "gui")]
impl<'a, P: TextInputDataProvider> TextInput<'a, P> {
    pub fn block(mut self, block: Block<'a>) -> Self {
        self.block = Some(block);
        self
    }

    pub fn style(mut self, style: Style) -> Self {
        self.style = style;
        self
    }

    pub fn suggestion_style(mut self, style: Style) -> Self {
        self.suggestion_style = style;
        self
    }

    pub fn border_type(mut self, ty: BorderType) -> Self {
        self.border_type = ty;
        if let Some(b) = &mut self.block {
            *b = b.clone().border_type(ty);
        }
        self
    }
}

#[cfg(feature = "gui")]
impl<'a, P: TextInputDataProvider> StatefulWidget for TextInput<'a, P> {
    type State = TextInputState<P>;

    fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
        state.ensure_visible(area, self.block.as_ref());

        let inner = if let Some(b) = &self.block {
            b.clone().render(area, buf);
            b.inner(area)
        } else {
            area
        };

        let edited_now = state.take_edited_flag();
        let text = state.current_display_text_for_render();
        let suggestion = state.suggestion_suffix();

        let line = if text.is_empty() && suggestion.is_none() {
            Line::from(Span::raw(state.placeholder.clone().unwrap_or_default()))
        } else {
            let fits = display_width(&text) <= inner.width;
            let start_cols = if fits {
                if edited_now {
                    0
                } else {
                    state.h_scroll
                }
            } else {
                let cursor_cols = display_cols_up_to(&text, state.display_cursor_position());
                let (target_h, _) = compute_h_scroll_with_padding(cursor_cols, inner.width);
                target_h.max(state.h_scroll)
            };

            if suggestion.is_some() {
                clip_inline_completion_with_indicator_padded(
                    &text,
                    suggestion,
                    inner.width,
                    state.overflow_indicator,
                    start_cols,
                    self.style,
                    self.suggestion_style,
                )
            } else {
                clip_window_with_indicator_padded(
                    &text,
                    inner.width,
                    state.overflow_indicator,
                    start_cols,
                )
            }
        };

        let p = Paragraph::new(vec![line])
            .alignment(Alignment::Left)
            .style(self.style);

        p.render(inner, buf);
    }
}