mirror of
https://github.com/trezor/trezor-firmware.git
synced 2025-01-03 20:11:00 +00:00
refactor(core/ui): clear display on rust side
[no changelog]
This commit is contained in:
parent
d8e7c00087
commit
d99e1eedd2
@ -47,17 +47,6 @@ STATIC mp_obj_t mod_trezorui_Display_make_new(const mp_obj_type_t *type,
|
||||
return MP_OBJ_FROM_PTR(o);
|
||||
}
|
||||
|
||||
/// def clear(self) -> None:
|
||||
/// """
|
||||
/// Clear display with black color.
|
||||
/// """
|
||||
STATIC mp_obj_t mod_trezorui_Display_clear(mp_obj_t self) {
|
||||
display_clear();
|
||||
return mp_const_none;
|
||||
}
|
||||
STATIC MP_DEFINE_CONST_FUN_OBJ_1(mod_trezorui_Display_clear_obj,
|
||||
mod_trezorui_Display_clear);
|
||||
|
||||
/// def refresh(self) -> None:
|
||||
/// """
|
||||
/// Refresh display (update screen).
|
||||
@ -161,7 +150,6 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_1(mod_trezorui_Display_clear_save_obj,
|
||||
mod_trezorui_Display_clear_save);
|
||||
|
||||
STATIC const mp_rom_map_elem_t mod_trezorui_Display_locals_dict_table[] = {
|
||||
{MP_ROM_QSTR(MP_QSTR_clear), MP_ROM_PTR(&mod_trezorui_Display_clear_obj)},
|
||||
{MP_ROM_QSTR(MP_QSTR_refresh),
|
||||
MP_ROM_PTR(&mod_trezorui_Display_refresh_obj)},
|
||||
{MP_ROM_QSTR(MP_QSTR_bar), MP_ROM_PTR(&mod_trezorui_Display_bar_obj)},
|
||||
|
@ -290,6 +290,7 @@ fn generate_trezorhal_bindings() {
|
||||
.allowlist_function("storage_set_counter")
|
||||
.allowlist_function("storage_next_counter")
|
||||
// display
|
||||
.allowlist_function("display_clear")
|
||||
.allowlist_function("display_offset")
|
||||
.allowlist_function("display_refresh")
|
||||
.allowlist_function("display_backlight")
|
||||
|
@ -174,3 +174,9 @@ pub fn refresh() {
|
||||
ffi::display_refresh();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn clear() {
|
||||
unsafe {
|
||||
ffi::display_clear();
|
||||
}
|
||||
}
|
||||
|
@ -6,7 +6,7 @@ use crate::{
|
||||
time::Duration,
|
||||
ui::{
|
||||
component::{maybe::PaintOverlapping, MsgMap},
|
||||
display::Color,
|
||||
display::{self, Color},
|
||||
geometry::{Offset, Rect},
|
||||
},
|
||||
};
|
||||
@ -191,6 +191,80 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
/// Same as `Child` but also handles screen clearing when layout is first
|
||||
/// painted.
|
||||
pub struct Root<T> {
|
||||
inner: Child<T>,
|
||||
marked_for_clear: bool,
|
||||
}
|
||||
|
||||
impl<T> Root<T> {
|
||||
pub fn new(component: T) -> Self {
|
||||
Self {
|
||||
inner: Child::new(component),
|
||||
marked_for_clear: true,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn inner(&self) -> &Child<T> {
|
||||
&self.inner
|
||||
}
|
||||
|
||||
pub fn skip_paint(&mut self) {
|
||||
self.inner.skip_paint()
|
||||
}
|
||||
|
||||
pub fn clear_screen(&mut self) {
|
||||
self.marked_for_clear = true;
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Component for Root<T>
|
||||
where
|
||||
T: Component,
|
||||
{
|
||||
type Msg = T::Msg;
|
||||
|
||||
fn place(&mut self, bounds: Rect) -> Rect {
|
||||
self.inner.place(bounds)
|
||||
}
|
||||
|
||||
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
|
||||
let msg = self.inner.event(ctx, event);
|
||||
if ctx.needs_repaint_root() {
|
||||
self.marked_for_clear = true;
|
||||
let mut dummy_ctx = EventCtx::new();
|
||||
let paint_msg = self.inner.event(&mut dummy_ctx, Event::RequestPaint);
|
||||
assert!(matches!(paint_msg, None));
|
||||
assert!(dummy_ctx.timers.is_empty());
|
||||
}
|
||||
msg
|
||||
}
|
||||
|
||||
fn paint(&mut self) {
|
||||
if self.marked_for_clear && self.inner.will_paint() {
|
||||
self.marked_for_clear = false;
|
||||
display::clear()
|
||||
}
|
||||
self.inner.paint();
|
||||
}
|
||||
|
||||
#[cfg(feature = "ui_bounds")]
|
||||
fn bounds(&self, sink: &mut dyn FnMut(Rect)) {
|
||||
self.inner.bounds(sink)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "ui_debug")]
|
||||
impl<T> crate::trace::Trace for Root<T>
|
||||
where
|
||||
T: crate::trace::Trace,
|
||||
{
|
||||
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
|
||||
self.inner.trace(t)
|
||||
}
|
||||
}
|
||||
|
||||
impl<M, T, U> Component for (T, U)
|
||||
where
|
||||
T: Component<Msg = M>,
|
||||
@ -379,6 +453,7 @@ pub struct EventCtx {
|
||||
paint_requested: bool,
|
||||
anim_frame_scheduled: bool,
|
||||
page_count: Option<usize>,
|
||||
root_repaint_requested: bool,
|
||||
}
|
||||
|
||||
impl EventCtx {
|
||||
@ -404,6 +479,7 @@ impl EventCtx {
|
||||
* `Child::marked_for_paint` being true. */
|
||||
anim_frame_scheduled: false,
|
||||
page_count: None,
|
||||
root_repaint_requested: false,
|
||||
}
|
||||
}
|
||||
|
||||
@ -442,6 +518,14 @@ impl EventCtx {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn request_repaint_root(&mut self) {
|
||||
self.root_repaint_requested = true;
|
||||
}
|
||||
|
||||
pub fn needs_repaint_root(&self) -> bool {
|
||||
self.root_repaint_requested
|
||||
}
|
||||
|
||||
pub fn set_page_count(&mut self, count: usize) {
|
||||
#[cfg(feature = "ui_debug")]
|
||||
assert!(self.page_count.is_none());
|
||||
@ -461,6 +545,7 @@ impl EventCtx {
|
||||
self.paint_requested = false;
|
||||
self.anim_frame_scheduled = false;
|
||||
self.page_count = None;
|
||||
self.root_repaint_requested = false;
|
||||
}
|
||||
|
||||
fn register_timer(&mut self, token: TimerToken, deadline: Duration) {
|
||||
|
@ -16,7 +16,7 @@ pub mod qr_code;
|
||||
pub mod text;
|
||||
pub mod timeout;
|
||||
|
||||
pub use base::{Child, Component, ComponentExt, Event, EventCtx, Never, TimerToken};
|
||||
pub use base::{Child, Component, ComponentExt, Event, EventCtx, Never, Root, TimerToken};
|
||||
pub use border::Border;
|
||||
pub use empty::Empty;
|
||||
pub use label::Label;
|
||||
|
@ -341,6 +341,11 @@ pub fn rect_fill_corners(r: Rect, fg_color: Color) {
|
||||
}
|
||||
}
|
||||
|
||||
/// Draw black rectangle over entire screen.
|
||||
pub fn clear() {
|
||||
display::clear();
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq)]
|
||||
pub struct TextOverlay<T> {
|
||||
area: Rect,
|
||||
|
@ -17,7 +17,7 @@ use crate::{
|
||||
},
|
||||
time::Duration,
|
||||
ui::{
|
||||
component::{Child, Component, Event, EventCtx, Never, TimerToken},
|
||||
component::{Component, Event, EventCtx, Never, Root, TimerToken},
|
||||
constant,
|
||||
display::sync,
|
||||
geometry::Rect,
|
||||
@ -49,9 +49,10 @@ pub trait ObjComponent: MaybeTrace {
|
||||
fn obj_paint(&mut self) -> bool;
|
||||
fn obj_bounds(&self, _sink: &mut dyn FnMut(Rect)) {}
|
||||
fn obj_skip_paint(&mut self) {}
|
||||
fn obj_request_clear(&mut self) {}
|
||||
}
|
||||
|
||||
impl<T> ObjComponent for Child<T>
|
||||
impl<T> ObjComponent for Root<T>
|
||||
where
|
||||
T: ComponentMsgObj + MaybeTrace,
|
||||
{
|
||||
@ -61,14 +62,14 @@ where
|
||||
|
||||
fn obj_event(&mut self, ctx: &mut EventCtx, event: Event) -> Result<Obj, Error> {
|
||||
if let Some(msg) = self.event(ctx, event) {
|
||||
self.inner().msg_try_into_obj(msg)
|
||||
self.inner().inner().msg_try_into_obj(msg)
|
||||
} else {
|
||||
Ok(Obj::const_none())
|
||||
}
|
||||
}
|
||||
|
||||
fn obj_paint(&mut self) -> bool {
|
||||
let will_paint = self.will_paint();
|
||||
let will_paint = self.inner().will_paint();
|
||||
self.paint();
|
||||
will_paint
|
||||
}
|
||||
@ -81,6 +82,10 @@ where
|
||||
fn obj_skip_paint(&mut self) {
|
||||
self.skip_paint()
|
||||
}
|
||||
|
||||
fn obj_request_clear(&mut self) {
|
||||
self.clear_screen()
|
||||
}
|
||||
}
|
||||
|
||||
/// `LayoutObj` is a GC-allocated object exported to MicroPython, with type
|
||||
@ -102,9 +107,9 @@ struct LayoutObjInner {
|
||||
impl LayoutObj {
|
||||
/// Create a new `LayoutObj`, wrapping a root component.
|
||||
pub fn new(root: impl ComponentMsgObj + MaybeTrace + 'static) -> Result<Gc<Self>, Error> {
|
||||
// Let's wrap the root component into a `Child` to maintain the top-level
|
||||
// Let's wrap the root component into a `Root` to maintain the top-level
|
||||
// invalidation logic.
|
||||
let wrapped_root = Child::new(root);
|
||||
let wrapped_root = Root::new(root);
|
||||
// SAFETY: We are coercing GC-allocated sized ptr into an unsized one.
|
||||
let root =
|
||||
unsafe { Gc::from_raw(Gc::into_raw(Gc::new(wrapped_root)?) as *mut dyn ObjComponent) };
|
||||
@ -174,6 +179,12 @@ impl LayoutObj {
|
||||
Ok(msg)
|
||||
}
|
||||
|
||||
fn obj_request_clear(&self) {
|
||||
let mut inner = self.inner.borrow_mut();
|
||||
// SAFETY: `inner.root` is unique because of the `inner.borrow_mut()`.
|
||||
unsafe { Gc::as_mut(&mut inner.root) }.obj_request_clear();
|
||||
}
|
||||
|
||||
/// Run a paint pass over the component tree. Returns true if any component
|
||||
/// actually requested painting since last invocation of the function.
|
||||
fn obj_paint_if_requested(&self) -> bool {
|
||||
@ -427,6 +438,7 @@ extern "C" fn ui_layout_request_complete_repaint(this: Obj) -> Obj {
|
||||
#[cfg(feature = "ui_debug")]
|
||||
panic!("cannot raise messages during RequestPaint");
|
||||
};
|
||||
this.obj_request_clear();
|
||||
Ok(Obj::const_none())
|
||||
};
|
||||
unsafe { util::try_or_raise(block) }
|
||||
|
@ -18,11 +18,6 @@ class Display:
|
||||
Initialize the display.
|
||||
"""
|
||||
|
||||
def clear(self) -> None:
|
||||
"""
|
||||
Clear display with black color.
|
||||
"""
|
||||
|
||||
def refresh(self) -> None:
|
||||
"""
|
||||
Refresh display (update screen).
|
||||
|
@ -1,10 +1,9 @@
|
||||
# pylint: disable=wrong-import-position
|
||||
import utime
|
||||
from micropython import const
|
||||
from trezorui import Display
|
||||
from typing import TYPE_CHECKING, Any, Awaitable, Generator
|
||||
|
||||
from trezor import io, loop, utils, workflow
|
||||
from trezor import loop, utils
|
||||
|
||||
# all rendering is done through a singleton of `Display`
|
||||
display = Display()
|
||||
@ -46,9 +45,6 @@ if utils.EMULATOR or utils.INTERNAL_MODEL in ("T1B1", "T2B1"):
|
||||
# import style later to avoid circular dep
|
||||
from trezor.ui import style # isort:skip
|
||||
|
||||
# import style definitions into namespace
|
||||
from trezor.ui.style import * # isort:skip # noqa: F401,F403
|
||||
|
||||
|
||||
async def _alert(count: int) -> None:
|
||||
short_sleep = loop.sleep(20)
|
||||
@ -93,84 +89,6 @@ def backlight_fade(val: int, delay: int = 14000, step: int = 15) -> None:
|
||||
display.backlight(val)
|
||||
|
||||
|
||||
# Component events. Should be different from `io.TOUCH_*` events.
|
||||
# Event dispatched when components should draw to the display, if they are
|
||||
# marked for re-paint.
|
||||
RENDER = const(-255)
|
||||
# Event dispatched when components should mark themselves for re-painting.
|
||||
REPAINT = const(-256)
|
||||
|
||||
# How long, in milliseconds, should the layout rendering task sleep between
|
||||
# the render calls.
|
||||
_RENDER_DELAY_MS = const(10)
|
||||
|
||||
|
||||
class Component:
|
||||
"""
|
||||
Abstract class.
|
||||
|
||||
Components are GUI classes that inherit `Component` and form a tree, with a
|
||||
`Layout` at the root, and other components underneath. Components that
|
||||
have children, and therefore need to dispatch events to them, usually
|
||||
override the `dispatch` method. Leaf components usually override the event
|
||||
methods (`on_*`). Components signal a completion to the layout by raising
|
||||
an instance of `Result`.
|
||||
"""
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.repaint = True
|
||||
|
||||
if utils.INTERNAL_MODEL in ("T2T1", "D001"):
|
||||
|
||||
def dispatch(self, event: int, x: int, y: int) -> None:
|
||||
if event is RENDER:
|
||||
self.on_render()
|
||||
elif event is io.TOUCH_START:
|
||||
self.on_touch_start(x, y)
|
||||
elif event is io.TOUCH_MOVE:
|
||||
self.on_touch_move(x, y)
|
||||
elif event is io.TOUCH_END:
|
||||
self.on_touch_end(x, y)
|
||||
elif event is REPAINT:
|
||||
self.repaint = True
|
||||
|
||||
def on_touch_start(self, x: int, y: int) -> None:
|
||||
pass
|
||||
|
||||
def on_touch_move(self, x: int, y: int) -> None:
|
||||
pass
|
||||
|
||||
def on_touch_end(self, x: int, y: int) -> None:
|
||||
pass
|
||||
|
||||
elif utils.INTERNAL_MODEL in ("T1B1", "T2B1"):
|
||||
|
||||
def dispatch(self, event: int, x: int, y: int) -> None:
|
||||
if event is RENDER:
|
||||
self.on_render()
|
||||
elif event is io.BUTTON_PRESSED:
|
||||
self.on_button_pressed(x)
|
||||
elif event is io.BUTTON_RELEASED:
|
||||
self.on_button_released(x)
|
||||
elif event is REPAINT:
|
||||
self.repaint = True
|
||||
|
||||
def on_button_pressed(self, button_number: int) -> None:
|
||||
pass
|
||||
|
||||
def on_button_released(self, button_number: int) -> None:
|
||||
pass
|
||||
|
||||
def on_render(self) -> None:
|
||||
pass
|
||||
|
||||
if __debug__:
|
||||
|
||||
def read_content_into(self, content_store: list[str]) -> None:
|
||||
content_store.clear()
|
||||
content_store.append(self.__class__.__name__)
|
||||
|
||||
|
||||
class Result(Exception):
|
||||
"""
|
||||
When components want to trigger layout completion, they do so through
|
||||
@ -195,7 +113,7 @@ class Cancelled(Exception):
|
||||
"""
|
||||
|
||||
|
||||
class Layout(Component):
|
||||
class Layout:
|
||||
"""
|
||||
Abstract class.
|
||||
|
||||
@ -205,9 +123,6 @@ class Layout(Component):
|
||||
raised, usually from some of the child components.
|
||||
"""
|
||||
|
||||
BACKLIGHT_LEVEL = style.BACKLIGHT_NORMAL
|
||||
RENDER_SLEEP: loop.Syscall = loop.sleep(_RENDER_DELAY_MS)
|
||||
|
||||
async def __iter__(self) -> Any:
|
||||
"""
|
||||
Run the layout and wait until it completes. Returns the result value.
|
||||
@ -253,72 +168,13 @@ class Layout(Component):
|
||||
returns, the others are closed and `create_tasks` is called again.
|
||||
|
||||
Usually overridden to add another tasks to the list."""
|
||||
return self.handle_input(), self.handle_rendering()
|
||||
raise NotImplementedError
|
||||
|
||||
if utils.INTERNAL_MODEL in ("T2T1", "D001"):
|
||||
if __debug__:
|
||||
|
||||
def handle_input(self) -> Generator:
|
||||
"""Task that is waiting for the user input."""
|
||||
touch = loop.wait(io.TOUCH)
|
||||
while True:
|
||||
# Using `yield` instead of `await` to avoid allocations.
|
||||
event, x, y = yield touch
|
||||
workflow.idle_timer.touch()
|
||||
self.dispatch(event, x, y)
|
||||
# We dispatch a render event right after the touch. Quick and dirty
|
||||
# way to get the lowest input-to-render latency.
|
||||
self.dispatch(RENDER, 0, 0)
|
||||
|
||||
elif utils.INTERNAL_MODEL in ("T1B1", "T2B1"):
|
||||
|
||||
def handle_input(self) -> Generator:
|
||||
"""Task that is waiting for the user input."""
|
||||
button = loop.wait(io.BUTTON)
|
||||
while True:
|
||||
event, button_num = yield button
|
||||
workflow.idle_timer.touch()
|
||||
self.dispatch(event, button_num, 0)
|
||||
self.dispatch(RENDER, 0, 0)
|
||||
|
||||
else:
|
||||
raise ValueError("Unknown Trezor model")
|
||||
|
||||
def _before_render(self) -> None:
|
||||
# Before the first render, we dim the display.
|
||||
backlight_fade(style.BACKLIGHT_NONE)
|
||||
# Clear the screen of any leftovers, make sure everything is marked for
|
||||
# repaint (we can be running the same layout instance multiple times)
|
||||
# and paint it.
|
||||
display.clear()
|
||||
self.dispatch(REPAINT, 0, 0)
|
||||
self.dispatch(RENDER, 0, 0)
|
||||
|
||||
if __debug__ and self.should_notify_layout_change:
|
||||
from apps.debug import notify_layout_change
|
||||
|
||||
# notify about change and do not notify again until next await.
|
||||
# (handle_rendering might be called multiple times in a single await,
|
||||
# because of the endless loop in __iter__)
|
||||
self.should_notify_layout_change = False
|
||||
notify_layout_change(self)
|
||||
|
||||
# Display is usually refreshed after every loop step, but here we are
|
||||
# rendering everything synchronously, so refresh it manually and turn
|
||||
# the brightness on again.
|
||||
refresh()
|
||||
backlight_fade(self.BACKLIGHT_LEVEL)
|
||||
|
||||
def handle_rendering(self) -> loop.Task: # type: ignore [awaitable-is-generator]
|
||||
"""Task that is rendering the layout in a busy loop."""
|
||||
self._before_render()
|
||||
sleep = self.RENDER_SLEEP
|
||||
while True:
|
||||
# Wait for a couple of ms and render the layout again. Because
|
||||
# components use re-paint marking, they do not really draw on the
|
||||
# display needlessly. Using `yield` instead of `await` to avoid allocations.
|
||||
# TODO: remove the busy loop
|
||||
yield sleep
|
||||
self.dispatch(RENDER, 0, 0)
|
||||
def read_content_into(self, content_store: list[str]) -> None:
|
||||
content_store.clear()
|
||||
content_store.append(self.__class__.__name__)
|
||||
|
||||
|
||||
def wait_until_layout_is_running() -> Awaitable[None]: # type: ignore [awaitable-is-generator]
|
||||
|
@ -194,8 +194,6 @@ class RustLayout(ui.Layout):
|
||||
return self.handle_timers(), self.handle_input_and_rendering()
|
||||
|
||||
def _first_paint(self) -> None:
|
||||
# Clear the screen of any leftovers.
|
||||
ui.display.clear()
|
||||
self._paint()
|
||||
|
||||
if __debug__ and self.should_notify_layout_change:
|
||||
@ -253,7 +251,6 @@ def draw_simple(layout: Any) -> None:
|
||||
raise RuntimeError
|
||||
|
||||
layout.attach_timer_fn(dummy_set_timer)
|
||||
ui.display.clear()
|
||||
layout.paint()
|
||||
ui.refresh()
|
||||
|
||||
|
@ -15,7 +15,6 @@ class RustProgress:
|
||||
layout: Any,
|
||||
):
|
||||
self.layout = layout
|
||||
ui.display.clear()
|
||||
self.layout.attach_timer_fn(self.set_timer)
|
||||
self.layout.paint()
|
||||
|
||||
|
@ -30,6 +30,8 @@ if __debug__:
|
||||
|
||||
|
||||
class RustLayout(ui.Layout):
|
||||
BACKLIGHT_LEVEL = ui.style.BACKLIGHT_NORMAL
|
||||
|
||||
# pylint: disable=super-init-not-called
|
||||
def __init__(self, layout: Any):
|
||||
self.layout = layout
|
||||
@ -160,9 +162,7 @@ class RustLayout(ui.Layout):
|
||||
return self.handle_timers(), self.handle_input_and_rendering()
|
||||
|
||||
def _first_paint(self) -> None:
|
||||
# Clear the screen of any leftovers.
|
||||
ui.backlight_fade(ui.style.BACKLIGHT_NONE)
|
||||
ui.display.clear()
|
||||
self._paint()
|
||||
|
||||
if __debug__ and self.should_notify_layout_change:
|
||||
@ -223,7 +223,6 @@ def draw_simple(layout: Any) -> None:
|
||||
|
||||
layout.attach_timer_fn(dummy_set_timer)
|
||||
ui.backlight_fade(ui.style.BACKLIGHT_DIM)
|
||||
ui.display.clear()
|
||||
layout.paint()
|
||||
ui.refresh()
|
||||
ui.backlight_fade(ui.style.BACKLIGHT_NORMAL)
|
||||
|
@ -86,7 +86,7 @@ class Homescreen(HomescreenBase):
|
||||
|
||||
class Lockscreen(HomescreenBase):
|
||||
RENDER_INDICATOR = storage_cache.LOCKSCREEN_ON
|
||||
BACKLIGHT_LEVEL = ui.BACKLIGHT_LOW
|
||||
BACKLIGHT_LEVEL = ui.style.BACKLIGHT_LOW
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
@ -95,7 +95,7 @@ class Lockscreen(HomescreenBase):
|
||||
) -> None:
|
||||
self.bootscreen = bootscreen
|
||||
if bootscreen:
|
||||
self.BACKLIGHT_LEVEL = ui.BACKLIGHT_NORMAL
|
||||
self.BACKLIGHT_LEVEL = ui.style.BACKLIGHT_NORMAL
|
||||
|
||||
skip = (
|
||||
not bootscreen and storage_cache.homescreen_shown is self.RENDER_INDICATOR
|
||||
|
@ -16,7 +16,6 @@ class RustProgress:
|
||||
):
|
||||
self.layout = layout
|
||||
ui.backlight_fade(ui.style.BACKLIGHT_DIM)
|
||||
ui.display.clear()
|
||||
self.layout.attach_timer_fn(self.set_timer)
|
||||
self.layout.paint()
|
||||
ui.backlight_fade(ui.style.BACKLIGHT_NORMAL)
|
||||
|
@ -5,9 +5,6 @@ from trezor.ui import display
|
||||
|
||||
class TestDisplay(unittest.TestCase):
|
||||
|
||||
def test_clear(self):
|
||||
display.clear()
|
||||
|
||||
def test_refresh(self):
|
||||
display.refresh()
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user