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