Compare commits

..

5 Commits

@ -53,7 +53,7 @@ static uint64_t term_glyph_bits(char ch) {
uint8_t bytes[8];
} result = {0};
if (ch > ' ' && ch < 128) {
if (ch > 32 && (uint8_t)ch < 128) {
const uint8_t *b = &Font_Bitmap[(ch - ' ') * 5];
for (int y = 0; y < 7; y++) {

@ -67,6 +67,18 @@ impl Div<Duration> for Duration {
}
}
impl From<f32> for Duration {
fn from(value: f32) -> Self {
Self::from_millis((value * 1000.0) as u32)
}
}
impl From<Duration> for f32 {
fn from(value: Duration) -> Self {
value.to_millis() as f32 / 1000.0
}
}
/* Instants can wrap around and we want them to be comparable even after
* wrapping around. This works by setting a maximum allowable difference
* between two Instants to half the range. In checked_add and checked_sub, we
@ -148,6 +160,75 @@ impl Ord for Instant {
}
}
/// A stopwatch is a utility designed for measuring the amount of time
/// that elapses between its start and stop points. It can be used in various
/// situations - animation timing, event timing, testing and debugging.
pub enum Stopwatch {
Stopped(Duration),
Running(Instant),
}
impl Default for Stopwatch {
/// Returns a new sopteed stopwatch by default.
fn default() -> Self {
Self::new_stopped()
}
}
impl Stopwatch {
/// Creates a new stopped stopwatch with duration of zero
pub fn new_stopped() -> Self {
Self::Stopped(Duration::ZERO)
}
/// Creates a new started stopwatch that starts
/// at the current instant.
pub fn new_started() -> Self {
Self::Running(Instant::now())
}
/// Starts or restarts the stopwatch.
///
/// If the stopwatch is already running, it restarts, setting
/// the elapsed time to zero.
pub fn start(&mut self) {
*self = Self::Running(Instant::now());
}
/// Stops the stopwatch.
///
/// When stopped, the `elapsed()` method will return the total
/// duration for which the stopwatch was running.
pub fn stop(&mut self) {
*self = Self::Stopped(self.elapsed());
}
/// Returns the elapsed duration since the stopwatch was last started.
///
/// If the stopwatch is running, it calculates the time from the last
/// start instant to the current instant.
pub fn elapsed(&self) -> Duration {
match *self {
Self::Stopped(duration) => duration,
Self::Running(time) => Instant::now().checked_duration_since(time).unwrap(),
}
}
/// Returns `true` if the stopwatch is currenly running.
pub fn is_running(&self) -> bool {
matches!(*self, Self::Running(_))
}
/// Checks if the stopwatch is running and whether the elapsed
/// time since the last start is less than or equal to a specified limit.
pub fn is_running_within(&self, limit: Duration) -> bool {
match *self {
Self::Stopped(_) => false,
Self::Running(_) => self.elapsed() <= limit,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
@ -160,4 +241,54 @@ mod tests {
assert_eq!(later, Instant { millis: 0 });
assert!(earlier < later);
}
#[test]
fn stopwatch_builds_correctly() {
let sw = Stopwatch::new_started();
assert!(sw.is_running());
let sw = Stopwatch::new_stopped();
assert!(!sw.is_running());
let sw: Stopwatch = Default::default();
assert!(!sw.is_running());
}
fn wait(duration: Duration) {
let origin = Instant::now();
while Instant::now().checked_duration_since(origin).unwrap() < duration {}
}
#[test]
fn stopwatch_starts_correcly() {
let mut sw = Stopwatch::new_stopped();
assert!(!sw.is_running());
sw.start();
assert!(sw.is_running());
wait(Duration::from_millis(10));
assert!(sw.elapsed() >= Duration::from_millis(10));
assert!(!sw.is_running_within(Duration::from_millis(5)));
assert!(sw.is_running_within(Duration::from_millis(10000)));
}
#[test]
fn stopwatch_stops_correctly() {
let mut sw = Stopwatch::new_started();
assert!(sw.is_running());
wait(Duration::from_millis(10));
sw.stop();
assert!(!sw.is_running());
let elapsed = sw.elapsed();
assert!(elapsed >= Duration::from_millis(10));
wait(Duration::from_millis(10));
assert!(sw.elapsed() == elapsed);
assert!(!sw.is_running_within(Duration::from_millis(5)));
assert!(!sw.is_running_within(Duration::from_millis(10000)));
}
}

@ -1,43 +0,0 @@
use crate::time;
pub struct AnimTimer {
base: Option<time::Instant>,
limit: Option<time::Duration>,
}
impl AnimTimer {
pub fn new() -> Self {
Self {
base: None,
limit: None,
}
}
pub fn reset(&mut self) {
self.base = None;
}
pub fn start(&mut self) {
self.base = Some(time::Instant::now());
}
pub fn stop(&mut self) {
self.base = None;
}
pub fn is_running(&self) -> bool {
self.base.is_some()
}
pub fn elapsed(&self) -> f32 {
if let Some(t) = self.base {
time::Instant::now()
.checked_duration_since(t)
.unwrap()
.to_millis() as f32
/ 1000.0
} else {
0.0
}
}
}

@ -1,19 +1,21 @@
use super::anim_timer::AnimTimer;
use crate::ui::{
component::Event,
component::{Event, TimerToken},
display::{self, Color, Font},
event::TouchEvent,
geometry::{Offset, Point, Rect},
shape::{self, Viewport},
};
use crate::{time, trezorhal::io::io_touch_read};
use crate::{
time::{Duration, Stopwatch},
trezorhal::io::io_touch_read,
};
use core::fmt::Write;
use heapless::String;
use super::{screen1::build_screen1, screen2::build_screen2};
fn render_time_overlay(duration: time::Duration) {
fn render_time_overlay(duration: Duration) {
shape::render_on_display(
Some(Viewport::new(Rect::new(
Point::new(200, 0),
@ -24,7 +26,7 @@ fn render_time_overlay(duration: time::Duration) {
let text_color = Color::rgb(255, 255, 0);
let mut info = String::<128>::new();
write!(info, "{}", duration.to_millis() as f32 / 1.0).unwrap();
write!(info, "{}", duration.to_millis()).unwrap();
let font = Font::NORMAL;
let pt = Point::new(200, font.vert_center(0, 20, "A"));
@ -48,25 +50,41 @@ fn touch_event() -> Option<TouchEvent> {
TouchEvent::new(event_type, ex as _, ey as _).ok()
}
// Returns the split point between two screens
// Animates the split point between two screens
// (ranging from -240 to 0)
fn anim_screen_transition(timer: &AnimTimer) -> i16 {
let anim = pareen::constant(0.0)
.seq_ease_in_out(
0.0,
pareen::easer::functions::Back,
1.5,
pareen::constant(-240.0),
)
.seq_ease_in_out(
5.0,
pareen::easer::functions::Back,
0.75,
pareen::constant(0.0),
)
.repeat(10.0);
anim.eval(timer.elapsed()) as i16
#[derive(Default)]
struct ScreenTransitionAnim {
timer: Stopwatch,
}
impl ScreenTransitionAnim {
pub fn is_active(&self) -> bool {
self.timer.is_running()
}
pub fn eval(&self) -> i16 {
let anim = pareen::constant(0.0)
.seq_ease_in_out(
0.0,
pareen::easer::functions::Back,
1.5,
pareen::constant(-240.0),
)
.seq_ease_in_out(
5.0,
pareen::easer::functions::Back,
0.75,
pareen::constant(0.0),
)
.dur(10.0)
.repeat();
anim.eval(self.timer.elapsed().into()) as i16
}
}
extern "C" fn void() {
}
#[no_mangle]
@ -74,29 +92,33 @@ extern "C" fn drawlib_demo() {
let screen1 = build_screen1().unwrap();
let screen2 = build_screen2().unwrap();
let mut timer = AnimTimer::new();
timer.start();
screen1.obj_event(Event::Attach).unwrap();
screen2.obj_event(Event::Attach).unwrap();
let mut anim = ScreenTransitionAnim::default();
anim.timer.start();
loop {
//screen1.obj_event(Event::Timer(TimerToken::INVALID)).unwrap();
//screen2.obj_event(Event::Timer(TimerToken::INVALID)).unwrap();
if let Some(e) = touch_event() {
screen1.obj_event(Event::Touch(e)).unwrap();
screen2.obj_event(Event::Touch(e)).unwrap();
}
let split = anim_screen_transition(&timer);
let split = anim.eval();
display::sync();
let start_time = time::Instant::now();
let stopwatch = Stopwatch::new_started();
//screen1.obj_paint_if_requested();
screen1.obj_render(Offset::x(split));
screen2.obj_render(Offset::x(240 + split));
let duration = time::Instant::now()
.checked_duration_since(start_time)
.unwrap();
render_time_overlay(duration);
render_time_overlay(stopwatch.elapsed());
display::refresh();
}

@ -1,4 +1,3 @@
pub mod anim_timer;
pub mod demo_core;
pub mod screen1;
pub mod screen2;

@ -9,48 +9,58 @@ use crate::ui::{
shape::Renderer,
};
use crate::time::{Duration, Stopwatch};
use crate::{
error::Error,
micropython::{gc::Gc, obj::Obj},
};
use pareen;
use super::anim_timer::AnimTimer;
pub struct Screen1 {
jump_time: AnimTimer,
#[derive(Default)]
struct BallAnim {
pub timer: Stopwatch,
}
impl Screen1 {
pub fn new() -> Self {
Self {
jump_time: AnimTimer::new(),
}
impl BallAnim {
const DURATION: f32 = 1.5;
pub fn is_active(&self) -> bool {
self.timer.is_running_within(Duration::from(Self::DURATION))
}
}
impl Screen1 {
fn anim_ball_coords(&self) -> Point {
pub fn eval(&self) -> Point {
let x_anim = pareen::constant(30.0).seq_ease_out(
0.0,
pareen::easer::functions::Cubic,
1.5,
Self::DURATION,
pareen::constant(210.0),
);
let y_anim = pareen::constant(30.0).seq_ease_out(
0.0,
pareen::easer::functions::Bounce,
1.5,
Self::DURATION,
pareen::constant(210.0),
);
let t = self.jump_time.elapsed();
let t = self.timer.elapsed().into();
Point::new(x_anim.eval(t) as i16, y_anim.eval(t) as i16)
}
}
fn anim_text_coords(&self) -> Point {
#[derive(Default)]
struct TextAnim {
pub timer: Stopwatch,
}
impl TextAnim {
pub fn is_active(&self) -> bool {
self.timer.is_running_within(Duration::from(3.4))
}
pub fn eval(&self) -> Point {
let x_anim = pareen::constant(30.0)
.seq_ease_in(
0.0,
@ -65,12 +75,26 @@ impl Screen1 {
pareen::constant(30.0),
);
let t = self.jump_time.elapsed();
let t = self.timer.elapsed().into();
Point::new(x_anim.eval(t) as i16, 120)
}
}
pub struct Screen1 {
ball_anim: BallAnim,
text_anim: TextAnim,
}
impl Screen1 {
pub fn new() -> Self {
Self {
ball_anim: Default::default(),
text_anim: Default::default(),
}
}
}
impl Component for Screen1 {
type Msg = Never;
@ -78,14 +102,19 @@ impl Component for Screen1 {
bounds
}
fn event(&mut self, _ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
match event {
Event::Touch(TouchEvent::TouchStart(_)) => {
self.jump_time.start();
self.ball_anim.timer.start();
self.text_anim.timer.start();
}
_ => {}
}
if self.ball_anim.is_active() && self.text_anim.is_active() {
ctx.request_anim_frame();
}
None
}
@ -97,11 +126,11 @@ impl Component for Screen1 {
.with_bg(Color::rgb(0, 20, 40))
.render(target);
shape::Text::new(self.anim_text_coords(), "Touch to start")
shape::Text::new(self.text_anim.eval(), "Touch to start")
.with_font(Font::BOLD)
.render(target);
shape::Circle::new(self.anim_ball_coords(), 16)
shape::Circle::new(self.ball_anim.eval(), 16)
.with_bg(Color::rgb(96, 128, 128))
.render(target);
}

@ -1,8 +1,7 @@
use crate::ui::{
component::{Component, Event, EventCtx, Never},
display::Color,
event::TouchEvent,
geometry::{Offset, Point, Rect},
geometry::{Point, Rect},
layout::obj::{ComponentMsgObj, LayoutObj},
model_tt::{component::Frame, theme},
shape,
@ -15,44 +14,34 @@ use crate::{
};
use pareen;
use super::anim_timer::AnimTimer;
use crate::time::Stopwatch;
pub struct Screen1 {
jump_time: AnimTimer,
#[derive(Default)]
struct LoaderAnim {
pub timer: Stopwatch,
}
impl Screen1 {
pub fn new() -> Self {
Self {
jump_time: {
let mut timer = AnimTimer::new();
timer.start();
timer
},
}
impl LoaderAnim {
pub fn is_active(&self) -> bool {
self.timer.is_running()
}
}
impl Screen1 {
fn anim_radius(&self) -> (i16, i16, i16) {
let r1_anim = pareen::circle()
.cos()
.scale_min_max(60f32, 64f32)
.repeat(1.0f32);
pub fn eval(&self) -> (i16, i16, i16) {
let r1_anim = pareen::circle().cos().scale_min_max(60.0, 64.0).repeat(1.0);
let r2_anim = pareen::circle()
.cos()
.scale_min_max(52f32, 58f32)
.squeeze(0f32..=0.5f32)
.repeat(0.5f32);
.scale_min_max(52.0, 58.0)
.squeeze(0.0..=0.5)
.repeat(0.5);
let r3_anim = pareen::circle()
.cos()
.scale_min_max(40f32, 50f32)
.squeeze(0f32..=2f32)
.repeat(2f32);
.scale_min_max(40.0, 50.0)
.squeeze(0.0..=2.0)
.repeat(2.0);
let t = self.jump_time.elapsed();
let t = self.timer.elapsed().into();
(
r1_anim.eval(t) as i16,
@ -62,6 +51,18 @@ impl Screen1 {
}
}
pub struct Screen1 {
loader_anim: LoaderAnim,
}
impl Screen1 {
pub fn new() -> Self {
Self {
loader_anim: Default::default(),
}
}
}
impl Component for Screen1 {
type Msg = Never;
@ -69,14 +70,18 @@ impl Component for Screen1 {
bounds
}
fn event(&mut self, _ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
match event {
Event::Touch(TouchEvent::TouchStart(_)) => {
self.jump_time.start();
Event::Attach => {
self.loader_anim.timer.start();
}
_ => {}
}
if self.loader_anim.is_active() {
ctx.request_anim_frame();
}
None
}
@ -90,7 +95,7 @@ impl Component for Screen1 {
let center = Point::new(120, 120);
let (r1, r2, r3) = self.anim_radius();
let (r1, r2, r3) = self.loader_anim.eval();
shape::Circle::new(center, r1)
.with_thickness(2)

@ -191,14 +191,14 @@ impl LayoutObj {
// painting by now, and we're prepared for a paint pass.
// Drain any pending timers into the callback.
while let Some((token, deadline)) = inner.event_ctx.pop_timer() {
let token = token.try_into();
while let Some((_token, _deadline)) = inner.event_ctx.pop_timer() {
/*let token = token.try_into();
let deadline = deadline.try_into();
if let (Ok(token), Ok(deadline)) = (token, deadline) {
inner.timer_fn.call_with_n_args(&[token, deadline])?;
} else {
// Failed to convert token or deadline into `Obj`, skip.
}
}*/
}
if let Some(count) = inner.event_ctx.page_count() {

Loading…
Cancel
Save