1use std::sync::Arc;
4
5use crate::callback::Callback;
6use crate::core::element::Element;
7use crate::style::{BorderStyle, Style, StyleSlot};
8use crate::widgets::button::ButtonVariant;
9use crate::widgets::{Button, HStack, Text};
10
11#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
13pub struct PaginationState {
14 page: usize,
15 per_page: usize,
16 total_items: usize,
17}
18
19impl PaginationState {
20 pub fn new(total_items: usize, per_page: usize) -> Self {
22 let per_page = per_page.max(1);
23 let mut state = Self {
24 page: 0,
25 per_page,
26 total_items,
27 };
28 state.clamp_page();
29 state
30 }
31
32 pub fn page(&self) -> usize {
34 self.page
35 }
36
37 pub fn per_page(&self) -> usize {
39 self.per_page
40 }
41
42 pub fn total_items(&self) -> usize {
44 self.total_items
45 }
46
47 pub fn total_pages(&self) -> usize {
49 self.total_items.max(1).div_ceil(self.per_page)
50 }
51
52 pub fn is_first_page(&self) -> bool {
54 self.page == 0
55 }
56
57 pub fn is_last_page(&self) -> bool {
59 self.page + 1 >= self.total_pages()
60 }
61
62 pub fn set_page(&mut self, page: usize) {
64 self.page = page;
65 self.clamp_page();
66 }
67
68 pub fn prev_page(&mut self) {
70 self.page = self.page.saturating_sub(1);
71 }
72
73 pub fn next_page(&mut self) {
75 if !self.is_last_page() {
76 self.page += 1;
77 }
78 }
79
80 pub fn first_page(&mut self) {
82 self.page = 0;
83 }
84
85 pub fn last_page(&mut self) {
87 self.page = self.total_pages().saturating_sub(1);
88 }
89
90 pub fn set_per_page(&mut self, per_page: usize) {
92 self.per_page = per_page.max(1);
93 self.clamp_page();
94 }
95
96 pub fn set_total_items(&mut self, total_items: usize) {
98 self.total_items = total_items;
99 self.clamp_page();
100 }
101
102 pub fn range(&self) -> (usize, usize) {
104 let start = self.page.saturating_mul(self.per_page);
105 let end = start.saturating_add(self.per_page).min(self.total_items);
106 (start.min(self.total_items), end)
107 }
108
109 fn clamp_page(&mut self) {
110 self.page = self.page.min(self.total_pages().saturating_sub(1));
111 }
112}
113
114impl Default for PaginationState {
115 fn default() -> Self {
116 Self::new(0, 10)
117 }
118}
119
120#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
122pub enum PaginationAction {
123 First,
125 Prev,
127 Next,
129 Last,
131}
132
133#[derive(Clone, Debug, PartialEq, Eq, Hash)]
135pub struct PaginationLabels {
136 pub first: Arc<str>,
138 pub prev: Arc<str>,
140 pub next: Arc<str>,
142 pub last: Arc<str>,
144}
145
146impl Default for PaginationLabels {
147 fn default() -> Self {
148 Self {
149 first: "<<".into(),
150 prev: "<".into(),
151 next: ">".into(),
152 last: ">>".into(),
153 }
154 }
155}
156
157#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
159pub struct PaginationButtonOverrides {
160 pub variant: Option<ButtonVariant>,
162 pub border_style: Option<BorderStyle>,
164 pub style: Option<Style>,
166 pub hover_style: Option<StyleSlot>,
168 pub focus_style: Option<StyleSlot>,
170 pub disabled_style: Option<Style>,
172}
173
174impl PaginationButtonOverrides {
175 pub fn new() -> Self {
177 Self::default()
178 }
179
180 pub fn variant(mut self, variant: ButtonVariant) -> Self {
182 self.variant = Some(variant);
183 self
184 }
185
186 pub fn border_style(mut self, border_style: BorderStyle) -> Self {
188 self.border_style = Some(border_style);
189 self
190 }
191
192 pub fn style(mut self, style: Style) -> Self {
194 self.style = Some(style);
195 self
196 }
197
198 pub fn hover_style(mut self, style: Style) -> Self {
200 self.hover_style = Some(StyleSlot::Replace(style));
201 self
202 }
203
204 pub fn extend_hover_style(mut self, style: Style) -> Self {
206 self.hover_style = Some(StyleSlot::Extend(style));
207 self
208 }
209
210 pub fn inherit_hover_style(mut self) -> Self {
212 self.hover_style = Some(StyleSlot::Inherit);
213 self
214 }
215
216 pub fn hover_style_slot(mut self, slot: StyleSlot) -> Self {
218 self.hover_style = Some(slot);
219 self
220 }
221
222 pub fn focus_style(mut self, style: Style) -> Self {
224 self.focus_style = Some(StyleSlot::Replace(style));
225 self
226 }
227
228 pub fn extend_focus_style(mut self, style: Style) -> Self {
230 self.focus_style = Some(StyleSlot::Extend(style));
231 self
232 }
233
234 pub fn inherit_focus_style(mut self) -> Self {
236 self.focus_style = Some(StyleSlot::Inherit);
237 self
238 }
239
240 pub fn focus_style_slot(mut self, slot: StyleSlot) -> Self {
242 self.focus_style = Some(slot);
243 self
244 }
245
246 pub fn disabled_style(mut self, style: Style) -> Self {
248 self.disabled_style = Some(style);
249 self
250 }
251}
252
253#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
255pub struct PaginationInfo {
256 pub page_index: usize,
258 pub page_number: usize,
260 pub total_pages: usize,
262 pub total_items: usize,
264 pub per_page: usize,
266 pub start: usize,
268 pub end: usize,
270}
271
272type PaginationInfoFormatter = Arc<dyn Fn(PaginationInfo) -> Arc<str>>;
273
274#[derive(Clone)]
276pub struct PaginationBar {
277 state: PaginationState,
278 labels: PaginationLabels,
279 show_first_last: bool,
280 show_range_info: bool,
281 gap: u16,
282 button_variant: ButtonVariant,
283 button_border_style: BorderStyle,
284 button_style: Style,
285 button_hover_style: StyleSlot,
286 button_focus_style: StyleSlot,
287 button_disabled_style: Style,
288 button_overrides: [PaginationButtonOverrides; 4],
289 info_style: Style,
290 info_formatter: Option<PaginationInfoFormatter>,
291 on_action: Option<Callback<PaginationAction>>,
292}
293
294impl PaginationBar {
295 pub fn new(state: PaginationState) -> Self {
297 Self {
298 state,
299 labels: PaginationLabels::default(),
300 show_first_last: true,
301 show_range_info: true,
302 gap: 1,
303 button_variant: ButtonVariant::Outlined,
304 button_border_style: BorderStyle::Plain,
305 button_style: Style::default(),
306 button_hover_style: StyleSlot::Inherit,
307 button_focus_style: StyleSlot::Inherit,
308 button_disabled_style: Style::default(),
309 button_overrides: [PaginationButtonOverrides::default(); 4],
310 info_style: Style::default(),
311 info_formatter: None,
312 on_action: None,
313 }
314 }
315
316 pub fn labels(mut self, labels: PaginationLabels) -> Self {
318 self.labels = labels;
319 self
320 }
321
322 pub fn first_label(mut self, label: impl Into<Arc<str>>) -> Self {
324 self.labels.first = label.into();
325 self
326 }
327
328 pub fn prev_label(mut self, label: impl Into<Arc<str>>) -> Self {
330 self.labels.prev = label.into();
331 self
332 }
333
334 pub fn next_label(mut self, label: impl Into<Arc<str>>) -> Self {
336 self.labels.next = label.into();
337 self
338 }
339
340 pub fn last_label(mut self, label: impl Into<Arc<str>>) -> Self {
342 self.labels.last = label.into();
343 self
344 }
345
346 pub fn show_first_last(mut self, show: bool) -> Self {
348 self.show_first_last = show;
349 self
350 }
351
352 pub fn show_range_info(mut self, show: bool) -> Self {
354 self.show_range_info = show;
355 self
356 }
357
358 pub fn gap(mut self, gap: u16) -> Self {
360 self.gap = gap;
361 self
362 }
363
364 pub fn button_variant(mut self, variant: ButtonVariant) -> Self {
366 self.button_variant = variant;
367 self
368 }
369
370 pub fn button_border_style(mut self, style: BorderStyle) -> Self {
372 self.button_border_style = style;
373 self
374 }
375
376 pub fn button_style(mut self, style: Style) -> Self {
378 self.button_style = style;
379 self
380 }
381
382 pub fn button_hover_style(mut self, style: Style) -> Self {
384 self.button_hover_style = StyleSlot::Replace(style);
385 self
386 }
387
388 pub fn extend_button_hover_style(mut self, style: Style) -> Self {
390 self.button_hover_style = StyleSlot::Extend(style);
391 self
392 }
393
394 pub fn inherit_button_hover_style(mut self) -> Self {
396 self.button_hover_style = StyleSlot::Inherit;
397 self
398 }
399
400 pub fn button_hover_style_slot(mut self, slot: StyleSlot) -> Self {
402 self.button_hover_style = slot;
403 self
404 }
405
406 pub fn button_focus_style(mut self, style: Style) -> Self {
408 self.button_focus_style = StyleSlot::Replace(style);
409 self
410 }
411
412 pub fn extend_button_focus_style(mut self, style: Style) -> Self {
414 self.button_focus_style = StyleSlot::Extend(style);
415 self
416 }
417
418 pub fn inherit_button_focus_style(mut self) -> Self {
420 self.button_focus_style = StyleSlot::Inherit;
421 self
422 }
423
424 pub fn button_focus_style_slot(mut self, slot: StyleSlot) -> Self {
426 self.button_focus_style = slot;
427 self
428 }
429
430 pub fn button_disabled_style(mut self, style: Style) -> Self {
432 self.button_disabled_style = style;
433 self
434 }
435
436 pub fn button_overrides_for(
438 mut self,
439 action: PaginationAction,
440 overrides: PaginationButtonOverrides,
441 ) -> Self {
442 self.button_overrides[action_index(action)] = overrides;
443 self
444 }
445
446 pub fn first_button_overrides(mut self, overrides: PaginationButtonOverrides) -> Self {
448 self.button_overrides[action_index(PaginationAction::First)] = overrides;
449 self
450 }
451
452 pub fn prev_button_overrides(mut self, overrides: PaginationButtonOverrides) -> Self {
454 self.button_overrides[action_index(PaginationAction::Prev)] = overrides;
455 self
456 }
457
458 pub fn next_button_overrides(mut self, overrides: PaginationButtonOverrides) -> Self {
460 self.button_overrides[action_index(PaginationAction::Next)] = overrides;
461 self
462 }
463
464 pub fn last_button_overrides(mut self, overrides: PaginationButtonOverrides) -> Self {
466 self.button_overrides[action_index(PaginationAction::Last)] = overrides;
467 self
468 }
469
470 pub fn info_style(mut self, style: Style) -> Self {
472 self.info_style = style;
473 self
474 }
475
476 pub fn info_formatter<F>(mut self, formatter: F) -> Self
478 where
479 F: Fn(PaginationInfo) -> Arc<str> + 'static,
480 {
481 self.info_formatter = Some(Arc::new(formatter));
482 self
483 }
484
485 pub fn on_action(mut self, cb: Callback<PaginationAction>) -> Self {
487 self.on_action = Some(cb);
488 self
489 }
490
491 fn nav_button(&self, label: Arc<str>, disabled: bool, action: PaginationAction) -> Button {
492 let overrides = self.button_overrides[action_index(action)];
493 let variant = overrides.variant.unwrap_or(self.button_variant);
494 let border_style = overrides.border_style.unwrap_or(self.button_border_style);
495 let style = overrides.style.unwrap_or(self.button_style);
496 let hover_style = overrides.hover_style.unwrap_or(self.button_hover_style);
497 let focus_style = overrides.focus_style.unwrap_or(self.button_focus_style);
498 let disabled_style = overrides
499 .disabled_style
500 .unwrap_or(self.button_disabled_style);
501
502 let mut button = Button::new(label)
503 .variant(variant)
504 .style(style)
505 .hover_style_slot(hover_style)
506 .focus_style_slot(focus_style)
507 .disabled_style(disabled_style)
508 .disabled(disabled);
509
510 if matches!(variant, ButtonVariant::Outlined) {
511 button = button.border_style(border_style);
512 }
513
514 if let Some(cb) = self.on_action.clone() {
515 button = button.on_click(Callback::new(move |_| cb.emit(action)));
516 }
517
518 button
519 }
520}
521
522impl From<PaginationBar> for Element {
523 fn from(bar: PaginationBar) -> Self {
524 let mut row = HStack::new().gap(bar.gap);
525
526 if bar.show_first_last {
527 row = row.child(bar.nav_button(
528 bar.labels.first.clone(),
529 bar.state.is_first_page(),
530 PaginationAction::First,
531 ));
532 }
533
534 row = row.child(bar.nav_button(
535 bar.labels.prev.clone(),
536 bar.state.is_first_page(),
537 PaginationAction::Prev,
538 ));
539
540 let page = bar.state.page() + 1;
541 let total_pages = bar.state.total_pages();
542 let total = bar.state.total_items();
543 let (start, end) = bar.state.range();
544 let first_row = if total == 0 {
545 0
546 } else {
547 start.saturating_add(1)
548 };
549 let info_data = PaginationInfo {
550 page_index: bar.state.page(),
551 page_number: page,
552 total_pages,
553 total_items: total,
554 per_page: bar.state.per_page(),
555 start,
556 end,
557 };
558 let info = if let Some(formatter) = bar.info_formatter.as_ref() {
559 formatter(info_data)
560 } else if bar.show_range_info {
561 Arc::from(format!(
562 "Page {}/{} (rows {}-{} of {})",
563 page, total_pages, first_row, end, total
564 ))
565 } else {
566 Arc::from(format!("Page {}/{}", page, total_pages))
567 };
568 row = row.child(Text::new(info).style(bar.info_style));
569
570 row = row.child(bar.nav_button(
571 bar.labels.next.clone(),
572 bar.state.is_last_page(),
573 PaginationAction::Next,
574 ));
575
576 if bar.show_first_last {
577 row = row.child(bar.nav_button(
578 bar.labels.last.clone(),
579 bar.state.is_last_page(),
580 PaginationAction::Last,
581 ));
582 }
583
584 row.into()
585 }
586}
587
588fn action_index(action: PaginationAction) -> usize {
589 match action {
590 PaginationAction::First => 0,
591 PaginationAction::Prev => 1,
592 PaginationAction::Next => 2,
593 PaginationAction::Last => 3,
594 }
595}
596
597#[cfg(test)]
598mod tests {
599 use super::{PaginationLabels, PaginationState};
600
601 #[test]
602 fn clamps_page_to_last_after_total_change() {
603 let mut state = PaginationState::new(120, 10);
604 state.set_page(9);
605 state.set_total_items(15);
606 assert_eq!(state.page(), 1);
607 }
608
609 #[test]
610 fn range_matches_page_window() {
611 let mut state = PaginationState::new(53, 10);
612 state.set_page(2);
613 assert_eq!(state.range(), (20, 30));
614
615 state.last_page();
616 assert_eq!(state.range(), (50, 53));
617 }
618
619 #[test]
620 fn default_labels_are_ascii_navigation_arrows() {
621 let labels = PaginationLabels::default();
622 assert_eq!(labels.first.as_ref(), "<<");
623 assert_eq!(labels.prev.as_ref(), "<");
624 assert_eq!(labels.next.as_ref(), ">");
625 assert_eq!(labels.last.as_ref(), ">>");
626 }
627}