1use crate::layout::GenericLayout;
2use crate::pager::PagerStyle;
3use ratatui::buffer::Buffer;
4use ratatui::layout::{Alignment, Rect};
5use ratatui::style::Style;
6use ratatui::text::Line;
7use ratatui::widgets::{StatefulWidget, Widget};
8use std::borrow::Cow;
9use std::cell::{RefCell, RefMut};
10use std::hash::Hash;
11use std::rc::Rc;
12
13#[derive(Debug)]
15pub struct Pager<W>
16where
17 W: Eq + Hash + Clone,
18{
19 layout: Rc<RefCell<GenericLayout<W>>>,
20 page: usize,
21 style: Style,
22 label_style: Option<Style>,
23 label_alignment: Option<Alignment>,
24}
25
26#[derive(Debug)]
28pub struct PagerBuffer<'a, W>
29where
30 W: Eq + Hash + Clone,
31{
32 layout: Rc<RefCell<GenericLayout<W>>>,
33 page_area: Rect,
34 widget_area: Rect,
35 buffer: Rc<RefCell<&'a mut Buffer>>,
36 label_style: Option<Style>,
37 label_alignment: Option<Alignment>,
38}
39
40impl<W> Clone for Pager<W>
41where
42 W: Eq + Hash + Clone,
43{
44 fn clone(&self) -> Self {
45 Self {
46 layout: self.layout.clone(),
47 page: self.page,
48 style: self.style,
49 label_style: self.label_style,
50 label_alignment: self.label_alignment,
51 }
52 }
53}
54
55impl<W> Default for Pager<W>
56where
57 W: Eq + Hash + Clone,
58{
59 fn default() -> Self {
60 Self {
61 layout: Default::default(),
62 page: Default::default(),
63 style: Default::default(),
64 label_style: Default::default(),
65 label_alignment: Default::default(),
66 }
67 }
68}
69
70impl<W> Pager<W>
71where
72 W: Eq + Hash + Clone,
73{
74 pub fn new() -> Self {
75 Self::default()
76 }
77
78 pub fn layout(mut self, layout: Rc<RefCell<GenericLayout<W>>>) -> Self {
80 self.layout = layout;
81 self
82 }
83
84 pub fn page(mut self, page: usize) -> Self {
86 self.page = page;
87 self
88 }
89
90 pub fn style(mut self, style: Style) -> Self {
92 self.style = style;
93 self
94 }
95
96 pub fn label_style(mut self, style: Style) -> Self {
98 self.label_style = Some(style);
99 self
100 }
101
102 pub fn label_alignment(mut self, alignment: Alignment) -> Self {
104 self.label_alignment = Some(alignment);
105 self
106 }
107
108 pub fn styles(mut self, styles: PagerStyle) -> Self {
110 self.style = styles.style;
111 if let Some(label) = styles.label_style {
112 self.label_style = Some(label);
113 }
114 if let Some(alignment) = styles.label_alignment {
115 self.label_alignment = Some(alignment);
116 }
117 self
118 }
119
120 #[allow(clippy::needless_lifetimes)]
122 pub fn into_buffer<'b>(
123 self,
124 area: Rect,
125 buf: Rc<RefCell<&'b mut Buffer>>,
126 ) -> PagerBuffer<'b, W> {
127 let page_size = self.layout.borrow().page_size();
128 let page_area = Rect::new(
129 0,
130 self.page as u16 * page_size.height,
131 page_size.width,
132 page_size.height,
133 );
134
135 PagerBuffer {
136 layout: self.layout,
137 page_area,
138 widget_area: area,
139 buffer: buf,
140 label_style: self.label_style,
141 label_alignment: self.label_alignment,
142 }
143 }
144}
145
146impl<'a, W> PagerBuffer<'a, W>
147where
148 W: Eq + Hash + Clone,
149{
150 #[inline]
152 pub fn is_visible(&self, idx: usize) -> bool {
153 let area = self.layout.borrow().widget(idx);
154 self.page_area.intersects(area)
155 }
156
157 #[inline]
159 pub fn is_label_visible(&self, idx: usize) -> bool {
160 let area = self.layout.borrow().widget(idx);
161 self.page_area.intersects(area)
162 }
163
164 #[inline(always)]
166 pub fn widget_idx(&self, widget: W) -> Option<usize> {
167 self.layout.borrow().try_index_of(widget)
168 }
169
170 #[inline(always)]
172 pub fn render_label<FN>(&mut self, idx: usize, render_fn: FN) -> bool
173 where
174 FN: FnOnce(&Cow<'static, str>, Rect, &mut Buffer),
175 {
176 let Some(label_area) = self.locate_label(idx) else {
177 return false;
178 };
179 let layout = self.layout.borrow();
180 let mut buffer = self.buffer.borrow_mut();
181 if let Some(label_str) = layout.try_label_str(idx) {
182 render_fn(label_str, label_area, *buffer);
183 } else {
184 render_fn(&Cow::default(), label_area, *buffer);
185 }
186 true
187 }
188
189 #[inline(always)]
191 pub fn render_auto_label(&mut self, idx: usize) -> bool {
192 let Some(label_area) = self.locate_label(idx) else {
193 return false;
194 };
195 let layout = self.layout.borrow();
196 let Some(label_str) = layout.try_label_str(idx) else {
197 return false;
198 };
199
200 let mut buffer = self.buffer.borrow_mut();
201 let mut label = Line::from(label_str.as_ref());
202 if let Some(style) = self.label_style {
203 label = label.style(style)
204 };
205 if let Some(align) = self.label_alignment {
206 label = label.alignment(align);
207 }
208 label.render(label_area, *buffer);
209
210 true
211 }
212
213 #[inline(always)]
215 pub fn render_widget<FN, WW>(&mut self, idx: usize, render_fn: FN) -> bool
216 where
217 FN: FnOnce() -> WW,
218 WW: Widget,
219 {
220 let Some(widget_area) = self.locate_widget(idx) else {
221 return false;
222 };
223 let mut buffer = self.buffer.borrow_mut();
224 render_fn().render(widget_area, *buffer);
225 true
226 }
227
228 #[inline(always)]
230 pub fn render<FN, WW, SS>(&mut self, idx: usize, render_fn: FN, state: &mut SS) -> bool
231 where
232 FN: FnOnce() -> WW,
233 WW: StatefulWidget<State = SS>,
234 {
235 let Some(widget_area) = self.locate_widget(idx) else {
236 return false;
237 };
238 let mut buffer = self.buffer.borrow_mut();
239 render_fn().render(widget_area, *buffer, state);
240
241 true
242 }
243
244 #[inline(always)]
246 pub fn render_opt<FN, WW, SS>(&mut self, idx: usize, render_fn: FN, state: &mut SS) -> bool
247 where
248 FN: FnOnce() -> Option<WW>,
249 WW: StatefulWidget<State = SS>,
250 {
251 let Some(widget_area) = self.locate_widget(idx) else {
252 return false;
253 };
254 let mut buffer = self.buffer.borrow_mut();
255 let widget = render_fn();
256 if let Some(widget) = widget {
257 widget.render(widget_area, *buffer, state);
258 }
259 true
260 }
261
262 #[inline(always)]
266 #[allow(clippy::question_mark)]
267 pub fn render2<FN, WW, SS, R>(&mut self, idx: usize, render_fn: FN, state: &mut SS) -> Option<R>
268 where
269 FN: FnOnce() -> (WW, R),
270 WW: StatefulWidget<State = SS>,
271 {
272 let Some(widget_area) = self.locate_widget(idx) else {
273 return None;
274 };
275 let mut buffer = self.buffer.borrow_mut();
276 let (widget, remainder) = render_fn();
277 widget.render(widget_area, *buffer, state);
278
279 Some(remainder)
280 }
281
282 pub fn render_block(&mut self) {
284 let mut buffer = self.buffer.borrow_mut();
285 for (idx, block_area) in self.layout.borrow().block_area_iter().enumerate() {
286 if let Some(block_area) = self.locate_area(*block_area) {
287 if let Some(block) = self.layout.borrow().block(idx) {
288 block.render(block_area, *buffer);
289 }
290 }
291 }
292 }
293
294 #[inline]
298 pub fn locate_widget(&self, idx: usize) -> Option<Rect> {
299 self.locate_area(self.layout.borrow().widget(idx))
300 }
301
302 #[inline]
306 pub fn locate_label(&self, idx: usize) -> Option<Rect> {
307 self.locate_area(self.layout.borrow().label(idx))
308 }
309
310 #[inline]
315 pub fn locate_area(&self, area: Rect) -> Option<Rect> {
316 let area = self.page_area.intersection(area);
317 if area.is_empty() {
318 None
319 } else {
320 Some(Rect::new(
321 area.x - self.page_area.x + self.widget_area.x,
322 area.y - self.page_area.y + self.widget_area.y,
323 area.width,
324 area.height,
325 ))
326 }
327 }
328
329 #[inline]
331 pub fn buffer<'b>(&'b mut self) -> RefMut<'b, &'a mut Buffer> {
332 self.buffer.borrow_mut()
333 }
334}