ratatui_kit/components/scroll_view/
state.rs

1//! ScrollViewState:滚动视图的状态管理结构,记录偏移量、尺寸、页大小等。
2//!
3//! 常与 ScrollView 组件配合使用,支持键盘/鼠标事件驱动的滚动。
4//!
5//! ## 用法示例
6//! ```rust
7//! let scroll_state = hooks.use_state(ScrollViewState::default);
8//! element!(ScrollView(scroll_view_state: scroll_state.get()) { ... })
9//! // 在事件处理器中调用 scroll_state.write().handle_event(&event)
10//! ```
11//! 支持上下左右/翻页/鼠标滚轮等多种滚动方式。
12
13use crossterm::event::{Event, KeyCode, KeyEventKind, MouseEventKind};
14use ratatui::layout::{Position, Size};
15
16#[derive(Debug, Default, Clone, Copy, Eq, PartialEq, Hash)]
17/// 滚动视图状态。
18pub struct ScrollViewState {
19    /// 偏移量是滚动视图需要移动的行数和列数。
20    pub(crate) offset: Position,
21    /// 滚动视图的尺寸。在第一次渲染调用前不会被设置。
22    pub(crate) size: Option<Size>,
23    /// 滚动视图一页的尺寸。在第一次渲染调用前不会被设置。
24    pub(crate) page_size: Option<Size>,
25}
26
27impl ScrollViewState {
28    /// 创建一个偏移量为 (0, 0) 的新滚动视图状态
29    pub fn new() -> Self {
30        Self::default()
31    }
32
33    /// 创建一个带有指定偏移量的新滚动视图状态
34    pub fn with_offset(offset: Position) -> Self {
35        Self {
36            offset,
37            ..Default::default()
38        }
39    }
40
41    /// 设置滚动视图状态的偏移量
42    pub const fn set_offset(&mut self, offset: Position) {
43        self.offset = offset;
44    }
45
46    /// 获取滚动视图状态的偏移量
47    pub const fn offset(&self) -> Position {
48        self.offset
49    }
50
51    /// 向上滚动一行
52    pub const fn scroll_up(&mut self) {
53        self.offset.y = self.offset.y.saturating_sub(1);
54    }
55
56    /// 向下滚动一行
57    pub const fn scroll_down(&mut self) {
58        self.offset.y = self.offset.y.saturating_add(1);
59    }
60
61    /// 向下滚动一页
62    pub fn scroll_page_down(&mut self) {
63        let page_size = self.page_size.map_or(1, |size| size.height);
64        // 我们减去 1 以确保页面之间有一行重叠
65        self.offset.y = self.offset.y.saturating_add(page_size).saturating_sub(1);
66    }
67
68    /// 向上滚动一页
69    pub fn scroll_page_up(&mut self) {
70        let page_size = self.page_size.map_or(1, |size| size.height);
71        // 我们加上 1 以确保页面之间有一行重叠
72        self.offset.y = self.offset.y.saturating_add(1).saturating_sub(page_size);
73    }
74
75    /// 向左滚动一列
76    pub const fn scroll_left(&mut self) {
77        self.offset.x = self.offset.x.saturating_sub(1);
78    }
79
80    /// 向右滚动一列
81    pub const fn scroll_right(&mut self) {
82        self.offset.x = self.offset.x.saturating_add(1);
83    }
84
85    /// 滚动到缓冲区顶部
86    pub const fn scroll_to_top(&mut self) {
87        self.offset = Position::ORIGIN;
88    }
89
90    /// 滚动到缓冲区底部
91    pub fn scroll_to_bottom(&mut self) {
92        // 渲染调用会调整偏移量以确保不会滚动到缓冲区末尾之后,所以这里可以将偏移量设置为最大值
93        let bottom = self
94            .size
95            .map_or(u16::MAX, |size| size.height.saturating_sub(1));
96        self.offset.y = bottom;
97    }
98
99    pub fn handle_event(&mut self, event: &Event) {
100        match event {
101            Event::Key(key) if key.kind == KeyEventKind::Press => match key.code {
102                KeyCode::Up | KeyCode::Char('k') => {
103                    self.scroll_up();
104                }
105                KeyCode::Down | KeyCode::Char('j') => {
106                    self.scroll_down();
107                }
108                KeyCode::Left | KeyCode::Char('h') => {
109                    self.scroll_left();
110                }
111                KeyCode::Right | KeyCode::Char('l') => {
112                    self.scroll_right();
113                }
114                KeyCode::PageUp => {
115                    self.scroll_page_up();
116                }
117                KeyCode::PageDown => {
118                    self.scroll_page_down();
119                }
120                KeyCode::Home => {
121                    self.scroll_to_top();
122                }
123                KeyCode::End => {
124                    self.scroll_to_bottom();
125                }
126                _ => {}
127            },
128            Event::Mouse(event) => match event.kind {
129                MouseEventKind::ScrollDown => {
130                    self.scroll_down();
131                }
132                MouseEventKind::ScrollUp => {
133                    self.scroll_up();
134                }
135                MouseEventKind::ScrollLeft => {
136                    self.scroll_left();
137                }
138                MouseEventKind::ScrollRight => {
139                    self.scroll_right();
140                }
141                _ => {}
142            },
143            _ => {}
144        }
145    }
146}