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.
trezor-firmware/core/embed/rust/src/ui/model_tr/component/button_controller.rs

827 lines
31 KiB

use super::{
theme, Button, ButtonDetails, ButtonLayout, ButtonPos, HoldToConfirm, HoldToConfirmMsg,
};
use crate::{
time::{Duration, Instant},
ui::{
component::{base::Event, Component, EventCtx, Pad, TimerToken},
event::{ButtonEvent, PhysicalButton},
geometry::Rect,
shape::Renderer,
},
};
/// All possible states buttons (left and right) can be at.
#[derive(Copy, Clone, PartialEq, Eq)]
enum ButtonState {
/// Both buttons are in untouched state.
/// _ _
/// NEXT: OneDown
Nothing,
/// One Button is down when previously nothing was.
/// _ _ ... ↓ _ | _ ↓
/// NEXT: Nothing, BothDown, HTCNeedsRelease
OneDown(PhysicalButton),
/// Both buttons are down ("middle-click").
/// ↓ _ | _ ↓ ... ↓ ↓
/// NEXT: OneReleased
BothDown,
/// One button is down when previously both were.
/// Happens when "middle-click" is performed.
/// ↓ ↓ ... ↓ _ | _ ↓
/// NEXT: Nothing, BothDown
OneReleased(PhysicalButton),
/// One button is down after it triggered a HoldToConfirm event.
/// Needed so that we can cleanly reset the state.
/// ↓ _ | _ ↓ ... ↓ _ | _ ↓
/// NEXT: Nothing
HTCNeedsRelease(PhysicalButton),
}
pub enum ButtonControllerMsg {
/// Button was pressed down.
Pressed(ButtonPos),
/// Which button was triggered, and whether it was pressed for a longer
/// time before releasing.
Triggered(ButtonPos, bool),
/// Button was pressed and held for longer time (not released yet).
LongPressed(ButtonPos),
/// Hold-to-confirm button was released prematurely - without triggering
/// LongPressed.
ReleasedWithoutLongPress(ButtonPos),
}
/// Defines what kind of button should be currently used.
pub enum ButtonType {
Button(Button),
HoldToConfirm(HoldToConfirm),
Nothing,
}
impl ButtonType {
pub fn from_button_details(pos: ButtonPos, btn_details: Option<ButtonDetails>) -> Self {
if let Some(btn_details) = btn_details {
if btn_details.duration.is_some() {
Self::HoldToConfirm(HoldToConfirm::from_button_details(pos, btn_details))
} else {
Self::Button(Button::from_button_details(pos, btn_details))
}
} else {
Self::Nothing
}
}
pub fn place(&mut self, button_area: Rect) {
match self {
Self::Button(button) => {
button.place(button_area);
}
Self::HoldToConfirm(htc) => {
htc.place(button_area);
}
Self::Nothing => {}
}
}
pub fn paint(&mut self) {
match self {
Self::Button(button) => {
button.paint();
}
Self::HoldToConfirm(htc) => {
htc.paint();
}
Self::Nothing => {}
}
}
pub fn render<'s>(&'s self, target: &mut impl Renderer<'s>) {
match self {
Self::Button(button) => {
button.render(target);
}
Self::HoldToConfirm(htc) => {
htc.render(target);
}
Self::Nothing => {}
}
}
}
/// Wrapping a button and its state, so that it can be easily
/// controlled from outside.
///
/// Users have a choice of a normal button or Hold-to-confirm button.
/// `button_type` specified what from those two is used, if anything.
pub struct ButtonContainer {
pos: ButtonPos,
button_type: ButtonType,
/// Holds the timestamp of when the button was pressed.
pressed_since: Option<Instant>,
/// How long the button should be pressed to send `long_press=true` in
/// `ButtonControllerMsg::Triggered`
long_press_ms: u32,
/// Timer for sending `ButtonControllerMsg::LongPressed`
long_pressed_timer: Option<TimerToken>,
/// Whether it should even send `ButtonControllerMsg::LongPressed` events
/// (optional)
send_long_press: bool,
}
impl ButtonContainer {
/// Supplying `None` as `btn_details` marks the button inactive
/// (it can be later activated in `set()`).
pub fn new(pos: ButtonPos, btn_details: Option<ButtonDetails>) -> Self {
const DEFAULT_LONG_PRESS_MS: u32 = 1000;
let send_long_press = btn_details
.as_ref()
.map_or(false, |btn| btn.send_long_press);
Self {
pos,
button_type: ButtonType::from_button_details(pos, btn_details),
pressed_since: None,
long_press_ms: DEFAULT_LONG_PRESS_MS,
long_pressed_timer: None,
send_long_press,
}
}
/// Changing the state of the button.
///
/// Passing `None` as `btn_details` will mark the button as inactive.
pub fn set(&mut self, btn_details: Option<ButtonDetails>, button_area: Rect) {
self.send_long_press = btn_details
.as_ref()
.map_or(false, |btn| btn.send_long_press);
self.button_type = ButtonType::from_button_details(self.pos, btn_details);
self.button_type.place(button_area);
}
/// Placing the possible component.
pub fn place(&mut self, bounds: Rect) {
self.button_type.place(bounds);
}
/// Painting the component that should be currently visible, if any.
pub fn paint(&mut self) {
self.button_type.paint();
}
pub fn render<'s>(&'s self, target: &mut impl Renderer<'s>) {
self.button_type.render(target);
}
/// Setting the visual state of the button - released/pressed.
pub fn set_pressed(&mut self, ctx: &mut EventCtx, is_pressed: bool) {
if let ButtonType::Button(btn) = &mut self.button_type {
btn.set_pressed(ctx, is_pressed);
}
}
/// Trigger an action or end hold.
/// Called when the button is released. If it is a simple button, it returns
/// a Triggered message. If it is a hold-to-confirm button, it ends the
/// hold.
pub fn maybe_trigger(&mut self, ctx: &mut EventCtx) -> Option<ButtonControllerMsg> {
match self.button_type {
ButtonType::Button(_) => {
// Finding out whether the button was long-pressed
let long_press = self.pressed_since.map_or(false, |since| {
Instant::now().saturating_duration_since(since).to_millis() > self.long_press_ms
});
self.pressed_since = None;
self.long_pressed_timer = None;
Some(ButtonControllerMsg::Triggered(self.pos, long_press))
}
ButtonType::HoldToConfirm(_) => {
self.hold_ended(ctx);
Some(ButtonControllerMsg::ReleasedWithoutLongPress(self.pos))
}
_ => None,
}
}
/// Find out whether hold-to-confirm was triggered.
pub fn htc_got_triggered(&mut self, ctx: &mut EventCtx, event: Event) -> bool {
if let ButtonType::HoldToConfirm(htc) = &mut self.button_type {
if matches!(htc.event(ctx, event), Some(HoldToConfirmMsg::Confirmed)) {
return true;
}
}
false
}
/// Saving the timestamp of when the button was pressed.
/// Also requesting a timer for long-press if wanted.
pub fn got_pressed(&mut self, ctx: &mut EventCtx) {
self.pressed_since = Some(Instant::now());
if self.send_long_press {
self.long_pressed_timer =
Some(ctx.request_timer(Duration::from_millis(self.long_press_ms)));
}
}
/// Reset the pressed information.
pub fn reset(&mut self) {
self.pressed_since = None;
self.long_pressed_timer = None;
}
/// Whether token matches what we have
pub fn is_timer_token(&self, token: TimerToken) -> bool {
self.long_pressed_timer == Some(token)
}
/// Registering hold event.
pub fn hold_started(&mut self, ctx: &mut EventCtx) {
if let ButtonType::HoldToConfirm(htc) = &mut self.button_type {
htc.event(ctx, Event::Button(ButtonEvent::HoldStarted));
}
}
/// Cancelling hold event.
pub fn hold_ended(&mut self, ctx: &mut EventCtx) {
if let ButtonType::HoldToConfirm(htc) = &mut self.button_type {
htc.event(ctx, Event::Button(ButtonEvent::HoldEnded));
}
}
}
/// Component responsible for handling buttons.
///
/// Acts as a state-machine of `ButtonState`.
///
/// Storing all three possible buttons - left, middle and right -
/// and handling their placement, painting and returning
/// appropriate events when they are triggered.
///
/// Buttons can be interactively changed by clients by `set()`.
///
/// Only "final" button events are returned in `ButtonControllerMsg::Triggered`,
/// based upon the buttons being long-press or not.
///
/// There is optional complexity with IgnoreButtonDelay, which gets executed
/// only in cases where clients request it.
pub struct ButtonController {
pad: Pad,
left_btn: ButtonContainer,
middle_btn: ButtonContainer,
right_btn: ButtonContainer,
state: ButtonState,
/// Button area is needed so the buttons
/// can be "re-placed" after their text is changed
/// Will be set in `place`
button_area: Rect,
/// Handling optional ignoring of buttons after pressing the other button.
ignore_btn_delay: Option<IgnoreButtonDelay>,
/// Whether to count with middle button
handle_middle_button: bool,
}
impl ButtonController {
pub fn new(btn_layout: ButtonLayout) -> Self {
let handle_middle_button = btn_layout.btn_middle.is_some();
Self {
pad: Pad::with_background(theme::BG).with_clear(),
left_btn: ButtonContainer::new(ButtonPos::Left, btn_layout.btn_left),
middle_btn: ButtonContainer::new(ButtonPos::Middle, btn_layout.btn_middle),
right_btn: ButtonContainer::new(ButtonPos::Right, btn_layout.btn_right),
state: ButtonState::Nothing,
button_area: Rect::zero(),
ignore_btn_delay: None,
handle_middle_button,
}
}
/// Set up the logic to ignore a button after some time from pressing
/// the other button.
pub fn with_ignore_btn_delay(mut self, delay_ms: u32) -> Self {
self.ignore_btn_delay = Some(IgnoreButtonDelay::new(delay_ms));
self
}
/// Updating all the three buttons to the wanted states.
pub fn set(&mut self, btn_layout: ButtonLayout) {
self.handle_middle_button = btn_layout.btn_middle.is_some();
self.pad.clear();
self.left_btn.set(btn_layout.btn_left, self.button_area);
self.middle_btn.set(btn_layout.btn_middle, self.button_area);
self.right_btn.set(btn_layout.btn_right, self.button_area);
}
/// Setting the pressed state for all three buttons by boolean flags.
fn set_pressed(&mut self, ctx: &mut EventCtx, left: bool, mid: bool, right: bool) {
self.left_btn.set_pressed(ctx, left);
self.middle_btn.set_pressed(ctx, mid);
self.right_btn.set_pressed(ctx, right);
}
pub fn highlight_button(&mut self, ctx: &mut EventCtx, pos: ButtonPos) {
match pos {
ButtonPos::Left => self.left_btn.set_pressed(ctx, true),
ButtonPos::Middle => self.middle_btn.set_pressed(ctx, true),
ButtonPos::Right => self.right_btn.set_pressed(ctx, true),
}
}
/// Handle middle button hold-to-confirm start.
/// We need to cancel possible holds in both other buttons.
fn middle_hold_started(&mut self, ctx: &mut EventCtx) {
self.left_btn.hold_ended(ctx);
self.middle_btn.hold_started(ctx);
self.right_btn.hold_ended(ctx);
}
/// Handling the expiration of HTC elements.
/// Finding out if they have been triggered and sending event
/// for the appropriate button.
/// Setting the state to wait for the appropriate release event
/// from the pressed button. Also resetting visible state.
fn handle_htc_expiration(
&mut self,
ctx: &mut EventCtx,
event: Event,
) -> Option<ButtonControllerMsg> {
if self.left_btn.htc_got_triggered(ctx, event) {
self.state = ButtonState::HTCNeedsRelease(PhysicalButton::Left);
self.set_pressed(ctx, false, false, false);
return Some(ButtonControllerMsg::Triggered(ButtonPos::Left, true));
} else if self.middle_btn.htc_got_triggered(ctx, event) {
self.state = ButtonState::Nothing;
self.set_pressed(ctx, false, false, false);
return Some(ButtonControllerMsg::Triggered(ButtonPos::Middle, true));
} else if self.right_btn.htc_got_triggered(ctx, event) {
self.state = ButtonState::HTCNeedsRelease(PhysicalButton::Right);
self.set_pressed(ctx, false, false, false);
return Some(ButtonControllerMsg::Triggered(ButtonPos::Right, true));
}
None
}
fn reset_button_presses(&mut self) {
self.left_btn.reset();
self.middle_btn.reset();
self.right_btn.reset();
}
fn got_pressed(&mut self, ctx: &mut EventCtx, pos: ButtonPos) {
// Only one (virtual) button can be pressed at the same time
self.reset_button_presses();
match pos {
ButtonPos::Left => {
self.left_btn.got_pressed(ctx);
}
ButtonPos::Middle => {
self.middle_btn.got_pressed(ctx);
}
ButtonPos::Right => {
self.right_btn.got_pressed(ctx);
}
}
}
fn handle_long_press_timer_token(&mut self, token: TimerToken) -> Option<ButtonPos> {
if self.left_btn.is_timer_token(token) {
return Some(ButtonPos::Left);
}
if self.middle_btn.is_timer_token(token) {
return Some(ButtonPos::Middle);
}
if self.right_btn.is_timer_token(token) {
return Some(ButtonPos::Right);
}
None
}
/// Resetting the state of the controller.
pub fn reset_state(&mut self, ctx: &mut EventCtx) {
self.state = ButtonState::Nothing;
self.reset_button_presses();
self.set_pressed(ctx, false, false, false);
if let Some(ignore_btn_delay) = &mut self.ignore_btn_delay {
ignore_btn_delay.reset();
}
}
}
impl Component for ButtonController {
type Msg = ButtonControllerMsg;
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
// State machine for the ButtonController
// We are matching event with `Event::Button` for a button action
// and `Event::Timer` for getting the expiration of HTC.
match event {
Event::Button(button_event) => {
let (new_state, event) = match self.state {
// _ _
ButtonState::Nothing => match button_event {
// ▼ * | * ▼
ButtonEvent::ButtonPressed(which) => {
// ↓ _ | _ ↓
// Initial button press will set the timer for second button,
// and after some delay, it will become un-clickable
if let Some(ignore_btn_delay) = &mut self.ignore_btn_delay {
ignore_btn_delay.handle_button_press(ctx, which);
}
(
ButtonState::OneDown(which),
match which {
// ▼ *
PhysicalButton::Left => {
self.got_pressed(ctx, ButtonPos::Left);
self.left_btn.hold_started(ctx);
Some(ButtonControllerMsg::Pressed(ButtonPos::Left))
}
// * ▼
PhysicalButton::Right => {
self.got_pressed(ctx, ButtonPos::Right);
self.right_btn.hold_started(ctx);
Some(ButtonControllerMsg::Pressed(ButtonPos::Right))
}
},
)
}
_ => (self.state, None),
},
// ↓ _ | _ ↓
ButtonState::OneDown(which_down) => match button_event {
// ▲ * | * ▲
ButtonEvent::ButtonReleased(b) if b == which_down => match which_down {
// ▲ *
// Trigger the button and make the second one clickable in all cases
PhysicalButton::Left => {
if let Some(ignore_btn_delay) = &mut self.ignore_btn_delay {
ignore_btn_delay.make_button_clickable(ButtonPos::Right);
}
// _ _
(ButtonState::Nothing, self.left_btn.maybe_trigger(ctx))
}
// * ▲
PhysicalButton::Right => {
if let Some(ignore_btn_delay) = &mut self.ignore_btn_delay {
ignore_btn_delay.make_button_clickable(ButtonPos::Left);
}
// _ _
(ButtonState::Nothing, self.right_btn.maybe_trigger(ctx))
}
},
// * ▼ | ▼ *
ButtonEvent::ButtonPressed(b) if b != which_down => {
// Buttons may be non-clickable (caused by long-holding the other
// button)
if let Some(ignore_btn_delay) = &self.ignore_btn_delay {
if ignore_btn_delay.ignore_button(b) {
return None;
}
}
// ↓ ↓
if self.handle_middle_button {
self.got_pressed(ctx, ButtonPos::Middle);
self.middle_hold_started(ctx);
(
ButtonState::BothDown,
Some(ButtonControllerMsg::Pressed(ButtonPos::Middle)),
)
} else {
self.got_pressed(ctx, b.into());
(
ButtonState::BothDown,
Some(ButtonControllerMsg::Pressed(b.into())),
)
}
}
_ => (self.state, None),
},
// ↓ ↓
ButtonState::BothDown => match button_event {
// ▲ * | * ▲
ButtonEvent::ButtonReleased(b) => {
self.middle_btn.hold_ended(ctx);
// _ ↓ | ↓ _
if self.handle_middle_button {
(ButtonState::OneReleased(b), None)
} else {
(
ButtonState::OneReleased(b),
Some(ButtonControllerMsg::Triggered(b.into(), false)),
)
}
}
_ => (self.state, None),
},
// ↓ _ | _ ↓
ButtonState::OneReleased(which_up) => match button_event {
// * ▼ | ▼ *
ButtonEvent::ButtonPressed(b) if b == which_up => {
self.middle_hold_started(ctx);
// ↓ ↓
(ButtonState::BothDown, None)
}
// ▲ * | * ▲
ButtonEvent::ButtonReleased(b) if b != which_up => {
// _ _
// Both buttons need to be clickable now
if let Some(ignore_btn_delay) = &mut self.ignore_btn_delay {
ignore_btn_delay.make_button_clickable(ButtonPos::Left);
ignore_btn_delay.make_button_clickable(ButtonPos::Right);
}
if self.handle_middle_button {
(ButtonState::Nothing, self.middle_btn.maybe_trigger(ctx))
} else {
(
ButtonState::Nothing,
Some(ButtonControllerMsg::Triggered(b.into(), false)),
)
}
}
_ => (self.state, None),
},
// ↓ _ | _ ↓
ButtonState::HTCNeedsRelease(needs_release) => match button_event {
// Only going out of this state if correct button was released
// ▲ * | * ▲
ButtonEvent::ButtonReleased(released) if needs_release == released => {
// _ _
(ButtonState::Nothing, None)
}
_ => (self.state, None),
},
};
// Updating the visual feedback for the buttons
match new_state {
// Not showing anything also when we wait for a release
ButtonState::Nothing | ButtonState::HTCNeedsRelease(_) => {
self.set_pressed(ctx, false, false, false);
}
ButtonState::OneDown(down_button) => match down_button {
PhysicalButton::Left => {
self.set_pressed(ctx, true, false, false);
}
PhysicalButton::Right => {
self.set_pressed(ctx, false, false, true);
}
},
ButtonState::BothDown | ButtonState::OneReleased(_) => {
self.set_pressed(ctx, false, true, false);
}
};
self.state = new_state;
event
}
// Timer - handle clickable properties and HoldToConfirm expiration
Event::Timer(token) => {
if let Some(ignore_btn_delay) = &mut self.ignore_btn_delay {
ignore_btn_delay.handle_timer_token(token);
}
if let Some(pos) = self.handle_long_press_timer_token(token) {
return Some(ButtonControllerMsg::LongPressed(pos));
}
self.handle_htc_expiration(ctx, event)
}
_ => None,
}
}
fn paint(&mut self) {
self.pad.paint();
self.left_btn.paint();
self.middle_btn.paint();
self.right_btn.paint();
}
fn render<'s>(&'s self, target: &mut impl Renderer<'s>) {
self.pad.render(target);
self.left_btn.render(target);
self.middle_btn.render(target);
self.right_btn.render(target);
}
fn place(&mut self, bounds: Rect) -> Rect {
// Saving button area so that we can re-place the buttons
// when they get updated
self.button_area = bounds;
self.pad.place(bounds);
self.left_btn.place(bounds);
self.middle_btn.place(bounds);
self.right_btn.place(bounds);
bounds
}
}
/// When one button is pressed, the other one becomes un-clickable after some
/// small time period. This is to prevent accidental clicks when user is holding
/// the button to automatically move through items.
struct IgnoreButtonDelay {
/// How big is the delay after the button is inactive
delay: Duration,
/// Whether left button is currently clickable
left_clickable: bool,
/// Whether right button is currently clickable
right_clickable: bool,
/// Timer for setting the left_clickable
left_clickable_timer: Option<TimerToken>,
/// Timer for setting the right_clickable
right_clickable_timer: Option<TimerToken>,
}
impl IgnoreButtonDelay {
pub fn new(delay_ms: u32) -> Self {
Self {
delay: Duration::from_millis(delay_ms),
left_clickable: true,
right_clickable: true,
left_clickable_timer: None,
right_clickable_timer: None,
}
}
pub fn make_button_clickable(&mut self, pos: ButtonPos) {
match pos {
ButtonPos::Left => {
self.left_clickable = true;
self.left_clickable_timer = None;
}
ButtonPos::Right => {
self.right_clickable = true;
self.right_clickable_timer = None;
}
ButtonPos::Middle => {}
}
}
pub fn handle_button_press(&mut self, ctx: &mut EventCtx, button: PhysicalButton) {
if matches!(button, PhysicalButton::Left) {
self.right_clickable_timer = Some(ctx.request_timer(self.delay));
}
if matches!(button, PhysicalButton::Right) {
self.left_clickable_timer = Some(ctx.request_timer(self.delay));
}
}
pub fn ignore_button(&self, button: PhysicalButton) -> bool {
if matches!(button, PhysicalButton::Left) && !self.left_clickable {
return true;
}
if matches!(button, PhysicalButton::Right) && !self.right_clickable {
return true;
}
false
}
pub fn handle_timer_token(&mut self, token: TimerToken) {
if self.left_clickable_timer == Some(token) {
self.left_clickable = false;
self.left_clickable_timer = None;
}
if self.right_clickable_timer == Some(token) {
self.right_clickable = false;
self.right_clickable_timer = None;
}
}
pub fn reset(&mut self) {
self.left_clickable = true;
self.right_clickable = true;
self.left_clickable_timer = None;
self.right_clickable_timer = None;
}
}
/// Component allowing for automatically moving through items (e.g. Choice
/// items).
///
/// Users are in full control of starting/stopping the movement.
///
/// Can be started e.g. by holding left/right button.
pub struct AutomaticMover {
/// For requesting timer events repeatedly
timer_token: Option<TimerToken>,
/// Which direction should we go (which button is down)
moving_direction: Option<ButtonPos>,
/// How many screens were moved automatically
auto_moved_screens: usize,
/// Function to get duration of each movement according to the already moved
/// steps
duration_func: fn(usize) -> u32,
}
impl AutomaticMover {
pub fn new() -> Self {
fn default_duration_func(steps: usize) -> u32 {
match steps {
x if x < 3 => 200,
x if x < 10 => 150,
_ => 100,
}
}
Self {
timer_token: None,
moving_direction: None,
auto_moved_screens: 0,
duration_func: default_duration_func,
}
}
pub fn with_duration_func(mut self, duration_func: fn(usize) -> u32) -> Self {
self.duration_func = duration_func;
self
}
/// Determines how long to wait between automatic movements.
/// Moves quicker with increasing number of screens moved.
/// Can be forced to be always the same (e.g. for animation purposes).
fn get_auto_move_duration(&self) -> Duration {
// Calculating duration from function
let ms_duration = (self.duration_func)(self.auto_moved_screens);
Duration::from_millis(ms_duration)
}
/// In which direction we are moving, if any
pub fn moving_direction(&self) -> Option<ButtonPos> {
self.moving_direction
}
// Whether we are currently moving.
pub fn is_moving(&self) -> bool {
self.moving_direction.is_some()
}
/// Whether we have done at least one automatic movement.
pub fn was_moving(&self) -> bool {
self.auto_moved_screens > 0
}
pub fn start_moving(&mut self, ctx: &mut EventCtx, button: ButtonPos) {
self.auto_moved_screens = 0;
self.moving_direction = Some(button);
self.timer_token = Some(ctx.request_timer(self.get_auto_move_duration()));
}
pub fn stop_moving(&mut self) {
self.moving_direction = None;
self.timer_token = None;
}
}
impl Component for AutomaticMover {
type Msg = ButtonPos;
fn place(&mut self, bounds: Rect) -> Rect {
bounds
}
fn paint(&mut self) {}
fn render<'s>(&'s self, _target: &mut impl Renderer<'s>) {}
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
// Moving automatically only when we receive a TimerToken that we have
// requested before
if let Event::Timer(token) = event {
if self.timer_token == Some(token) && self.moving_direction.is_some() {
// Request new token and send the appropriate button trigger event
self.timer_token = Some(ctx.request_timer(self.get_auto_move_duration()));
self.auto_moved_screens += 1;
return self.moving_direction;
}
}
None
}
}
// DEBUG-ONLY SECTION BELOW
#[cfg(feature = "ui_debug")]
impl crate::trace::Trace for ButtonContainer {
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
if let ButtonType::Button(btn) = &self.button_type {
btn.trace(t);
} else if let ButtonType::HoldToConfirm(htc) = &self.button_type {
htc.trace(t);
}
}
}
#[cfg(feature = "ui_debug")]
impl crate::trace::Trace for ButtonController {
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
t.component("ButtonController");
t.child("left_btn", &self.left_btn);
t.child("middle_btn", &self.middle_btn);
t.child("right_btn", &self.right_btn);
}
}
#[cfg(feature = "ui_debug")]
impl crate::trace::Trace for AutomaticMover {
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
t.component("AutomaticMover");
}
}