use crate::ui::{ component::{Component, ComponentExt, Event, EventCtx, Never, Pad, PageMsg, Paginate}, display::{self, Color}, geometry::{Insets, Offset, Point, Rect}, }; use super::{theme, Button, ButtonMsg, ButtonPos}; pub struct ButtonPage { content: T, scrollbar: ScrollBar, pad: Pad, prev: Button<&'static str>, next: Button<&'static str>, cancel: Button<&'static str>, confirm: Button<&'static str>, } impl ButtonPage where T: Paginate, T: Component, { pub fn new(content: T, background: Color) -> Self { Self { content, scrollbar: ScrollBar::vertical(), pad: Pad::with_background(background), prev: Button::with_text(ButtonPos::Left, "BACK", theme::button_cancel()), next: Button::with_text(ButtonPos::Right, "NEXT", theme::button_default()), cancel: Button::with_text(ButtonPos::Left, "CANCEL", theme::button_cancel()), confirm: Button::with_text(ButtonPos::Right, "CONFIRM", theme::button_default()), } } fn change_page(&mut self, ctx: &mut EventCtx, page: usize) { // Change the page in the content, clear the background under it and make sure // it gets completely repainted. self.content.change_page(page); self.content.request_complete_repaint(ctx); self.pad.clear(); } } impl Component for ButtonPage where T: Component, T: Paginate, { type Msg = PageMsg; fn place(&mut self, bounds: Rect) -> Rect { let button_height = theme::FONT_BOLD.line_height() + 2; let (content_area, button_area) = bounds.split_bottom(button_height); let (content_area, scrollbar_area) = content_area.split_right(ScrollBar::WIDTH); let content_area = content_area.inset(Insets::top(1)); self.pad.place(bounds); self.content.place(content_area); let page_count = self.content.page_count(); self.scrollbar.set_count_and_active_page(page_count, 0); self.scrollbar.place(scrollbar_area); self.prev.place(button_area); self.next.place(button_area); self.cancel.place(button_area); self.confirm.place(button_area); bounds } fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option { ctx.set_page_count(self.scrollbar.page_count); if self.scrollbar.has_previous_page() { if let Some(ButtonMsg::Clicked) = self.prev.event(ctx, event) { // Scroll up. self.scrollbar.go_to_previous_page(); self.change_page(ctx, self.scrollbar.active_page); return None; } } else if let Some(ButtonMsg::Clicked) = self.cancel.event(ctx, event) { return Some(PageMsg::Controls(false)); } if self.scrollbar.has_next_page() { if let Some(ButtonMsg::Clicked) = self.next.event(ctx, event) { // Scroll down. self.scrollbar.go_to_next_page(); self.change_page(ctx, self.scrollbar.active_page); return None; } } else if let Some(ButtonMsg::Clicked) = self.confirm.event(ctx, event) { return Some(PageMsg::Controls(true)); } if let Some(msg) = self.content.event(ctx, event) { return Some(PageMsg::Content(msg)); } None } fn paint(&mut self) { self.pad.paint(); self.content.paint(); self.scrollbar.paint(); if self.scrollbar.has_previous_page() { self.prev.paint(); } else { self.cancel.paint(); } if self.scrollbar.has_next_page() { self.next.paint(); } else { self.confirm.paint(); } } } #[cfg(feature = "ui_debug")] impl crate::trace::Trace for ButtonPage where T: crate::trace::Trace, { fn trace(&self, t: &mut dyn crate::trace::Tracer) { t.open("ButtonPage"); t.field("active_page", &self.scrollbar.active_page); t.field("page_count", &self.scrollbar.page_count); t.field("content", &self.content); t.close(); } } pub struct ScrollBar { area: Rect, page_count: usize, active_page: usize, } impl ScrollBar { pub const WIDTH: i32 = 8; pub const DOT_SIZE: Offset = Offset::new(4, 4); pub const DOT_INTERVAL: i32 = 6; pub fn vertical() -> Self { Self { area: Rect::zero(), page_count: 0, active_page: 0, } } pub fn set_count_and_active_page(&mut self, page_count: usize, active_page: usize) { self.page_count = page_count; self.active_page = active_page; } pub fn has_next_page(&self) -> bool { self.active_page < self.page_count - 1 } pub fn has_previous_page(&self) -> bool { self.active_page > 0 } pub fn go_to_next_page(&mut self) { self.active_page = self.active_page.saturating_add(1).min(self.page_count - 1); } pub fn go_to_previous_page(&mut self) { self.active_page = self.active_page.saturating_sub(1); } fn paint_dot(&self, active: bool, top_left: Point) { let sides = [ Rect::from_top_left_and_size(top_left + Offset::x(1), Offset::new(2, 1)), Rect::from_top_left_and_size(top_left + Offset::y(1), Offset::new(1, 2)), Rect::from_top_left_and_size( top_left + Offset::new(1, Self::DOT_SIZE.y - 1), Offset::new(2, 1), ), Rect::from_top_left_and_size( top_left + Offset::new(Self::DOT_SIZE.x - 1, 1), Offset::new(1, 2), ), ]; for side in sides { display::rect_fill(side, theme::FG) } if active { display::rect_fill( Rect::from_top_left_and_size(top_left, Self::DOT_SIZE).inset(Insets::uniform(1)), theme::FG, ) } } } impl Component for ScrollBar { type Msg = Never; fn place(&mut self, bounds: Rect) -> Rect { self.area = bounds; self.area } fn event(&mut self, _ctx: &mut EventCtx, _event: Event) -> Option { None } fn paint(&mut self) { let count = self.page_count as i32; let interval = { let available_height = self.area.height(); let naive_height = count * Self::DOT_INTERVAL; if naive_height > available_height { available_height / count } else { Self::DOT_INTERVAL } }; let mut dot = Point::new( self.area.center().x - Self::DOT_SIZE.x / 2, self.area.center().y - (count / 2) * interval, ); for i in 0..self.page_count { self.paint_dot(i == self.active_page, dot); dot.y += interval } } }