1use crate::_private::NonExhaustive;
2use crate::event::PagerOutcome;
3use crate::pager::PagerStyle;
4use crate::util::revert_style;
5use rat_event::util::MouseFlagsN;
6use rat_event::{ConsumedEvent, HandleEvent, MouseOnly, Regular, ct_event};
7use rat_focus::{FocusFlag, HasFocus};
8use ratatui::buffer::Buffer;
9use ratatui::layout::{Alignment, Rect, Size};
10use ratatui::style::Style;
11use ratatui::text::Span;
12use ratatui::widgets::{Block, StatefulWidget, Widget};
13use std::cmp::min;
14use unicode_display_width::width as unicode_width;
15
16#[derive(Debug, Clone)]
18pub struct PageNavigation<'a> {
19 pages: u8,
20 block: Option<Block<'a>>,
21 style: Style,
22 nav_style: Option<Style>,
23 title_style: Option<Style>,
24 next_page: &'a str,
25 prev_page: &'a str,
26 first_page: &'a str,
27 last_page: &'a str,
28}
29
30#[derive(Debug, Clone)]
32pub struct PageNavigationState {
33 pub area: Rect,
36 pub widget_areas: Vec<Rect>,
39 pub prev_area: Rect,
42 pub next_area: Rect,
45
46 pub page: usize,
49 pub page_inc: usize,
52
53 pub page_count: usize,
56
57 pub container: FocusFlag,
60
61 pub mouse: MouseFlagsN,
63
64 pub non_exhaustive: NonExhaustive,
66}
67
68impl Default for PageNavigation<'_> {
69 fn default() -> Self {
70 Self {
71 pages: 1,
72 block: Default::default(),
73 style: Default::default(),
74 nav_style: Default::default(),
75 title_style: Default::default(),
76 next_page: ">>>",
77 prev_page: "<<<",
78 first_page: " [ ",
79 last_page: " ] ",
80 }
81 }
82}
83
84impl<'a> PageNavigation<'a> {
85 pub fn new() -> Self {
86 Self::default()
87 }
88
89 pub fn pages(mut self, pages: u8) -> Self {
95 self.pages = pages;
96 self
97 }
98
99 pub fn style(mut self, style: Style) -> Self {
101 self.style = style;
102 self.block = self.block.map(|v| v.style(style));
103 self
104 }
105
106 pub fn nav_style(mut self, nav_style: Style) -> Self {
108 self.nav_style = Some(nav_style);
109 self
110 }
111
112 pub fn title_style(mut self, title_style: Style) -> Self {
114 self.title_style = Some(title_style);
115 self
116 }
117
118 pub fn block(mut self, block: Block<'a>) -> Self {
120 self.block = Some(block.style(self.style));
121 self
122 }
123
124 pub fn next_page_mark(mut self, txt: &'a str) -> Self {
125 self.next_page = txt;
126 self
127 }
128
129 pub fn prev_page_mark(mut self, txt: &'a str) -> Self {
130 self.prev_page = txt;
131 self
132 }
133
134 pub fn first_page_mark(mut self, txt: &'a str) -> Self {
135 self.first_page = txt;
136 self
137 }
138
139 pub fn last_page_mark(mut self, txt: &'a str) -> Self {
140 self.last_page = txt;
141 self
142 }
143
144 pub fn styles(mut self, styles: PagerStyle) -> Self {
146 self.style = styles.style;
147 if let Some(nav) = styles.navigation {
148 self.nav_style = Some(nav);
149 }
150 if let Some(title) = styles.title {
151 self.title_style = Some(title);
152 }
153 if let Some(block) = styles.block {
154 self.block = Some(block);
155 }
156 if let Some(txt) = styles.next_page_mark {
157 self.next_page = txt;
158 }
159 if let Some(txt) = styles.prev_page_mark {
160 self.prev_page = txt;
161 }
162 if let Some(txt) = styles.first_page_mark {
163 self.first_page = txt;
164 }
165 if let Some(txt) = styles.last_page_mark {
166 self.last_page = txt;
167 }
168 self.block = self.block.map(|v| v.style(styles.style));
169 self
170 }
171
172 pub fn layout_size(&self, area: Rect) -> Size {
174 let inner = self.inner(area);
175 Size::new(inner.width / self.pages as u16, inner.height)
176 }
177
178 pub fn inner(&self, area: Rect) -> Rect {
180 if let Some(block) = &self.block {
181 block.inner(area)
182 } else {
183 area
184 }
185 }
186}
187
188impl StatefulWidget for PageNavigation<'_> {
189 type State = PageNavigationState;
190
191 fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
192 state.area = area;
193 state.page_inc = self.pages as usize;
194
195 let widget_area = self.inner(area);
196
197 let width = widget_area.width / self.pages as u16;
198 let mut column_area = Rect::new(widget_area.x, widget_area.y, width, widget_area.height);
199 state.widget_areas.clear();
200 for _ in 0..self.pages {
201 state.widget_areas.push(column_area);
202 column_area.x += column_area.width;
203 }
204
205 if state.page > 0 {
206 state.prev_area = Rect::new(
207 widget_area.x,
208 area.y,
209 unicode_width(self.prev_page) as u16,
210 1,
211 );
212 } else {
213 state.prev_area = Rect::new(
214 widget_area.x,
215 area.y,
216 unicode_width(self.first_page) as u16,
217 1,
218 );
219 }
220 if (state.page + self.pages as usize) < state.page_count {
221 let p = unicode_width(self.next_page) as u16;
222 state.next_area = Rect::new(
223 widget_area.x + widget_area.width.saturating_sub(p),
224 area.y,
225 p,
226 1,
227 );
228 } else {
229 let p = unicode_width(self.last_page) as u16;
230 state.next_area = Rect::new(
231 widget_area.x + widget_area.width.saturating_sub(p),
232 area.y,
233 p,
234 1,
235 );
236 }
237
238 let block = if state.page_count > 1 {
240 let title = format!(" {}/{} ", state.page + 1, state.page_count);
241 let block = self
242 .block
243 .unwrap_or_else(|| Block::new().style(self.style))
244 .title_bottom(title)
245 .title_alignment(Alignment::Right);
246 if let Some(title_style) = self.title_style {
247 block.title_style(title_style)
248 } else {
249 block
250 }
251 } else {
252 self.block.unwrap_or_else(|| Block::new().style(self.style))
253 };
254 block.render(area, buf);
255
256 let nav_style = self.nav_style.unwrap_or(self.style);
258 if matches!(state.mouse.hover.get(), Some(0)) {
259 buf.set_style(state.prev_area, revert_style(nav_style));
260 } else {
261 buf.set_style(state.prev_area, nav_style);
262 }
263 if state.page > 0 {
264 Span::from(self.prev_page).render(state.prev_area, buf);
265 } else {
266 Span::from(self.first_page).render(state.prev_area, buf);
267 }
268 if matches!(state.mouse.hover.get(), Some(1)) {
269 buf.set_style(state.next_area, revert_style(nav_style));
270 } else {
271 buf.set_style(state.next_area, nav_style);
272 }
273 if (state.page + self.pages as usize) < state.page_count {
274 Span::from(self.next_page).render(state.next_area, buf);
275 } else {
276 Span::from(self.last_page).render(state.next_area, buf);
277 }
278 }
279}
280
281impl Default for PageNavigationState {
282 fn default() -> Self {
283 Self {
284 area: Default::default(),
285 widget_areas: Default::default(),
286 prev_area: Default::default(),
287 next_area: Default::default(),
288 page: Default::default(),
289 page_inc: 1,
290 page_count: Default::default(),
291 container: Default::default(),
292 mouse: Default::default(),
293 non_exhaustive: NonExhaustive,
294 }
295 }
296}
297
298impl PageNavigationState {
299 pub fn new() -> Self {
300 Self::default()
301 }
302
303 pub fn clear(&mut self) {
305 self.page = 0;
306 self.page_count = 0;
307 }
308
309 pub fn set_page(&mut self, page: usize) -> bool {
311 let old_page = self.page;
312 self.page = min(page, self.page_count.saturating_sub(1));
313 old_page != self.page
314 }
315
316 pub fn page(&self) -> usize {
318 self.page
319 }
320
321 pub fn set_page_count(&mut self, count: usize) {
323 self.page_count = count;
324 self.page = min(self.page, count.saturating_sub(1));
325 }
326
327 pub fn page_count(&self) -> usize {
329 self.page_count
330 }
331
332 pub fn next_page(&mut self) -> bool {
334 let old_page = self.page;
335
336 if self.page + self.page_inc == self.page_count {
337 } else if self.page + self.page_inc > self.page_count {
339 self.page = self.page_count.saturating_sub(1);
340 } else {
341 self.page += self.page_inc;
342 }
343
344 old_page != self.page
345 }
346
347 pub fn prev_page(&mut self) -> bool {
349 if self.page >= self.page_inc {
350 self.page -= self.page_inc;
351 true
352 } else if self.page > 0 {
353 self.page = 0;
354 true
355 } else {
356 false
357 }
358 }
359}
360
361impl HandleEvent<crossterm::event::Event, Regular, PagerOutcome> for PageNavigationState {
362 fn handle(&mut self, event: &crossterm::event::Event, _qualifier: Regular) -> PagerOutcome {
363 let r = if self.container.is_focused() {
364 match event {
365 ct_event!(keycode press ALT-PageUp) => {
366 if self.prev_page() {
367 PagerOutcome::Page(self.page())
368 } else {
369 PagerOutcome::Continue
370 }
371 }
372 ct_event!(keycode press ALT-PageDown) => {
373 if self.next_page() {
374 PagerOutcome::Page(self.page())
375 } else {
376 PagerOutcome::Continue
377 }
378 }
379 _ => PagerOutcome::Continue,
380 }
381 } else {
382 PagerOutcome::Continue
383 };
384
385 r.or_else(|| self.handle(event, MouseOnly))
386 }
387}
388
389impl HandleEvent<crossterm::event::Event, MouseOnly, PagerOutcome> for PageNavigationState {
390 fn handle(&mut self, event: &crossterm::event::Event, _qualifier: MouseOnly) -> PagerOutcome {
391 match event {
392 ct_event!(mouse down Left for x,y) if self.prev_area.contains((*x, *y).into()) => {
393 if self.prev_page() {
394 PagerOutcome::Page(self.page)
395 } else {
396 PagerOutcome::Unchanged
397 }
398 }
399 ct_event!(mouse down Left for x,y) if self.next_area.contains((*x, *y).into()) => {
400 if self.next_page() {
401 PagerOutcome::Page(self.page)
402 } else {
403 PagerOutcome::Unchanged
404 }
405 }
406 ct_event!(scroll down for x,y) => {
407 if self.area.contains((*x, *y).into()) {
408 if self.next_page() {
409 PagerOutcome::Page(self.page)
410 } else {
411 PagerOutcome::Continue
412 }
413 } else {
414 PagerOutcome::Continue
415 }
416 }
417 ct_event!(scroll up for x,y) => {
418 if self.area.contains((*x, *y).into()) {
419 if self.prev_page() {
420 PagerOutcome::Page(self.page)
421 } else {
422 PagerOutcome::Continue
423 }
424 } else {
425 PagerOutcome::Continue
426 }
427 }
428 ct_event!(mouse any for m)
429 if self.mouse.hover(&[self.prev_area, self.next_area], m) =>
430 {
431 PagerOutcome::Changed
432 }
433 _ => PagerOutcome::Continue,
434 }
435 }
436}