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