Skip to main content

ratatui_kit/terminal/
mod.rs

1use futures::{StreamExt, stream::BoxStream};
2use ratatui::buffer::Buffer;
3use std::{fmt::Debug, io};
4
5mod cross_terminal;
6pub use cross_terminal::CrossTerminal;
7
8pub trait TerminalImpl: Send {
9    type Event: Clone + Debug;
10    fn event_stream(&mut self) -> io::Result<BoxStream<'static, Self::Event>>;
11    fn received_ctrl_c(event: Self::Event) -> bool;
12    fn draw<F>(&mut self, f: F) -> io::Result<()>
13    where
14        F: FnOnce(&mut ratatui::Frame);
15
16    fn insert_before<F>(&mut self, height: u16, draw_fn: F) -> io::Result<()>
17    where
18        F: FnOnce(&mut Buffer);
19}
20
21// 终端封装:纯 raw event source。
22//
23// 事件分发已从「发布订阅广播」迁移到中央 `InputRuntime`(见 [`crate::input`]):
24// 渲染循环经 [`Terminal::next_event`] 取单个 raw 事件,先经 [`TerminalImpl::received_ctrl_c`]
25// 判定退出,否则交 `system_context.input.dispatch(event)` 分层投递。
26pub struct Terminal<T = CrossTerminal>
27where
28    T: TerminalImpl,
29{
30    inner: Box<T>,
31    event_stream: BoxStream<'static, T::Event>,
32}
33
34impl<T> Terminal<T>
35where
36    T: TerminalImpl,
37{
38    pub fn new(inner: T) -> io::Result<Self> {
39        let mut inner = Box::new(inner);
40        Ok(Self {
41            event_stream: inner.event_stream()?,
42            inner,
43        })
44    }
45
46    pub fn draw<F>(&mut self, f: F) -> io::Result<()>
47    where
48        F: FnOnce(&mut ratatui::Frame),
49    {
50        self.inner.draw(f)
51    }
52
53    pub fn insert_before<F>(&mut self, height: u16, draw_fn: F) -> io::Result<()>
54    where
55        F: FnOnce(&mut Buffer),
56    {
57        self.inner.insert_before(height, draw_fn)
58    }
59
60    // 异步等待下一个 raw 事件。`None` 表示事件流结束。
61    //
62    // 不做 ctrl_c 检测(交调用方经 [`TerminalImpl::received_ctrl_c`] 判定)、不广播。
63    pub async fn next_event(&mut self) -> Option<T::Event> {
64        self.event_stream.next().await
65    }
66}
67
68// update 路径所需终端能力的**对象安全**投影。
69//
70// `ComponentUpdater` 持 `&mut dyn UpdaterTerminal` 而非具体 `Terminal<CrossTerminal>`——
71// 因 `update_component` 经 `dyn` 分发,`ComponentUpdater` 必须非泛型,故把终端能力擦除成
72// 对象安全 trait。这使 update 能在无头测试里用 no-op 终端驱动(渲染 harness)。
73//
74// 事件订阅已移除(改由 `InputRuntime` 中央分发),故此 trait 仅暴露 update 真正用到的 `insert_before`
75// (闭包 box 化,因 `TerminalImpl::insert_before` 泛型闭包不对象安全)。
76pub trait UpdaterTerminal {
77    fn insert_before(
78        &mut self,
79        height: u16,
80        draw_fn: Box<dyn FnOnce(&mut Buffer)>,
81    ) -> io::Result<()>;
82}
83
84impl<T> UpdaterTerminal for Terminal<T>
85where
86    T: TerminalImpl<Event = crossterm::event::Event>,
87{
88    fn insert_before(
89        &mut self,
90        height: u16,
91        draw_fn: Box<dyn FnOnce(&mut Buffer)>,
92    ) -> io::Result<()> {
93        // 转发到 Terminal 的固有泛型 insert_before(Box<dyn FnOnce> 本身即 FnOnce)。
94        Terminal::insert_before(self, height, draw_fn)
95    }
96}