1use crate::caption::{Caption, CaptionState, CaptionStyle};
2use crate::layout::GenericLayout;
3use crate::pager::PagerStyle;
4use rat_focus::FocusFlag;
5use ratatui::buffer::Buffer;
6use ratatui::layout::{Alignment, Rect};
7use ratatui::style::Style;
8use ratatui::text::Line;
9use ratatui::widgets::{StatefulWidget, Widget};
10use std::borrow::Cow;
11use std::cell::{RefCell, RefMut};
12use std::hash::Hash;
13use std::rc::Rc;
14
15#[derive(Debug)]
17pub struct Pager<W>
18where
19 W: Eq + Hash + Clone,
20{
21 layout: Rc<RefCell<GenericLayout<W>>>,
22 page: usize,
23 style: Style,
24 label_style: Option<Style>,
25 label_alignment: Option<Alignment>,
26 caption_style: Option<CaptionStyle>,
27}
28
29#[derive(Debug)]
31pub struct PagerBuffer<'a, W>
32where
33 W: Eq + Hash + Clone,
34{
35 layout: Rc<RefCell<GenericLayout<W>>>,
36 page_area: Rect,
37 widget_area: Rect,
38 buffer: Rc<RefCell<&'a mut Buffer>>,
39 label_style: Option<Style>,
40 label_alignment: Option<Alignment>,
41 caption_style: Option<CaptionStyle>,
42}
43
44impl<W> Clone for Pager<W>
45where
46 W: Eq + Hash + Clone,
47{
48 fn clone(&self) -> Self {
49 Self {
50 layout: self.layout.clone(),
51 page: self.page,
52 style: self.style,
53 label_style: self.label_style,
54 label_alignment: self.label_alignment,
55 caption_style: self.caption_style.clone(),
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 caption_style: Default::default(),
72 }
73 }
74}
75
76impl<W> Pager<W>
77where
78 W: Eq + Hash + Clone,
79{
80 pub fn new() -> Self {
81 Self::default()
82 }
83
84 pub fn layout(mut self, layout: Rc<RefCell<GenericLayout<W>>>) -> Self {
86 self.layout = layout;
87 self
88 }
89
90 pub fn page(mut self, page: usize) -> Self {
92 self.page = page;
93 self
94 }
95
96 pub fn style(mut self, style: Style) -> Self {
98 self.style = style;
99 self
100 }
101
102 pub fn label_style(mut self, style: Style) -> Self {
104 self.label_style = Some(style);
105 self
106 }
107
108 pub fn label_alignment(mut self, alignment: Alignment) -> Self {
110 self.label_alignment = Some(alignment);
111 self
112 }
113
114 pub fn caption_style(mut self, style: CaptionStyle) -> Self {
116 self.caption_style = Some(style);
117 self
118 }
119
120 pub fn styles(mut self, styles: PagerStyle) -> Self {
122 self.style = styles.style;
123 if let Some(label) = styles.label_style {
124 self.label_style = Some(label);
125 }
126 if let Some(alignment) = styles.label_alignment {
127 self.label_alignment = Some(alignment);
128 }
129 if let Some(caption_style) = styles.caption_style {
130 self.caption_style = Some(caption_style);
131 }
132 self
133 }
134
135 #[allow(clippy::needless_lifetimes)]
137 pub fn into_buffer<'b>(
138 self,
139 area: Rect,
140 buf: Rc<RefCell<&'b mut Buffer>>,
141 ) -> PagerBuffer<'b, W> {
142 let page_size = self.layout.borrow().page_size();
143 let page_area = Rect::new(
144 0,
145 self.page as u16 * page_size.height,
146 page_size.width,
147 page_size.height,
148 );
149
150 PagerBuffer {
151 layout: self.layout,
152 page_area,
153 widget_area: area,
154 buffer: buf,
155 label_style: self.label_style,
156 label_alignment: self.label_alignment,
157 caption_style: self.caption_style,
158 }
159 }
160}
161
162impl<'a, W> PagerBuffer<'a, W>
163where
164 W: Eq + Hash + Clone,
165{
166 #[inline]
168 pub fn is_visible(&self, idx: usize) -> bool {
169 let area = self.layout.borrow().widget(idx);
170 self.page_area.intersects(area)
171 }
172
173 #[inline]
175 pub fn is_label_visible(&self, idx: usize) -> bool {
176 let area = self.layout.borrow().widget(idx);
177 self.page_area.intersects(area)
178 }
179
180 #[inline(always)]
182 pub fn widget_idx(&self, widget: W) -> Option<usize> {
183 self.layout.borrow().try_index_of(widget)
184 }
185
186 #[inline(always)]
188 pub fn render_label<FN>(&mut self, idx: usize, render_fn: FN) -> bool
189 where
190 FN: FnOnce(&Cow<'static, str>, Rect, &mut Buffer),
191 {
192 let Some(label_area) = self.locate_label(idx) else {
193 return false;
194 };
195 let layout = self.layout.borrow();
196 let label_str = layout.try_label_str(idx);
197 if let Some(label_str) = label_str {
198 let mut buffer = self.buffer.borrow_mut();
199 render_fn(label_str, label_area, *buffer);
200 true
201 } else {
202 false
203 }
204 }
205
206 #[inline(always)]
208 pub fn render_caption(
209 &mut self,
210 idx: usize,
211 link: &FocusFlag,
212 state: &mut CaptionState,
213 ) -> bool {
214 let Some(label_area) = self.locate_label(idx) else {
215 return false;
216 };
217 let layout = self.layout.borrow();
218 let Some(label_str) = layout.try_label_str(idx) else {
219 return false;
220 };
221
222 let mut buffer = self.buffer.borrow_mut();
223 let mut label = Caption::parse(label_str.as_ref()).link(link);
224 if let Some(style) = &self.caption_style {
225 label = label.styles(style.clone())
226 }
227 label.render(label_area, *buffer, state);
228
229 true
230 }
231
232 #[inline(always)]
234 pub fn render_auto_label(&mut self, idx: usize) -> bool {
235 let Some(label_area) = self.locate_label(idx) else {
236 return false;
237 };
238 let layout = self.layout.borrow();
239 let Some(label_str) = layout.try_label_str(idx) else {
240 return false;
241 };
242
243 let mut buffer = self.buffer.borrow_mut();
244 let mut label = Line::from(label_str.as_ref());
245 if let Some(style) = self.label_style {
246 label = label.style(style)
247 };
248 if let Some(align) = self.label_alignment {
249 label = label.alignment(align);
250 }
251 label.render(label_area, *buffer);
252
253 true
254 }
255
256 #[inline(always)]
258 pub fn render_widget<FN, WW>(&mut self, idx: usize, render_fn: FN) -> bool
259 where
260 FN: FnOnce() -> WW,
261 WW: Widget,
262 {
263 let Some(widget_area) = self.locate_widget(idx) else {
264 return false;
265 };
266 let mut buffer = self.buffer.borrow_mut();
267 render_fn().render(widget_area, *buffer);
268 true
269 }
270
271 #[inline(always)]
273 pub fn render<FN, WW, SS>(&mut self, idx: usize, render_fn: FN, state: &mut SS) -> bool
274 where
275 FN: FnOnce() -> WW,
276 WW: StatefulWidget<State = SS>,
277 {
278 let Some(widget_area) = self.locate_widget(idx) else {
279 return false;
280 };
281 let mut buffer = self.buffer.borrow_mut();
282 render_fn().render(widget_area, *buffer, state);
283
284 true
285 }
286
287 #[inline(always)]
289 pub fn render_opt<FN, WW, SS>(&mut self, idx: usize, render_fn: FN, state: &mut SS) -> bool
290 where
291 FN: FnOnce() -> Option<WW>,
292 WW: StatefulWidget<State = SS>,
293 {
294 let Some(widget_area) = self.locate_widget(idx) else {
295 return false;
296 };
297 let mut buffer = self.buffer.borrow_mut();
298 let widget = render_fn();
299 if let Some(widget) = widget {
300 widget.render(widget_area, *buffer, state);
301 }
302 true
303 }
304
305 #[inline(always)]
309 #[allow(clippy::question_mark)]
310 pub fn render2<FN, WW, SS, R>(&mut self, idx: usize, render_fn: FN, state: &mut SS) -> Option<R>
311 where
312 FN: FnOnce() -> (WW, R),
313 WW: StatefulWidget<State = SS>,
314 {
315 let Some(widget_area) = self.locate_widget(idx) else {
316 return None;
317 };
318 let mut buffer = self.buffer.borrow_mut();
319 let (widget, remainder) = render_fn();
320 widget.render(widget_area, *buffer, state);
321
322 Some(remainder)
323 }
324
325 pub fn render_block(&mut self) {
327 let mut buffer = self.buffer.borrow_mut();
328 for (idx, block_area) in self.layout.borrow().block_area_iter().enumerate() {
329 if let Some(block_area) = self.locate_area(*block_area) {
330 if let Some(block) = self.layout.borrow().block(idx) {
331 block.render(block_area, *buffer);
332 }
333 }
334 }
335 }
336
337 #[inline]
341 pub fn locate_widget(&self, idx: usize) -> Option<Rect> {
342 self.locate_area(self.layout.borrow().widget(idx))
343 }
344
345 #[inline]
349 pub fn locate_label(&self, idx: usize) -> Option<Rect> {
350 self.locate_area(self.layout.borrow().label(idx))
351 }
352
353 #[inline]
358 pub fn locate_area(&self, area: Rect) -> Option<Rect> {
359 let area = self.page_area.intersection(area);
360 if area.is_empty() {
361 None
362 } else {
363 Some(Rect::new(
364 area.x - self.page_area.x + self.widget_area.x,
365 area.y - self.page_area.y + self.widget_area.y,
366 area.width,
367 area.height,
368 ))
369 }
370 }
371
372 #[inline]
374 pub fn buffer<'b>(&'b mut self) -> RefMut<'b, &'a mut Buffer> {
375 self.buffer.borrow_mut()
376 }
377}