1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2025-04-21 09:39:02 +00:00

feat(eckhart): set brightness screen

This commit is contained in:
Lukas Bielesch 2025-03-30 13:29:35 +02:00 committed by M1nd3r
parent 33bf5143a3
commit bf1da542e4
4 changed files with 237 additions and 4 deletions

View File

@ -17,7 +17,7 @@ use super::firmware::{
AllowedTextContent, ConfirmHomescreen, ConfirmHomescreenMsg, Homescreen, HomescreenMsg,
MnemonicInput, MnemonicKeyboard, MnemonicKeyboardMsg, NumberInputScreen, NumberInputScreenMsg,
PinKeyboard, PinKeyboardMsg, SelectWordCountMsg, SelectWordCountScreen, SelectWordMsg,
SelectWordScreen, TextScreen, TextScreenMsg,
SelectWordScreen, SetBrightnessMsg, SetBrightnessScreen, TextScreen, TextScreenMsg,
};
impl ComponentMsgObj for PinKeyboard<'_> {
@ -128,3 +128,12 @@ impl ComponentMsgObj for ConfirmHomescreen {
}
}
}
impl ComponentMsgObj for SetBrightnessScreen {
fn msg_try_into_obj(&self, msg: Self::Msg) -> Result<Obj, Error> {
match msg {
SetBrightnessMsg::Confirmed => Ok(CONFIRMED.as_obj()),
SetBrightnessMsg::Cancelled => Ok(CANCELLED.as_obj()),
}
}
}

View File

@ -0,0 +1,216 @@
use crate::{
storage,
translations::TR,
trezorhal::display,
ui::{
component::{Component, Event, EventCtx},
event::TouchEvent,
geometry::{Alignment2D, Insets, Offset, Point, Rect},
shape::{Bar, Renderer},
},
};
use super::super::{
constant::SCREEN,
firmware::{Header, HeaderMsg},
theme,
};
pub struct SetBrightnessScreen {
header: Header,
slider: VerticalSlider,
}
pub enum SetBrightnessMsg {
Confirmed,
Cancelled,
}
impl SetBrightnessScreen {
const SLIDER_HEIGHT: i16 = 392;
pub fn new(min: u16, max: u16, init_value: u16) -> Self {
Self {
header: Header::new(TR::brightness__title.into()).with_close_button(),
slider: VerticalSlider::new(min, max, init_value),
}
}
}
impl Component for SetBrightnessScreen {
type Msg = SetBrightnessMsg;
fn place(&mut self, bounds: Rect) -> Rect {
// assert full screen
debug_assert_eq!(bounds.height(), SCREEN.height());
debug_assert_eq!(bounds.width(), SCREEN.width());
let (header_area, rest) = bounds.split_top(Header::HEADER_HEIGHT);
let (slider_area, _) = rest.split_top(Self::SLIDER_HEIGHT);
self.header.place(header_area);
self.slider.place(slider_area);
bounds
}
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
if let Some(HeaderMsg::Cancelled) = self.header.event(ctx, event) {
return Some(SetBrightnessMsg::Cancelled);
}
if let Some(value) = self.slider.event(ctx, event) {
unwrap!(storage::set_brightness(value as _));
return Some(SetBrightnessMsg::Confirmed);
}
None
}
fn render<'s>(&'s self, target: &mut impl Renderer<'s>) {
self.header.render(target);
self.slider.render(target);
}
}
#[cfg(feature = "ui_debug")]
impl crate::trace::Trace for SetBrightnessScreen {
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
t.component("SetBrightnessScreen");
t.child("Header", &self.header);
t.child("Slider", &self.slider);
}
}
struct VerticalSlider {
area: Rect,
touch_area: Rect,
min: u16,
max: u16,
value: u16,
val_pct: u16,
touching: bool,
}
impl VerticalSlider {
const SLIDER_WIDTH: i16 = 120;
pub fn new(min: u16, max: u16, value: u16) -> Self {
debug_assert!(min < max);
let value = value.clamp(min, max);
Self {
area: Rect::zero(),
touch_area: Rect::zero(),
min,
max,
value,
val_pct: 0,
touching: false,
}
}
pub fn update_value(&mut self, pos: Point, ctx: &mut EventCtx) {
// Area where slider value is not saturated
let proportional_area = self.area.inset(Insets::new(
Self::SLIDER_WIDTH / 2,
0,
Self::SLIDER_WIDTH / 2,
0,
));
let filled = (proportional_area.y1 - pos.y).clamp(0, proportional_area.height());
let val_pct = (filled as u16 * 100) / proportional_area.height() as u16;
let val = (val_pct * (self.max - self.min)) / 100 + self.min;
if val != self.value {
ctx.request_paint();
self.value = val;
}
}
}
impl Component for VerticalSlider {
type Msg = u8;
fn place(&mut self, bounds: Rect) -> Rect {
self.area = Rect::snap(
bounds.center(),
Offset::new(Self::SLIDER_WIDTH, bounds.height()),
Alignment2D::CENTER,
);
self.touch_area = self.area.outset(Insets::uniform(20));
bounds
}
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
if let Event::Touch(touch_event) = event {
match touch_event {
TouchEvent::TouchStart(pos) if self.touch_area.contains(pos) => {
// Detect only touches inside the touch area
self.touching = true;
self.update_value(pos, ctx);
display::backlight(self.value as _);
ctx.request_paint();
}
TouchEvent::TouchMove(pos) if self.touching => {
self.update_value(pos, ctx);
// Update only if the touch started inside the touch area
display::backlight(self.value as _);
}
TouchEvent::TouchEnd(pos) if self.touching => {
self.touching = false;
self.update_value(pos, ctx);
ctx.request_paint();
// Confirm the value only if the touch ended inside the touch area
if self.touch_area.contains(pos) {
return Some(self.value as _);
} else {
display::backlight(self.value as _);
}
}
_ => {}
};
}
None
}
fn render<'s>(&'s self, target: &mut impl Renderer<'s>) {
let val_pct = ((100 * (self.value - self.min)) / (self.max - self.min)).clamp(0, 100);
// Square area for the slider
let (_, small_area) = self.area.split_bottom(Self::SLIDER_WIDTH);
// Background pad
Bar::new(self.area)
.with_radius(12)
.with_bg(theme::GREY_EXTRA_DARK)
.render(target);
// Moving slider
Bar::new(small_area.translate(
Offset::y(val_pct as i16 * (self.area.height() - Self::SLIDER_WIDTH) / 100).neg(),
))
.with_radius(4)
.with_bg(theme::GREY_LIGHT)
.render(target);
}
}
#[cfg(feature = "ui_debug")]
impl crate::trace::Trace for VerticalSlider {
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
t.component("VerticalSlider");
t.int("value", self.value as i64);
}
}
#[cfg(test)]
mod tests {
use super::{super::super::constant::SCREEN, *};
#[test]
fn test_component_heights_fit_screen() {
assert!(
SetBrightnessScreen::SLIDER_HEIGHT + Header::HEADER_HEIGHT <= SCREEN.height(),
"Components overflow the screen height",
);
}
}

View File

@ -1,4 +1,5 @@
mod action_bar;
mod brightness_screen;
mod confirm_homescreen;
mod header;
mod hint;
@ -14,6 +15,7 @@ mod vertical_menu;
mod vertical_menu_screen;
pub use action_bar::{ActionBar, ActionBarMsg};
pub use brightness_screen::{SetBrightnessMsg, SetBrightnessScreen};
pub use confirm_homescreen::{ConfirmHomescreen, ConfirmHomescreenMsg};
pub use header::{Header, HeaderMsg};
pub use hint::Hint;

View File

@ -34,7 +34,7 @@ use super::{
firmware::{
ActionBar, Bip39Input, ConfirmHomescreen, Header, HeaderMsg, Hint, Homescreen,
MnemonicKeyboard, NumberInputScreen, PinKeyboard, SelectWordCountScreen, SelectWordScreen,
Slip39Input, TextScreen,
SetBrightnessScreen, Slip39Input, TextScreen,
},
flow, fonts, theme, UIEckhart,
};
@ -648,8 +648,14 @@ impl FirmwareUI for UIEckhart {
Ok(layout)
}
fn set_brightness(_current_brightness: Option<u8>) -> Result<impl LayoutMaybeTrace, Error> {
Err::<RootComponent<Empty, ModelUI>, Error>(Error::ValueError(c"not implemented"))
fn set_brightness(current_brightness: Option<u8>) -> Result<impl LayoutMaybeTrace, Error> {
let content = SetBrightnessScreen::new(
theme::backlight::get_backlight_min() as u16,
theme::backlight::get_backlight_max() as u16,
current_brightness.unwrap_or(theme::backlight::get_backlight_normal()) as u16,
);
let layout = RootComponent::new(content);
Ok(layout)
}
fn show_address_details(