You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
242 lines
7.8 KiB
242 lines
7.8 KiB
use crate::{
|
|
translations::TR,
|
|
ui::{
|
|
component::{Child, Component, ComponentExt, Event, EventCtx, Pad, PageMsg, Paginate},
|
|
display::Color,
|
|
geometry::{Insets, Rect},
|
|
shape::Renderer,
|
|
},
|
|
};
|
|
|
|
use super::{
|
|
constant, frame::ScrollableContent, theme, ButtonController, ButtonControllerMsg,
|
|
ButtonDetails, ButtonLayout, ButtonPos,
|
|
};
|
|
|
|
pub struct ButtonPage<T>
|
|
where
|
|
T: Component + Paginate,
|
|
{
|
|
page_count: usize,
|
|
active_page: usize,
|
|
content: Child<T>,
|
|
pad: Pad,
|
|
/// Left button of the first screen
|
|
cancel_btn_details: Option<ButtonDetails>,
|
|
/// Right button of the last screen
|
|
confirm_btn_details: Option<ButtonDetails>,
|
|
/// Left button of every screen
|
|
back_btn_details: Option<ButtonDetails>,
|
|
/// Right button of every screen apart the last one
|
|
next_btn_details: Option<ButtonDetails>,
|
|
buttons: Child<ButtonController>,
|
|
}
|
|
|
|
impl<T> ButtonPage<T>
|
|
where
|
|
T: Component + Paginate,
|
|
{
|
|
pub fn new(content: T, background: Color) -> Self {
|
|
Self {
|
|
page_count: 0, // will be set in place()
|
|
active_page: 0,
|
|
content: Child::new(content),
|
|
pad: Pad::with_background(background).with_clear(),
|
|
cancel_btn_details: Some(ButtonDetails::cancel_icon()),
|
|
confirm_btn_details: Some(ButtonDetails::text(TR::buttons__confirm.into())),
|
|
back_btn_details: Some(ButtonDetails::up_arrow_icon()),
|
|
next_btn_details: Some(ButtonDetails::down_arrow_icon_wide()),
|
|
// Setting empty layout for now, we do not yet know the page count.
|
|
// Initial button layout will be set in `place()` after we can call
|
|
// `content.page_count()`.
|
|
buttons: Child::new(ButtonController::new(ButtonLayout::empty())),
|
|
}
|
|
}
|
|
|
|
pub fn with_cancel_btn(mut self, btn_details: Option<ButtonDetails>) -> Self {
|
|
self.cancel_btn_details = btn_details;
|
|
self
|
|
}
|
|
|
|
pub fn with_confirm_btn(mut self, btn_details: Option<ButtonDetails>) -> Self {
|
|
self.confirm_btn_details = btn_details;
|
|
self
|
|
}
|
|
|
|
pub fn with_back_btn(mut self, btn_details: Option<ButtonDetails>) -> Self {
|
|
self.back_btn_details = btn_details;
|
|
self
|
|
}
|
|
|
|
pub fn with_next_btn(mut self, btn_details: Option<ButtonDetails>) -> Self {
|
|
self.next_btn_details = btn_details;
|
|
self
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
/// Basically just determining whether the right button for
|
|
/// initial page should be "NEXT" or "CONFIRM".
|
|
/// Can only be called when we know the final page_count.
|
|
fn set_buttons_for_initial_page(&mut self, page_count: usize) {
|
|
let btn_layout = self.get_button_layout(false, page_count > 1);
|
|
self.buttons = Child::new(ButtonController::new(btn_layout));
|
|
}
|
|
|
|
/// Called when user pressed "BACK" or "NEXT".
|
|
/// Change the page in the content, clear the background under it and make
|
|
/// sure it gets completely repainted. Also updating the buttons.
|
|
fn change_page(&mut self, ctx: &mut EventCtx) {
|
|
self.content.mutate(ctx, |ctx, content| {
|
|
content.change_page(self.active_page);
|
|
content.request_complete_repaint(ctx);
|
|
});
|
|
self.update_buttons(ctx);
|
|
self.pad.clear();
|
|
}
|
|
|
|
/// Reflecting the current page in the buttons.
|
|
fn update_buttons(&mut self, ctx: &mut EventCtx) {
|
|
let btn_layout = self.get_button_layout(self.has_previous_page(), self.has_next_page());
|
|
self.buttons.mutate(ctx, |_ctx, buttons| {
|
|
buttons.set(btn_layout);
|
|
});
|
|
}
|
|
|
|
fn get_button_layout(&self, has_prev: bool, has_next: bool) -> ButtonLayout {
|
|
let btn_left = self.get_left_button_details(!has_prev);
|
|
let btn_right = self.get_right_button_details(has_next);
|
|
ButtonLayout::new(btn_left, None, btn_right)
|
|
}
|
|
|
|
/// Get the left button details, depending whether the page is first or not.
|
|
fn get_left_button_details(&self, is_first: bool) -> Option<ButtonDetails> {
|
|
if is_first {
|
|
self.cancel_btn_details.clone()
|
|
} else {
|
|
self.back_btn_details.clone()
|
|
}
|
|
}
|
|
|
|
/// Get the right button details, depending on whether there is a next
|
|
/// page.
|
|
fn get_right_button_details(&self, has_next_page: bool) -> Option<ButtonDetails> {
|
|
if has_next_page {
|
|
self.next_btn_details.clone()
|
|
} else {
|
|
self.confirm_btn_details.clone()
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<T> ScrollableContent for ButtonPage<T>
|
|
where
|
|
T: Component + Paginate,
|
|
{
|
|
fn page_count(&self) -> usize {
|
|
self.page_count
|
|
}
|
|
fn active_page(&self) -> usize {
|
|
self.active_page
|
|
}
|
|
}
|
|
|
|
impl<T> Component for ButtonPage<T>
|
|
where
|
|
T: Component + Paginate,
|
|
{
|
|
type Msg = PageMsg<T::Msg>;
|
|
|
|
fn place(&mut self, bounds: Rect) -> Rect {
|
|
let (content_area, button_area) = bounds.split_bottom(theme::BUTTON_HEIGHT);
|
|
// Pad only the content, buttons handle it themselves.
|
|
self.pad.place(content_area);
|
|
// Moving the content LINE_SPACE pixels down, otherwise the top would not be
|
|
// padded correctly
|
|
self.content
|
|
.place(content_area.inset(Insets::top(constant::LINE_SPACE)));
|
|
// Need to be called here, only after content is placed
|
|
// and we can calculate the page count.
|
|
self.page_count = self.content.page_count();
|
|
self.set_buttons_for_initial_page(self.page_count);
|
|
self.buttons.place(button_area);
|
|
bounds
|
|
}
|
|
|
|
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
|
|
ctx.set_page_count(self.page_count());
|
|
if let Some(ButtonControllerMsg::Triggered(pos, _)) = self.buttons.event(ctx, event) {
|
|
match pos {
|
|
ButtonPos::Left => {
|
|
if self.has_previous_page() {
|
|
// Clicked BACK. Scroll up.
|
|
self.go_to_previous_page();
|
|
self.change_page(ctx);
|
|
} else {
|
|
// Clicked CANCEL. Send result.
|
|
return Some(PageMsg::Cancelled);
|
|
}
|
|
}
|
|
ButtonPos::Right => {
|
|
if self.has_next_page() {
|
|
// Clicked NEXT. Scroll down.
|
|
self.go_to_next_page();
|
|
self.change_page(ctx);
|
|
} else {
|
|
// Clicked CONFIRM. Send result.
|
|
return Some(PageMsg::Confirmed);
|
|
}
|
|
}
|
|
_ => {}
|
|
}
|
|
}
|
|
|
|
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.buttons.paint();
|
|
}
|
|
|
|
fn render<'s>(&'s self, target: &mut impl Renderer<'s>) {
|
|
self.pad.render(target);
|
|
self.content.render(target);
|
|
self.buttons.render(target);
|
|
}
|
|
}
|
|
|
|
// DEBUG-ONLY SECTION BELOW
|
|
|
|
#[cfg(feature = "ui_debug")]
|
|
impl<T> crate::trace::Trace for ButtonPage<T>
|
|
where
|
|
T: crate::trace::Trace + Paginate + Component,
|
|
{
|
|
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
|
|
t.component("ButtonPage");
|
|
t.int("active_page", self.active_page as i64);
|
|
t.int("page_count", self.page_count as i64);
|
|
t.child("buttons", &self.buttons);
|
|
t.child("content", &self.content);
|
|
}
|
|
}
|