promkit-widgets 0.7.0

Widgets for promkit
Documentation
use promkit_core::{
    ContentPosition, CreatedGraphemes, Widget, WidgetLayout, grapheme::StyledGraphemes,
};

#[path = "text/text.rs"]
mod inner;
pub use inner::Text;
pub mod config;
pub use config::Config;

/// Represents the state of a text-based component within the application.
///
/// This state encapsulates the properties and
/// behaviors specific to text handling,
#[derive(Clone, Default)]
pub struct State {
    /// The text to be rendered.
    pub text: Text,
    /// Configuration for rendering and behavior.
    pub config: Config,
}

impl State {
    pub fn replace(&mut self, state: Self) {
        *self = state;
    }

    pub fn replace_text(&mut self, text: Vec<StyledGraphemes>) {
        self.text.replace_contents(text);
    }
}

impl Widget for State {
    fn create_graphemes(&self) -> CreatedGraphemes {
        let lines = self.text.items().iter().map(|item| {
            if let Some(style) = &self.config.style {
                item.clone().apply_style(*style)
            } else {
                item.clone()
            }
        });

        CreatedGraphemes {
            graphemes: StyledGraphemes::from_lines(lines),
            layout: WidgetLayout {
                max_height: self.config.lines,
                ..Default::default()
            },
            cursor: self
                .text
                .current_line()
                .map(|current_line| ContentPosition {
                    row: current_line,
                    column: 0,
                }),
        }
    }
}

impl State {
    /// Interprets a text content position as a selectable line.
    ///
    /// Wrapped visual rows are normalized to their logical content row by the
    /// core renderer before this method resolves the line index.
    pub fn hit_at(&self, position: ContentPosition) -> Option<TextHit> {
        self.text
            .items()
            .get(position.row)
            .map(|_| TextHit::Select {
                index: position.row,
            })
    }
}

/// Semantic targets exposed by the text widget.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum TextHit {
    Select { index: usize },
}

#[cfg(test)]
mod tests {
    use super::*;

    mod state {
        use super::*;

        mod hit_at {
            use super::*;

            #[test]
            fn resolves_line_rows() {
                let state = State {
                    text: Text::from("first\nsecond"),
                    config: Config::default(),
                };

                assert_eq!(
                    state.hit_at(ContentPosition { row: 1, column: 20 }),
                    Some(TextHit::Select { index: 1 })
                );
                assert_eq!(state.hit_at(ContentPosition { row: 2, column: 0 }), None);
            }
        }
    }
}