Skip to main content

ratatui_kit/components/
text.rs

1use crate::{AnyElement, element, prelude::Fragment};
2use ratatui::{
3    buffer::Buffer,
4    layout::{Position, Rect},
5    style::Style,
6    widgets::{Paragraph, Widget},
7};
8use ratatui_kit_macros::{Props, component};
9use std::ops::{Deref, DerefMut};
10
11#[derive(Clone, Default)]
12pub struct TextParagraph<'a> {
13    inner: Paragraph<'a>,
14}
15
16// ratatui 0.30 起 `Paragraph` 内含 `Option<Block>`,而 `Block` 因新增阴影效果
17// (`Arc<dyn CellEffect>`)不再 Send + Sync;但 `Props` 要求 Send + Sync。
18// 与 `SendBlock` 同理:ratatui-kit 渲染单线程、所构造段落不挂自定义阴影效果,
19// 故对该 newtype 断言 Send + Sync 是安全的。
20// Safety: 见上方说明。
21unsafe impl Send for TextParagraph<'_> {}
22unsafe impl Sync for TextParagraph<'_> {}
23
24impl<'a> Deref for TextParagraph<'a> {
25    type Target = Paragraph<'a>;
26
27    fn deref(&self) -> &Self::Target {
28        &self.inner
29    }
30}
31
32impl DerefMut for TextParagraph<'_> {
33    fn deref_mut(&mut self) -> &mut Self::Target {
34        &mut self.inner
35    }
36}
37
38// 让 TextParagraph 自身成为可渲染 widget,从而可经 `$expr` 直接嵌入元素树
39// (`WidgetAdapter` 要求 widget 为 Send + Sync,裸 `Paragraph` 0.30 起不满足,故用本包装)。
40impl Widget for TextParagraph<'_> {
41    fn render(self, area: Rect, buf: &mut Buffer) {
42        self.inner.render(area, buf);
43    }
44}
45
46impl From<String> for TextParagraph<'_> {
47    fn from(value: String) -> Self {
48        Self {
49            inner: Paragraph::new(value),
50        }
51    }
52}
53
54impl<'a> From<Paragraph<'a>> for TextParagraph<'a> {
55    fn from(value: Paragraph<'a>) -> Self {
56        Self { inner: value }
57    }
58}
59
60#[derive(Default, Props)]
61pub struct TextProps {
62    pub text: TextParagraph<'static>,
63    pub style: Style,
64    pub alignment: ratatui::layout::Alignment,
65    pub scroll: Position,
66    pub wrap: Option<bool>,
67}
68
69#[component]
70pub fn Text(props: &TextProps) -> impl Into<AnyElement<'static>> {
71    let paragraph = props
72        .text
73        .inner
74        .clone()
75        .style(props.style)
76        .scroll((props.scroll.x, props.scroll.y))
77        .alignment(props.alignment);
78
79    let paragraph = if let Some(wrap) = props.wrap {
80        paragraph.wrap(ratatui::widgets::Wrap { trim: wrap })
81    } else {
82        paragraph
83    };
84
85    // 包成 Send + Sync 的 TextParagraph 再嵌入(裸 Paragraph 0.30 起非 Send,无法走 WidgetAdapter)。
86    let paragraph = TextParagraph::from(paragraph);
87
88    element! {
89        Fragment{
90            $paragraph
91        }
92    }
93}