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.
922 lines
27 KiB
922 lines
27 KiB
use crate::{
|
|
time::Duration,
|
|
ui::{
|
|
component::{Component, Event, EventCtx},
|
|
constant,
|
|
display::{self, Color, Font, Icon},
|
|
geometry::{Offset, Point, Rect},
|
|
},
|
|
StrBuffer,
|
|
};
|
|
|
|
use super::theme;
|
|
|
|
const HALF_SCREEN_BUTTON_WIDTH: i16 = constant::WIDTH / 2 - 1;
|
|
|
|
#[derive(Eq, PartialEq)]
|
|
pub enum ButtonMsg {
|
|
Clicked,
|
|
LongPressed,
|
|
}
|
|
|
|
#[derive(Copy, Clone)]
|
|
pub enum ButtonPos {
|
|
Left,
|
|
Middle,
|
|
Right,
|
|
}
|
|
|
|
pub struct Button {
|
|
bounds: Rect,
|
|
pos: ButtonPos,
|
|
content: ButtonContent,
|
|
styles: ButtonStyleSheet,
|
|
state: State,
|
|
}
|
|
|
|
impl Button {
|
|
pub fn new(pos: ButtonPos, content: ButtonContent, styles: ButtonStyleSheet) -> Self {
|
|
Self {
|
|
pos,
|
|
content,
|
|
styles,
|
|
bounds: Rect::zero(),
|
|
state: State::Released,
|
|
}
|
|
}
|
|
|
|
pub fn with_text(pos: ButtonPos, text: StrBuffer, styles: ButtonStyleSheet) -> Self {
|
|
Self::new(pos, ButtonContent::Text(text), styles)
|
|
}
|
|
|
|
pub fn with_icon(pos: ButtonPos, image: Icon, styles: ButtonStyleSheet) -> Self {
|
|
Self::new(pos, ButtonContent::Icon(image), styles)
|
|
}
|
|
|
|
pub fn content(&self) -> &ButtonContent {
|
|
&self.content
|
|
}
|
|
|
|
fn style(&self) -> ButtonStyle {
|
|
match self.state {
|
|
State::Released => self.styles.normal,
|
|
State::Pressed => self.styles.active,
|
|
}
|
|
}
|
|
|
|
/// Changing the icon content of the button.
|
|
pub fn set_icon(&mut self, image: Icon) {
|
|
self.content = ButtonContent::Icon(image);
|
|
}
|
|
|
|
/// Changing the text content of the button.
|
|
pub fn set_text(&mut self, text: StrBuffer) {
|
|
self.content = ButtonContent::Text(text);
|
|
}
|
|
|
|
/// Changing the style of the button.
|
|
pub fn set_style(&mut self, styles: ButtonStyleSheet) {
|
|
self.styles = styles;
|
|
}
|
|
|
|
// Setting the visual state of the button.
|
|
fn set(&mut self, ctx: &mut EventCtx, state: State) {
|
|
if self.state != state {
|
|
self.state = state;
|
|
ctx.request_paint();
|
|
}
|
|
}
|
|
|
|
// Setting the visual state of the button.
|
|
pub fn set_pressed(&mut self, ctx: &mut EventCtx, is_pressed: bool) {
|
|
let new_state = if is_pressed {
|
|
State::Pressed
|
|
} else {
|
|
State::Released
|
|
};
|
|
self.set(ctx, new_state);
|
|
}
|
|
|
|
/// Return the full area of the button according
|
|
/// to its current style, content and position.
|
|
fn get_current_area(&self) -> Rect {
|
|
let style = self.style();
|
|
|
|
// Button width may be forced. Otherwise calculate it.
|
|
let button_width = if let Some(width) = style.force_width {
|
|
width
|
|
} else {
|
|
let outline = if style.with_outline {
|
|
theme::BUTTON_OUTLINE
|
|
} else {
|
|
0
|
|
};
|
|
let content_width = match &self.content {
|
|
ButtonContent::Text(text) => style.font.text_width(text.as_ref()) - 1,
|
|
ButtonContent::Icon(icon) => icon.width() - 1,
|
|
};
|
|
content_width + 2 * outline
|
|
};
|
|
|
|
// Button height may be adjusted for the icon without outline
|
|
// Done to avoid highlighting bigger area than necessary when
|
|
// drawing the icon in active (black on white) state
|
|
let button_height = match &self.content {
|
|
ButtonContent::Text(_) => theme::BUTTON_HEIGHT,
|
|
ButtonContent::Icon(icon) => {
|
|
if style.with_outline {
|
|
theme::BUTTON_HEIGHT
|
|
} else {
|
|
icon.height()
|
|
}
|
|
}
|
|
};
|
|
|
|
let button_bounds = self.bounds.split_bottom(button_height).1;
|
|
let area = match self.pos {
|
|
ButtonPos::Left => button_bounds.split_left(button_width).0,
|
|
ButtonPos::Right => button_bounds.split_right(button_width).1,
|
|
ButtonPos::Middle => button_bounds.split_center(button_width).1,
|
|
};
|
|
|
|
// Allowing for possible offset of the area from current style
|
|
if let Some(offset) = style.offset {
|
|
area.translate(offset)
|
|
} else {
|
|
area
|
|
}
|
|
}
|
|
|
|
/// Determine baseline point for the text.
|
|
fn get_text_baseline(&self, style: &ButtonStyle) -> Point {
|
|
// Arms and outline require the text to be elevated.
|
|
let offset_y = if style.with_arms || style.with_outline {
|
|
theme::BUTTON_OUTLINE
|
|
} else {
|
|
0
|
|
};
|
|
|
|
let offset_x = if style.with_outline {
|
|
theme::BUTTON_OUTLINE
|
|
} else {
|
|
0
|
|
};
|
|
|
|
self.get_current_area().bottom_left() + Offset::new(offset_x, -offset_y)
|
|
}
|
|
}
|
|
|
|
impl Component for Button {
|
|
type Msg = ButtonMsg;
|
|
|
|
fn place(&mut self, bounds: Rect) -> Rect {
|
|
self.bounds = bounds;
|
|
self.get_current_area()
|
|
}
|
|
|
|
fn event(&mut self, _ctx: &mut EventCtx, _event: Event) -> Option<Self::Msg> {
|
|
// Events are handled by `ButtonController`
|
|
None
|
|
}
|
|
|
|
fn paint(&mut self) {
|
|
let style = self.style();
|
|
let text_color = style.text_color;
|
|
let background_color = text_color.negate();
|
|
let area = self.get_current_area();
|
|
|
|
// Optionally display "arms" at both sides of content, or create
|
|
// a nice rounded outline around it.
|
|
// By default just fill the content background.
|
|
if style.with_arms {
|
|
const ARM_WIDTH: i16 = 15;
|
|
|
|
// Prepare space for both the arms and content with BG color.
|
|
// Arms are icons 10*6 pixels.
|
|
let area_to_fill = area.extend_left(ARM_WIDTH).extend_right(ARM_WIDTH);
|
|
display::rect_fill(area_to_fill, background_color);
|
|
display::rect_fill_corners(area_to_fill, theme::BG);
|
|
|
|
// Paint both arms.
|
|
// Baselines are adjusted to give space between text and icon.
|
|
// 2 px because 1px might lead to odd coordinate which can't be render
|
|
Icon::new(theme::ICON_ARM_LEFT).draw_top_right(
|
|
area.left_center() + Offset::x(-2),
|
|
text_color,
|
|
background_color,
|
|
);
|
|
Icon::new(theme::ICON_ARM_RIGHT).draw_top_left(
|
|
area.right_center() + Offset::x(2),
|
|
text_color,
|
|
background_color,
|
|
);
|
|
} else if style.with_outline {
|
|
if background_color == theme::BG {
|
|
display::rect_outline_rounded(area, text_color, background_color, 2);
|
|
} else {
|
|
// With inverse colors having just radius of one, `rect_outline_rounded`
|
|
// is not suitable for inverse colors.
|
|
display::rect_fill(area, background_color);
|
|
display::rect_fill_corners(area, theme::BG);
|
|
}
|
|
} else {
|
|
display::rect_fill(area, background_color);
|
|
}
|
|
|
|
match &self.content {
|
|
ButtonContent::Text(text) => {
|
|
display::text_left(
|
|
self.get_text_baseline(&style),
|
|
text.as_ref(),
|
|
style.font,
|
|
text_color,
|
|
background_color,
|
|
);
|
|
}
|
|
ButtonContent::Icon(icon) => {
|
|
if style.with_outline {
|
|
// Accounting for the 8*8 icon with empty left column and bottom row
|
|
// (which fits the outline nicely and symmetrically)
|
|
let center = area.center() + Offset::uniform(1);
|
|
icon.draw_center(center, text_color, background_color);
|
|
} else {
|
|
// Positioning the icon in the corresponding corner/center
|
|
match self.pos {
|
|
ButtonPos::Left => {
|
|
icon.draw_bottom_left(area.bottom_left(), text_color, background_color)
|
|
}
|
|
ButtonPos::Right => icon.draw_bottom_right(
|
|
area.bottom_right(),
|
|
text_color,
|
|
background_color,
|
|
),
|
|
ButtonPos::Middle => {
|
|
icon.draw_center(area.center(), text_color, background_color)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(PartialEq, Eq)]
|
|
enum State {
|
|
Released,
|
|
Pressed,
|
|
}
|
|
|
|
pub enum ButtonContent {
|
|
Text(StrBuffer),
|
|
Icon(Icon),
|
|
}
|
|
|
|
pub struct ButtonStyleSheet {
|
|
pub normal: ButtonStyle,
|
|
pub active: ButtonStyle,
|
|
}
|
|
|
|
#[derive(Clone, Copy)]
|
|
pub struct ButtonStyle {
|
|
pub font: Font,
|
|
pub text_color: Color,
|
|
pub with_outline: bool,
|
|
pub with_arms: bool,
|
|
pub force_width: Option<i16>,
|
|
pub offset: Option<Offset>,
|
|
}
|
|
|
|
impl ButtonStyleSheet {
|
|
pub fn new(
|
|
normal_color: Color,
|
|
active_color: Color,
|
|
with_outline: bool,
|
|
with_arms: bool,
|
|
force_width: Option<i16>,
|
|
offset: Option<Offset>,
|
|
) -> Self {
|
|
Self {
|
|
normal: ButtonStyle {
|
|
font: theme::FONT_BUTTON,
|
|
text_color: normal_color,
|
|
with_outline,
|
|
with_arms,
|
|
force_width,
|
|
offset,
|
|
},
|
|
active: ButtonStyle {
|
|
font: theme::FONT_BUTTON,
|
|
text_color: active_color,
|
|
with_outline,
|
|
with_arms,
|
|
force_width,
|
|
offset,
|
|
},
|
|
}
|
|
}
|
|
|
|
// White text in normal mode.
|
|
pub fn default(
|
|
with_outline: bool,
|
|
with_arms: bool,
|
|
force_width: Option<i16>,
|
|
offset: Option<Offset>,
|
|
) -> Self {
|
|
Self::new(
|
|
theme::FG,
|
|
theme::BG,
|
|
with_outline,
|
|
with_arms,
|
|
force_width,
|
|
offset,
|
|
)
|
|
}
|
|
}
|
|
|
|
/// Describing the button on the screen - only visuals.
|
|
#[derive(Clone)]
|
|
pub struct ButtonDetails {
|
|
pub text: Option<StrBuffer>,
|
|
pub icon: Option<Icon>,
|
|
pub duration: Option<Duration>,
|
|
pub with_outline: bool,
|
|
pub with_arms: bool,
|
|
pub force_width: Option<i16>,
|
|
pub offset: Option<Offset>,
|
|
}
|
|
|
|
impl ButtonDetails {
|
|
/// Text button.
|
|
pub fn text(text: StrBuffer) -> Self {
|
|
Self {
|
|
text: Some(text),
|
|
icon: None,
|
|
duration: None,
|
|
with_outline: true,
|
|
with_arms: false,
|
|
force_width: None,
|
|
offset: None,
|
|
}
|
|
}
|
|
|
|
/// Icon button.
|
|
pub fn icon(icon: Icon) -> Self {
|
|
Self {
|
|
text: None,
|
|
icon: Some(icon),
|
|
duration: None,
|
|
with_outline: true,
|
|
with_arms: false,
|
|
force_width: None,
|
|
offset: None,
|
|
}
|
|
}
|
|
|
|
/// Text with arms signalling double press.
|
|
pub fn armed_text(text: StrBuffer) -> Self {
|
|
Self::text(text).with_arms()
|
|
}
|
|
|
|
/// Cross-style-icon cancel button with no outline.
|
|
pub fn cancel_icon() -> Self {
|
|
Self::icon(Icon::new(theme::ICON_CANCEL))
|
|
.with_no_outline()
|
|
.with_offset(Offset::new(2, -2))
|
|
}
|
|
|
|
/// Left arrow to signal going back. No outline.
|
|
pub fn left_arrow_icon() -> Self {
|
|
Self::icon(Icon::new(theme::ICON_ARROW_LEFT)).with_no_outline()
|
|
}
|
|
|
|
/// Right arrow to signal going forward. No outline.
|
|
pub fn right_arrow_icon() -> Self {
|
|
Self::icon(Icon::new(theme::ICON_ARROW_RIGHT)).with_no_outline()
|
|
}
|
|
|
|
/// Up arrow to signal paginating back. No outline. Offsetted little right
|
|
/// to not be on the boundary.
|
|
pub fn up_arrow_icon() -> Self {
|
|
Self::icon(Icon::new(theme::ICON_ARROW_UP))
|
|
.with_no_outline()
|
|
.with_offset(Offset::new(2, -3))
|
|
}
|
|
|
|
/// Down arrow to signal paginating forward. Takes half the screen's width
|
|
pub fn down_arrow_icon_wide() -> Self {
|
|
Self::icon(Icon::new(theme::ICON_ARROW_DOWN)).force_width(HALF_SCREEN_BUTTON_WIDTH)
|
|
}
|
|
|
|
/// Up arrow to signal paginating back. Takes half the screen's width
|
|
pub fn up_arrow_icon_wide() -> Self {
|
|
Self::icon(Icon::new(theme::ICON_ARROW_UP)).force_width(HALF_SCREEN_BUTTON_WIDTH)
|
|
}
|
|
|
|
/// Icon of a bin to signal deleting.
|
|
pub fn bin_icon() -> Self {
|
|
Self::icon(Icon::new(theme::ICON_BIN)).with_no_outline()
|
|
}
|
|
|
|
/// No outline around the button.
|
|
pub fn with_no_outline(mut self) -> Self {
|
|
self.with_outline = false;
|
|
self
|
|
}
|
|
|
|
/// Positioning the icon precisely where we want.
|
|
/// Buttons are by default placed exactly in the corners (left/right)
|
|
/// or in the center in case of center button. The offset can change it.
|
|
pub fn with_offset(mut self, offset: Offset) -> Self {
|
|
self.offset = Some(offset);
|
|
self
|
|
}
|
|
|
|
/// Left and right "arms" around the button.
|
|
/// Automatically disabling the outline.
|
|
pub fn with_arms(mut self) -> Self {
|
|
self.with_arms = true;
|
|
self.with_outline = false;
|
|
self
|
|
}
|
|
|
|
/// Default duration of the hold-to-confirm - 1 second.
|
|
pub fn with_default_duration(mut self) -> Self {
|
|
self.duration = Some(Duration::from_millis(1000));
|
|
self
|
|
}
|
|
|
|
/// Specific duration of the hold-to-confirm.
|
|
pub fn with_duration(mut self, duration: Duration) -> Self {
|
|
self.duration = Some(duration);
|
|
self
|
|
}
|
|
|
|
/// Width of the button.
|
|
pub fn force_width(mut self, width: i16) -> Self {
|
|
self.force_width = Some(width);
|
|
self
|
|
}
|
|
|
|
/// Button style that should be applied.
|
|
pub fn style(&self) -> ButtonStyleSheet {
|
|
ButtonStyleSheet::default(
|
|
self.with_outline,
|
|
self.with_arms,
|
|
self.force_width,
|
|
self.offset,
|
|
)
|
|
}
|
|
}
|
|
|
|
/// Holding the button details for all three possible buttons.
|
|
#[derive(Clone)]
|
|
pub struct ButtonLayout {
|
|
pub btn_left: Option<ButtonDetails>,
|
|
pub btn_middle: Option<ButtonDetails>,
|
|
pub btn_right: Option<ButtonDetails>,
|
|
}
|
|
|
|
impl ButtonLayout {
|
|
pub fn new(
|
|
btn_left: Option<ButtonDetails>,
|
|
btn_middle: Option<ButtonDetails>,
|
|
btn_right: Option<ButtonDetails>,
|
|
) -> Self {
|
|
Self {
|
|
btn_left,
|
|
btn_middle,
|
|
btn_right,
|
|
}
|
|
}
|
|
|
|
/// Empty layout for when we cannot yet tell which buttons
|
|
/// should be on the screen.
|
|
pub fn empty() -> Self {
|
|
Self::new(None, None, None)
|
|
}
|
|
|
|
/// Default button layout for all three buttons - icons.
|
|
pub fn default_three_icons() -> Self {
|
|
Self::arrow_armed_arrow("SELECT".into())
|
|
}
|
|
|
|
/// Special middle text for default icon layout.
|
|
pub fn arrow_armed_arrow(text: StrBuffer) -> Self {
|
|
Self::new(
|
|
Some(ButtonDetails::left_arrow_icon()),
|
|
Some(ButtonDetails::armed_text(text)),
|
|
Some(ButtonDetails::right_arrow_icon()),
|
|
)
|
|
}
|
|
|
|
/// Left cancel, armed text and next right arrow.
|
|
pub fn cancel_armed_arrow(text: StrBuffer) -> Self {
|
|
Self::new(
|
|
Some(ButtonDetails::cancel_icon()),
|
|
Some(ButtonDetails::armed_text(text)),
|
|
Some(ButtonDetails::right_arrow_icon()),
|
|
)
|
|
}
|
|
|
|
/// Middle armed text and next right arrow.
|
|
pub fn none_armed_arrow(text: StrBuffer) -> Self {
|
|
Self::new(
|
|
None,
|
|
Some(ButtonDetails::armed_text(text)),
|
|
Some(ButtonDetails::right_arrow_icon()),
|
|
)
|
|
}
|
|
|
|
/// Left cancel, armed text and right text.
|
|
pub fn cancel_armed_text(middle: StrBuffer, right: StrBuffer) -> Self {
|
|
Self::new(
|
|
Some(ButtonDetails::cancel_icon()),
|
|
Some(ButtonDetails::armed_text(middle)),
|
|
Some(ButtonDetails::text(right)),
|
|
)
|
|
}
|
|
|
|
/// Left back arrow and middle armed text.
|
|
pub fn arrow_armed_none(text: StrBuffer) -> Self {
|
|
Self::new(
|
|
Some(ButtonDetails::left_arrow_icon()),
|
|
Some(ButtonDetails::armed_text(text)),
|
|
None,
|
|
)
|
|
}
|
|
|
|
/// Left and right texts.
|
|
pub fn text_none_text(left: StrBuffer, right: StrBuffer) -> Self {
|
|
Self::new(
|
|
Some(ButtonDetails::text(left)),
|
|
None,
|
|
Some(ButtonDetails::text(right)),
|
|
)
|
|
}
|
|
|
|
/// Left text and right arrow.
|
|
pub fn text_none_arrow(text: StrBuffer) -> Self {
|
|
Self::new(
|
|
Some(ButtonDetails::text(text)),
|
|
None,
|
|
Some(ButtonDetails::right_arrow_icon()),
|
|
)
|
|
}
|
|
|
|
/// Only right text.
|
|
pub fn none_none_text(text: StrBuffer) -> Self {
|
|
Self::new(None, None, Some(ButtonDetails::text(text)))
|
|
}
|
|
|
|
/// Left and right arrow icons for navigation.
|
|
pub fn arrow_none_arrow() -> Self {
|
|
Self::new(
|
|
Some(ButtonDetails::left_arrow_icon()),
|
|
None,
|
|
Some(ButtonDetails::right_arrow_icon()),
|
|
)
|
|
}
|
|
|
|
/// Left arrow and right text.
|
|
pub fn arrow_none_text(text: StrBuffer) -> Self {
|
|
Self::new(
|
|
Some(ButtonDetails::left_arrow_icon()),
|
|
None,
|
|
Some(ButtonDetails::text(text)),
|
|
)
|
|
}
|
|
|
|
/// Up arrow left and right text.
|
|
pub fn up_arrow_none_text(text: StrBuffer) -> Self {
|
|
Self::new(
|
|
Some(ButtonDetails::up_arrow_icon()),
|
|
None,
|
|
Some(ButtonDetails::text(text)),
|
|
)
|
|
}
|
|
|
|
/// Cancel cross on left and right arrow.
|
|
pub fn cancel_none_arrow() -> Self {
|
|
Self::new(
|
|
Some(ButtonDetails::cancel_icon()),
|
|
None,
|
|
Some(ButtonDetails::right_arrow_icon()),
|
|
)
|
|
}
|
|
|
|
/// Cancel cross on left and right arrow facing down.
|
|
pub fn cancel_none_arrow_wide() -> Self {
|
|
Self::new(
|
|
Some(ButtonDetails::cancel_icon()),
|
|
None,
|
|
Some(ButtonDetails::down_arrow_icon_wide()),
|
|
)
|
|
}
|
|
|
|
/// Cancel cross on left and text on the right.
|
|
pub fn cancel_none_text(text: StrBuffer) -> Self {
|
|
Self::new(
|
|
Some(ButtonDetails::cancel_icon()),
|
|
None,
|
|
Some(ButtonDetails::text(text)),
|
|
)
|
|
}
|
|
|
|
/// Cancel cross on left and hold-to-confirm text on the right.
|
|
pub fn cancel_none_htc(text: StrBuffer) -> Self {
|
|
Self::new(
|
|
Some(ButtonDetails::cancel_icon()),
|
|
None,
|
|
Some(ButtonDetails::text(text).with_default_duration()),
|
|
)
|
|
}
|
|
|
|
/// Arrow back on left and hold-to-confirm text on the right.
|
|
pub fn arrow_none_htc(text: StrBuffer) -> Self {
|
|
Self::new(
|
|
Some(ButtonDetails::left_arrow_icon()),
|
|
None,
|
|
Some(ButtonDetails::text(text).with_default_duration()),
|
|
)
|
|
}
|
|
|
|
/// Arrow up on left and text on the right.
|
|
pub fn up_and_text(text: StrBuffer) -> Self {
|
|
Self::new(
|
|
Some(ButtonDetails::up_arrow_icon()),
|
|
None,
|
|
Some(ButtonDetails::text(text)),
|
|
)
|
|
}
|
|
|
|
/// Only armed text in the middle.
|
|
pub fn none_armed_none(text: StrBuffer) -> Self {
|
|
Self::new(None, Some(ButtonDetails::armed_text(text)), None)
|
|
}
|
|
|
|
/// Only hold-to-confirm with text on the right.
|
|
pub fn none_none_htc(text: StrBuffer, duration: Duration) -> Self {
|
|
Self::new(
|
|
None,
|
|
None,
|
|
Some(ButtonDetails::text(text).with_duration(duration)),
|
|
)
|
|
}
|
|
|
|
/// Only left arrow.
|
|
pub fn arrow_none_none() -> Self {
|
|
Self::new(Some(ButtonDetails::left_arrow_icon()), None, None)
|
|
}
|
|
|
|
/// Only right arrow facing down.
|
|
pub fn none_none_arrow_wide() -> Self {
|
|
Self::new(None, None, Some(ButtonDetails::down_arrow_icon_wide()))
|
|
}
|
|
}
|
|
|
|
/// What happens when a button is triggered.
|
|
/// Theoretically any action can be connected
|
|
/// with any button.
|
|
#[derive(Clone, PartialEq, Eq, Copy)]
|
|
pub enum ButtonAction {
|
|
/// Go to the next page of this flow
|
|
NextPage,
|
|
/// Go to the previous page of this flow
|
|
PrevPage,
|
|
/// Go to a page of this flow specified by an index.
|
|
/// Negative numbers can be used to count from the end.
|
|
/// (0 ~ GoToFirstPage, -1 ~ GoToLastPage etc.)
|
|
GoToIndex(i16),
|
|
/// Go forwards/backwards a specified number of pages.
|
|
/// Negative numbers mean going back.
|
|
MovePageRelative(i16),
|
|
/// Cancel the whole layout - send Msg::Cancelled
|
|
Cancel,
|
|
/// Confirm the whole layout - send Msg::Confirmed
|
|
Confirm,
|
|
/// Select current choice value
|
|
Select,
|
|
/// Some custom specific action
|
|
Action(&'static str),
|
|
}
|
|
|
|
/// Storing actions for all three possible buttons.
|
|
#[derive(Clone, Copy)]
|
|
pub struct ButtonActions {
|
|
pub left: Option<ButtonAction>,
|
|
pub middle: Option<ButtonAction>,
|
|
pub right: Option<ButtonAction>,
|
|
}
|
|
|
|
impl ButtonActions {
|
|
pub fn new(
|
|
left: Option<ButtonAction>,
|
|
middle: Option<ButtonAction>,
|
|
right: Option<ButtonAction>,
|
|
) -> Self {
|
|
Self {
|
|
left,
|
|
middle,
|
|
right,
|
|
}
|
|
}
|
|
|
|
/// Going back with left, going further with right
|
|
pub fn prev_none_next() -> Self {
|
|
Self::new(
|
|
Some(ButtonAction::PrevPage),
|
|
None,
|
|
Some(ButtonAction::NextPage),
|
|
)
|
|
}
|
|
|
|
/// Going back with left, going further with middle
|
|
pub fn prev_next_none() -> Self {
|
|
Self::new(
|
|
Some(ButtonAction::PrevPage),
|
|
Some(ButtonAction::NextPage),
|
|
None,
|
|
)
|
|
}
|
|
|
|
/// Previous with left, confirming with right
|
|
pub fn prev_none_confirm() -> Self {
|
|
Self::new(
|
|
Some(ButtonAction::PrevPage),
|
|
None,
|
|
Some(ButtonAction::Confirm),
|
|
)
|
|
}
|
|
|
|
/// Previous with left, confirming with middle
|
|
pub fn prev_confirm_none() -> Self {
|
|
Self::new(
|
|
Some(ButtonAction::PrevPage),
|
|
Some(ButtonAction::Confirm),
|
|
None,
|
|
)
|
|
}
|
|
|
|
/// Going to last page with left, to the next page with right
|
|
pub fn last_none_next() -> Self {
|
|
Self::new(
|
|
Some(ButtonAction::GoToIndex(-1)),
|
|
None,
|
|
Some(ButtonAction::NextPage),
|
|
)
|
|
}
|
|
|
|
/// Going to last page with left, to the next page with right and confirm
|
|
/// with middle
|
|
pub fn last_confirm_next() -> Self {
|
|
Self::new(
|
|
Some(ButtonAction::GoToIndex(-1)),
|
|
Some(ButtonAction::Confirm),
|
|
Some(ButtonAction::NextPage),
|
|
)
|
|
}
|
|
|
|
/// Going to previous page with left, to the next page with right and
|
|
/// confirm with middle
|
|
pub fn prev_confirm_next() -> Self {
|
|
Self::new(
|
|
Some(ButtonAction::PrevPage),
|
|
Some(ButtonAction::Confirm),
|
|
Some(ButtonAction::NextPage),
|
|
)
|
|
}
|
|
|
|
/// Cancelling with left, going to the next page with right
|
|
pub fn cancel_none_next() -> Self {
|
|
Self::new(
|
|
Some(ButtonAction::Cancel),
|
|
None,
|
|
Some(ButtonAction::NextPage),
|
|
)
|
|
}
|
|
|
|
/// Only going to the next page with right
|
|
pub fn none_none_next() -> Self {
|
|
Self::new(None, None, Some(ButtonAction::NextPage))
|
|
}
|
|
|
|
/// Only going to the prev page with left
|
|
pub fn prev_none_none() -> Self {
|
|
Self::new(Some(ButtonAction::PrevPage), None, None)
|
|
}
|
|
|
|
/// Cancelling with left, confirming with right
|
|
pub fn cancel_none_confirm() -> Self {
|
|
Self::new(
|
|
Some(ButtonAction::Cancel),
|
|
None,
|
|
Some(ButtonAction::Confirm),
|
|
)
|
|
}
|
|
|
|
/// Cancelling with left, confirming with middle and next with right
|
|
pub fn cancel_confirm_next() -> Self {
|
|
Self::new(
|
|
Some(ButtonAction::Cancel),
|
|
Some(ButtonAction::Confirm),
|
|
Some(ButtonAction::NextPage),
|
|
)
|
|
}
|
|
|
|
/// Going to the beginning with left, confirming with right
|
|
pub fn beginning_none_confirm() -> Self {
|
|
Self::new(
|
|
Some(ButtonAction::GoToIndex(0)),
|
|
None,
|
|
Some(ButtonAction::Confirm),
|
|
)
|
|
}
|
|
|
|
/// Going to the beginning with left, cancelling with right
|
|
pub fn beginning_none_cancel() -> Self {
|
|
Self::new(
|
|
Some(ButtonAction::GoToIndex(0)),
|
|
None,
|
|
Some(ButtonAction::Cancel),
|
|
)
|
|
}
|
|
|
|
/// Having access to appropriate action based on the `ButtonPos`
|
|
pub fn get_action(&self, pos: ButtonPos) -> Option<ButtonAction> {
|
|
match pos {
|
|
ButtonPos::Left => self.left,
|
|
ButtonPos::Middle => self.middle,
|
|
ButtonPos::Right => self.right,
|
|
}
|
|
}
|
|
}
|
|
|
|
// DEBUG-ONLY SECTION BELOW
|
|
|
|
#[cfg(feature = "ui_debug")]
|
|
impl crate::trace::Trace for Button {
|
|
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
|
|
t.open("Button");
|
|
match &self.content {
|
|
ButtonContent::Text(text) => t.field("text", text),
|
|
ButtonContent::Icon(icon) => t.field("icon", icon),
|
|
}
|
|
t.close();
|
|
}
|
|
}
|
|
|
|
#[cfg(feature = "ui_debug")]
|
|
use heapless::String;
|
|
|
|
#[cfg(feature = "ui_debug")]
|
|
impl crate::trace::Trace for ButtonDetails {
|
|
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
|
|
t.open("ButtonDetails");
|
|
let mut btn_text: String<30> = String::new();
|
|
if let Some(text) = &self.text {
|
|
btn_text.push_str(text.as_ref()).unwrap();
|
|
} else if let Some(icon) = &self.icon {
|
|
btn_text.push_str("Icon:").unwrap();
|
|
btn_text.push_str(icon.text.as_ref()).unwrap();
|
|
}
|
|
if let Some(duration) = &self.duration {
|
|
btn_text.push_str(" (HTC:").unwrap();
|
|
btn_text.push_str(inttostr!(duration.to_millis())).unwrap();
|
|
btn_text.push_str(")").unwrap();
|
|
}
|
|
t.button(btn_text.as_ref());
|
|
t.close();
|
|
}
|
|
}
|
|
|
|
#[cfg(feature = "ui_debug")]
|
|
impl ButtonAction {
|
|
/// Describing the action as a string. Debug-only.
|
|
pub fn string(&self) -> String<25> {
|
|
match self {
|
|
ButtonAction::NextPage => "Next".into(),
|
|
ButtonAction::PrevPage => "Prev".into(),
|
|
ButtonAction::GoToIndex(index) => {
|
|
build_string!(25, "Index(", inttostr!(*index), ")")
|
|
}
|
|
ButtonAction::MovePageRelative(index) => {
|
|
build_string!(25, "Relative(", inttostr!(*index), ")")
|
|
}
|
|
ButtonAction::Cancel => "Cancel".into(),
|
|
ButtonAction::Confirm => "Confirm".into(),
|
|
ButtonAction::Select => "Select".into(),
|
|
ButtonAction::Action(action) => (*action).into(),
|
|
}
|
|
}
|
|
|
|
/// Adding a description to the Select action.
|
|
pub fn select_item<T: AsRef<str>>(item: T) -> String<25> {
|
|
build_string!(25, &Self::Select.string(), "(", item.as_ref(), ")")
|
|
}
|
|
|
|
/// When there is no action.
|
|
pub fn empty() -> String<25> {
|
|
"None".into()
|
|
}
|
|
}
|