Skip to main content

ratatui_kit/hooks/
use_size.rs

1use std::{cell::Cell, rc::Rc};
2
3use crossterm::{event::Event, terminal};
4use ratatui::layout::Rect;
5
6use crate::{
7    Hook, State, SystemContext, UseState,
8    input::{EventOptions, EventPriority, EventResult},
9};
10
11mod private {
12    pub trait Sealed {}
13    impl Sealed for crate::Hooks<'_, '_> {}
14}
15
16// 获取终端当前尺寸,并在终端尺寸变化时更新,适合响应式布局。
17pub trait UseTerminalSize: private::Sealed {
18    fn use_terminal_size(&mut self) -> (u16, u16);
19}
20
21impl UseTerminalSize for crate::Hooks<'_, '_> {
22    fn use_terminal_size(&mut self) -> (u16, u16) {
23        let mut size = self.use_state(|| terminal::size().unwrap_or((0, 0)));
24
25        let hook = self.use_hook(UseTerminalSizeImpl::new);
26        hook.size = Some(size);
27
28        size.get()
29    }
30}
31
32// 终端尺寸监听保留为专用 hook,而不是复用 `use_event_handler(Global, ..)`。
33//
34// 这样手写 Component 直接调用 `use_terminal_size` 时仍不需要先 `with_context_stack`;
35// 注册 Resize handler 时由 `post_component_update` 通过 updater 拿根 `SystemContext`。
36struct UseTerminalSizeImpl {
37    size: Option<State<(u16, u16)>>,
38}
39
40impl UseTerminalSizeImpl {
41    fn new() -> Self {
42        Self { size: None }
43    }
44}
45
46impl Hook for UseTerminalSizeImpl {
47    fn post_component_update(&mut self, updater: &mut crate::ComponentUpdater) {
48        let Some(mut size) = self.size else {
49            return;
50        };
51        let mut system = updater
52            .get_context_mut::<SystemContext>()
53            .expect("SystemContext 缺失(根 context 必有)");
54
55        // Resize 是真全局事件:`layer=None` 不被任何 blocks_lower 截断,返回 Ignored
56        // 让多个 use_terminal_size 订阅者都能收到。
57        system.input.register_handler(
58            None,
59            EventPriority::Normal,
60            EventOptions::default(),
61            Rc::new(Cell::new(Rect::default())),
62            Box::new(move |event| {
63                if let Event::Resize(width, height) = event {
64                    size.set((width, height));
65                }
66                EventResult::Ignored
67            }),
68        );
69    }
70}
71
72// 获取组件当前尺寸,但是是上一帧的尺寸
73pub trait UsePreviousSize: private::Sealed {
74    fn use_previous_size(&mut self) -> Rect;
75}
76
77pub struct UsePreviousSizeImpl {
78    size: Rect,
79}
80
81impl Default for UsePreviousSizeImpl {
82    fn default() -> Self {
83        Self::new()
84    }
85}
86
87impl UsePreviousSizeImpl {
88    pub fn new() -> Self {
89        UsePreviousSizeImpl {
90            size: Rect::default(),
91        }
92    }
93}
94
95impl Hook for UsePreviousSizeImpl {
96    fn pre_component_draw(&mut self, drawer: &mut crate::ComponentDrawer) {
97        self.size = drawer.area;
98    }
99}
100
101impl UsePreviousSize for crate::Hooks<'_, '_> {
102    fn use_previous_size(&mut self) -> Rect {
103        let hook = self.use_hook(UsePreviousSizeImpl::new);
104        hook.size
105    }
106}