mirror of
https://github.com/trezor/trezor-firmware.git
synced 2025-07-26 08:29:26 +00:00
feat(core/rust): basic marquee implementation
[no changelog]
This commit is contained in:
parent
6c3e476398
commit
d768d56e07
@ -23,6 +23,11 @@
|
|||||||
#include "memzero.h"
|
#include "memzero.h"
|
||||||
|
|
||||||
#if USE_DMA2D
|
#if USE_DMA2D
|
||||||
|
#if defined BOOTLOADER
|
||||||
|
#define BUFFER_SECTION __attribute__((section(".buf")))
|
||||||
|
#else
|
||||||
|
#define BUFFER_SECTION
|
||||||
|
#endif
|
||||||
|
|
||||||
#define BUFFERS_16BPP 3
|
#define BUFFERS_16BPP 3
|
||||||
#define BUFFERS_4BPP 3
|
#define BUFFERS_4BPP 3
|
||||||
|
@ -55,6 +55,10 @@
|
|||||||
#define NODMA_BUFFER_SECTION __attribute__((section(".no_dma_buffers")))
|
#define NODMA_BUFFER_SECTION __attribute__((section(".no_dma_buffers")))
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#define LINE_BUFFER_16BPP_SIZE (BUFFER_PIXELS * 2)
|
||||||
|
#define LINE_BUFFER_4BPP_SIZE (BUFFER_PIXELS / 2)
|
||||||
|
#define TEXT_BUFFER_SIZE ((BUFFER_PIXELS * TEXT_BUFFER_HEIGHT) / 2)
|
||||||
|
|
||||||
typedef __attribute__((aligned(4))) struct {
|
typedef __attribute__((aligned(4))) struct {
|
||||||
uint8_t buffer[LINE_BUFFER_16BPP_SIZE];
|
uint8_t buffer[LINE_BUFFER_16BPP_SIZE];
|
||||||
} line_buffer_16bpp_t;
|
} line_buffer_16bpp_t;
|
||||||
|
229
core/embed/rust/src/ui/component/marquee.rs
Normal file
229
core/embed/rust/src/ui/component/marquee.rs
Normal file
@ -0,0 +1,229 @@
|
|||||||
|
use crate::{
|
||||||
|
micropython::buffer::StrBuffer,
|
||||||
|
time::{Duration, Instant},
|
||||||
|
ui::{
|
||||||
|
animation::Animation,
|
||||||
|
component::{Component, Event, EventCtx, Never, TimerToken},
|
||||||
|
display,
|
||||||
|
display::{Color, Font},
|
||||||
|
geometry::Rect,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
enum State {
|
||||||
|
Initial,
|
||||||
|
Left(Animation<i16>),
|
||||||
|
PauseLeft,
|
||||||
|
Right(Animation<i16>),
|
||||||
|
PauseRight,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Marquee {
|
||||||
|
area: Rect,
|
||||||
|
pause_token: Option<TimerToken>,
|
||||||
|
min_offset: i16,
|
||||||
|
max_offset: i16,
|
||||||
|
state: State,
|
||||||
|
text: StrBuffer,
|
||||||
|
font: Font,
|
||||||
|
fg: Color,
|
||||||
|
bg: Color,
|
||||||
|
duration: Duration,
|
||||||
|
pause: Duration,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Marquee {
|
||||||
|
pub fn new(text: StrBuffer, font: Font, fg: Color, bg: Color) -> Self {
|
||||||
|
Self {
|
||||||
|
area: Rect::zero(),
|
||||||
|
pause_token: None,
|
||||||
|
min_offset: 0,
|
||||||
|
max_offset: 0,
|
||||||
|
state: State::Initial,
|
||||||
|
text,
|
||||||
|
font,
|
||||||
|
fg,
|
||||||
|
bg,
|
||||||
|
duration: Duration::from_millis(2000),
|
||||||
|
pause: Duration::from_millis(1000),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn start(&mut self, ctx: &mut EventCtx, now: Instant) {
|
||||||
|
if let State::Initial = self.state {
|
||||||
|
let text_width = self.font.text_width(self.text.as_ref());
|
||||||
|
let max_offset = self.area.width() - text_width;
|
||||||
|
|
||||||
|
self.min_offset = 0;
|
||||||
|
self.max_offset = max_offset;
|
||||||
|
|
||||||
|
let anim = Animation::new(self.min_offset, max_offset, self.duration, now);
|
||||||
|
|
||||||
|
self.state = State::Left(anim);
|
||||||
|
|
||||||
|
// The animation is starting, request an animation frame event.
|
||||||
|
ctx.request_anim_frame();
|
||||||
|
|
||||||
|
// We don't have to wait for the animation frame event with the first paint,
|
||||||
|
// let's do that now.
|
||||||
|
ctx.request_paint();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn reset(&mut self) {
|
||||||
|
self.state = State::Initial;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn animation(&self) -> Option<&Animation<i16>> {
|
||||||
|
match &self.state {
|
||||||
|
State::Initial => None,
|
||||||
|
State::Left(a) => Some(a),
|
||||||
|
State::PauseLeft => None,
|
||||||
|
State::Right(a) => Some(a),
|
||||||
|
State::PauseRight => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_at_right(&self, now: Instant) -> bool {
|
||||||
|
if let Some(p) = self.progress(now) {
|
||||||
|
return p == self.min_offset;
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_at_left(&self, now: Instant) -> bool {
|
||||||
|
if let Some(p) = self.progress(now) {
|
||||||
|
return p == self.max_offset;
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn progress(&self, now: Instant) -> Option<i16> {
|
||||||
|
self.animation().map(|a| a.value(now))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_animating(&self) -> bool {
|
||||||
|
self.animation().is_some()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn paint_anim(&mut self, offset: i16) {
|
||||||
|
display::marquee(
|
||||||
|
self.area,
|
||||||
|
self.text.as_ref(),
|
||||||
|
offset,
|
||||||
|
self.font,
|
||||||
|
self.fg,
|
||||||
|
self.bg,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Component for Marquee {
|
||||||
|
type Msg = Never;
|
||||||
|
|
||||||
|
fn place(&mut self, bounds: Rect) -> Rect {
|
||||||
|
let base_width = self.font.text_width("M");
|
||||||
|
let text_width = self.font.text_width(self.text.as_ref());
|
||||||
|
let area_width = bounds.width();
|
||||||
|
|
||||||
|
let shift_width = if area_width > text_width {
|
||||||
|
area_width - text_width
|
||||||
|
} else {
|
||||||
|
text_width - area_width
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut duration = (300 * shift_width as u32) / base_width as u32;
|
||||||
|
if duration < 300 {
|
||||||
|
duration = 300;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.duration = Duration::from_millis(duration);
|
||||||
|
self.area = bounds;
|
||||||
|
self.area
|
||||||
|
}
|
||||||
|
|
||||||
|
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
|
||||||
|
let now = Instant::now();
|
||||||
|
|
||||||
|
if let Event::Timer(token) = event {
|
||||||
|
if self.pause_token == Some(token) {
|
||||||
|
match self.state {
|
||||||
|
State::PauseLeft => {
|
||||||
|
let anim =
|
||||||
|
Animation::new(self.max_offset, self.min_offset, self.duration, now);
|
||||||
|
self.state = State::Right(anim);
|
||||||
|
}
|
||||||
|
State::PauseRight => {
|
||||||
|
let anim =
|
||||||
|
Animation::new(self.min_offset, self.max_offset, self.duration, now);
|
||||||
|
self.state = State::Left(anim);
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
// We have something to paint, so request to be painted in the next pass.
|
||||||
|
ctx.request_paint();
|
||||||
|
// There is further progress in the animation, request an animation frame event.
|
||||||
|
ctx.request_anim_frame();
|
||||||
|
}
|
||||||
|
|
||||||
|
if token == EventCtx::ANIM_FRAME_TIMER {
|
||||||
|
if self.is_animating() {
|
||||||
|
// We have something to paint, so request to be painted in the next pass.
|
||||||
|
ctx.request_paint();
|
||||||
|
// There is further progress in the animation, request an animation frame
|
||||||
|
// event.
|
||||||
|
ctx.request_anim_frame();
|
||||||
|
}
|
||||||
|
|
||||||
|
match self.state {
|
||||||
|
State::Right(_) => {
|
||||||
|
if self.is_at_right(now) {
|
||||||
|
self.pause_token = Some(ctx.request_timer(self.pause));
|
||||||
|
self.state = State::PauseRight;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
State::Left(_) => {
|
||||||
|
if self.is_at_left(now) {
|
||||||
|
self.pause_token = Some(ctx.request_timer(self.pause));
|
||||||
|
self.state = State::PauseLeft;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn paint(&mut self) {
|
||||||
|
let now = Instant::now();
|
||||||
|
|
||||||
|
match self.state {
|
||||||
|
State::Initial => {
|
||||||
|
self.paint_anim(0);
|
||||||
|
}
|
||||||
|
State::PauseRight => {
|
||||||
|
self.paint_anim(self.min_offset);
|
||||||
|
}
|
||||||
|
State::PauseLeft => {
|
||||||
|
self.paint_anim(self.max_offset);
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
let progress = self.progress(now);
|
||||||
|
if let Some(done) = progress {
|
||||||
|
self.paint_anim(done as i16);
|
||||||
|
} else {
|
||||||
|
self.paint_anim(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "ui_debug")]
|
||||||
|
impl crate::trace::Trace for Marquee {
|
||||||
|
fn trace(&self, d: &mut dyn crate::trace::Tracer) {
|
||||||
|
d.open("Marquee");
|
||||||
|
d.close();
|
||||||
|
}
|
||||||
|
}
|
@ -6,6 +6,7 @@ pub mod empty;
|
|||||||
pub mod image;
|
pub mod image;
|
||||||
pub mod label;
|
pub mod label;
|
||||||
pub mod map;
|
pub mod map;
|
||||||
|
pub mod marquee;
|
||||||
pub mod maybe;
|
pub mod maybe;
|
||||||
pub mod pad;
|
pub mod pad;
|
||||||
pub mod paginated;
|
pub mod paginated;
|
||||||
@ -20,6 +21,7 @@ pub use empty::Empty;
|
|||||||
pub use image::Image;
|
pub use image::Image;
|
||||||
pub use label::Label;
|
pub use label::Label;
|
||||||
pub use map::Map;
|
pub use map::Map;
|
||||||
|
pub use marquee::Marquee;
|
||||||
pub use maybe::Maybe;
|
pub use maybe::Maybe;
|
||||||
pub use pad::Pad;
|
pub use pad::Pad;
|
||||||
pub use paginated::{PageMsg, Paginate};
|
pub use paginated::{PageMsg, Paginate};
|
||||||
|
@ -14,7 +14,7 @@ use super::{
|
|||||||
};
|
};
|
||||||
#[cfg(feature = "dma2d")]
|
#[cfg(feature = "dma2d")]
|
||||||
use crate::trezorhal::{
|
use crate::trezorhal::{
|
||||||
buffers::{get_buffer_16bpp, get_buffer_4bpp, get_text_buffer},
|
buffers::{get_buffer_16bpp, get_buffer_4bpp},
|
||||||
dma2d::{
|
dma2d::{
|
||||||
dma2d_setup_4bpp_over_16bpp, dma2d_setup_4bpp_over_4bpp, dma2d_start_blend,
|
dma2d_setup_4bpp_over_16bpp, dma2d_setup_4bpp_over_4bpp, dma2d_start_blend,
|
||||||
dma2d_wait_for_transfer,
|
dma2d_wait_for_transfer,
|
||||||
@ -24,6 +24,7 @@ use crate::{
|
|||||||
error::Error,
|
error::Error,
|
||||||
time::Duration,
|
time::Duration,
|
||||||
trezorhal::{
|
trezorhal::{
|
||||||
|
buffers::get_text_buffer,
|
||||||
display,
|
display,
|
||||||
display::ToifFormat,
|
display::ToifFormat,
|
||||||
qr, time,
|
qr, time,
|
||||||
@ -930,6 +931,36 @@ pub fn bar_with_text_and_fill<T: AsRef<str>>(
|
|||||||
pixeldata_dirty();
|
pixeldata_dirty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn marquee(area: Rect, text: &str, offset: i16, font: Font, fg: Color, bg: Color) {
|
||||||
|
let buffer = unsafe { get_text_buffer(0, true) };
|
||||||
|
|
||||||
|
let area = area.translate(get_offset());
|
||||||
|
let clamped = area.clamp(constant::screen());
|
||||||
|
set_window(clamped);
|
||||||
|
|
||||||
|
display::text_into_buffer(text, font.into(), buffer, offset);
|
||||||
|
let tbl = get_color_table(fg, bg);
|
||||||
|
|
||||||
|
for y in 0..clamped.height() {
|
||||||
|
for x in 0..clamped.width() {
|
||||||
|
let pixel = y * constant::WIDTH + x;
|
||||||
|
let byte_idx = pixel / 2;
|
||||||
|
if byte_idx < buffer.buffer.len() as _ {
|
||||||
|
let data = if pixel % 2 != 0 {
|
||||||
|
buffer.buffer[byte_idx as usize] >> 4
|
||||||
|
} else {
|
||||||
|
buffer.buffer[byte_idx as usize] & 0xF
|
||||||
|
};
|
||||||
|
pixeldata(tbl[data as usize]);
|
||||||
|
} else {
|
||||||
|
pixeldata(bg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pixeldata_dirty();
|
||||||
|
}
|
||||||
|
|
||||||
// Used on T1 only.
|
// Used on T1 only.
|
||||||
pub fn dotted_line(start: Point, width: i16, color: Color) {
|
pub fn dotted_line(start: Point, width: i16, color: Color) {
|
||||||
for x in (start.x..width).step_by(2) {
|
for x in (start.x..width).step_by(2) {
|
||||||
|
Loading…
Reference in New Issue
Block a user