use egui::{FontId, Response, Sense, Stroke, StrokeKind, Ui, Vec2};
use super::{alpha, corner};
use crate::{Icon, RADIUS, SPACING, palette_of};
pub struct Pagination<'a> {
page: &'a mut usize,
total_items: usize,
page_size: &'a mut usize,
page_sizes: Vec<usize>,
}
impl<'a> Pagination<'a> {
pub fn new(page: &'a mut usize, total_items: usize, page_size: &'a mut usize) -> Self {
Self {
page,
total_items,
page_size,
page_sizes: vec![10, 25, 50, 100],
}
}
pub fn page_sizes(mut self, sizes: impl IntoIterator<Item = usize>) -> Self {
self.page_sizes = sizes.into_iter().collect();
self
}
pub fn show(self, ui: &mut Ui) -> Response {
let palette = palette_of(ui.ctx());
let total_pages = if *self.page_size == 0 {
1
} else {
self.total_items.div_ceil(*self.page_size).max(1)
};
if *self.page >= total_pages {
*self.page = total_pages - 1;
}
let from = if self.total_items == 0 {
0
} else {
*self.page * *self.page_size + 1
};
let to = ((*self.page + 1) * *self.page_size).min(self.total_items);
ui.horizontal(|ui| {
ui.label(
egui::RichText::new(format!(
"Showing {from}–{to} of {total}",
total = self.total_items
))
.text_style(egui::TextStyle::Small)
.color(palette.text_secondary),
);
ui.add_space(SPACING.s4);
let prev_size = *self.page_size;
egui::ComboBox::from_id_salt("page_size")
.selected_text(format!("{} / page", *self.page_size))
.width(110.0)
.show_ui(ui, |ui| {
for size in &self.page_sizes {
ui.selectable_value(self.page_size, *size, format!("{size} / page"));
}
});
if *self.page_size != prev_size {
let first_item = (*self.page) * prev_size;
*self.page = first_item / (*self.page_size).max(1);
}
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
let next_disabled = *self.page + 1 >= total_pages;
let prev_disabled = *self.page == 0;
let next_resp = paint_arrow(ui, Icon::ChevronRight, next_disabled);
if next_resp.clicked() && !next_disabled {
*self.page += 1;
}
ui.add_space(SPACING.s2);
ui.label(
egui::RichText::new(format!("{}/{}", *self.page + 1, total_pages))
.font(FontId::new(12.0, egui::FontFamily::Monospace))
.color(palette.text_primary),
);
ui.add_space(SPACING.s2);
let prev_resp = paint_arrow(ui, Icon::ChevronLeft, prev_disabled);
if prev_resp.clicked() && !prev_disabled {
*self.page -= 1;
}
});
})
.response
}
}
fn paint_arrow(ui: &mut Ui, icon: Icon, disabled: bool) -> Response {
let palette = palette_of(ui.ctx());
let size = 26.0;
let sense = if disabled {
Sense::hover()
} else {
Sense::click()
};
let (rect, resp) = ui.allocate_exact_size(Vec2::splat(size), sense);
let bg = if !disabled && resp.hovered() {
palette.bg_hover
} else {
egui::Color32::TRANSPARENT
};
ui.painter().rect(
rect,
corner(RADIUS.sm),
bg,
Stroke::new(1.0, palette.border_default),
StrokeKind::Inside,
);
let color = if disabled {
alpha(palette.text_secondary, 0.45)
} else {
palette.text_primary
};
icon.paint(ui.painter(), rect.shrink(6.0), color);
resp
}