use crate::{Component, ComponentDrawer, ComponentUpdater, Hooks};
use ratatui::{
layout::{Alignment, Constraint, Position},
style::Style,
widgets::Paragraph,
};
use ratatui_kit_macros::{Props, with_layout_style};
const DEFAULT_WRAP_WIDTH: u16 = 80;
#[with_layout_style]
#[derive(Default, Props)]
pub struct WrappedTextProps {
pub text: String,
pub style: Style,
pub alignment: Alignment,
pub scroll: Position,
pub wrap_width: Option<u16>,
pub break_words: Option<bool>,
pub auto_height: Option<bool>,
}
pub struct WrappedText {
paragraph: Paragraph<'static>,
line_count: u16,
}
impl WrappedText {
fn from_props(props: &WrappedTextProps) -> Self {
let wrap_width = props
.wrap_width
.filter(|width| *width > 0)
.unwrap_or(match props.width {
Constraint::Length(width) if width > 0 => width,
_ => DEFAULT_WRAP_WIDTH,
});
let wrapped = wrap_text(&props.text, wrap_width, props.break_words.unwrap_or(true));
let line_count = wrapped_line_count(&wrapped);
Self {
paragraph: Paragraph::new(wrapped)
.style(props.style)
.scroll((props.scroll.x, props.scroll.y))
.alignment(props.alignment),
line_count,
}
}
fn line_count(&self) -> u16 {
self.line_count
}
}
impl Component for WrappedText {
type Props<'a> = WrappedTextProps;
fn new(props: &Self::Props<'_>) -> Self {
Self::from_props(props)
}
fn update(
&mut self,
props: &mut Self::Props<'_>,
_hooks: Hooks,
updater: &mut ComponentUpdater,
) {
*self = Self::from_props(props);
let mut layout_style = props.layout_style();
if props.auto_height.unwrap_or(true) {
layout_style.height = Constraint::Length(self.line_count());
}
updater.set_layout_style(layout_style);
}
fn draw(&mut self, drawer: &mut ComponentDrawer<'_, '_>) {
drawer.render_widget(&self.paragraph, drawer.area);
}
}
fn wrap_text(text: &str, width: u16, break_words: bool) -> String {
let options = textwrap::Options::new(width.max(1) as usize).break_words(break_words);
textwrap::fill(text, options)
}
fn wrapped_line_count(text: &str) -> u16 {
text.lines().count().max(1).min(u16::MAX as usize) as u16
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn line_count_uses_wrap_width() {
let props = WrappedTextProps {
text: "alpha beta gamma".into(),
wrap_width: Some(5),
..Default::default()
};
let text = WrappedText::from_props(&props);
assert_eq!(text.line_count(), 3);
}
#[test]
fn line_count_falls_back_to_length_width() {
let props = WrappedTextProps {
text: "alpha beta".into(),
width: Constraint::Length(5),
..Default::default()
};
let text = WrappedText::from_props(&props);
assert_eq!(text.line_count(), 2);
}
}