1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2024-11-22 15:38:11 +00:00

feat(core): send ButtonRequests for paging events

This commit is contained in:
matejcik 2021-06-17 13:57:03 +02:00 committed by matejcik
parent 69d1465e08
commit 1012ee8497
7 changed files with 68 additions and 27 deletions

View File

@ -45,7 +45,13 @@ message Failure {
* @next ButtonAck * @next ButtonAck
*/ */
message ButtonRequest { message ButtonRequest {
optional ButtonRequestType code = 1; optional ButtonRequestType code = 1; // enum identifier of the screen
optional uint32 pages = 2; // if the screen is paginated, number of pages
optional uint32 page_number = 3; // if the screen is paginated, current page (1-based)
/* Rationale: both fields are optional, and neither field can have 0 as a valid
value. So both testing `if pages` and `if page_number` do the right thing.
Also the following is always true: `page_is_last = (page_number == pages)` */
/** /**
* Type of button request * Type of button request
*/ */

View File

@ -0,0 +1 @@
ButtonRequest is sent also after every screen of a multi-page view.

View File

@ -31,7 +31,6 @@ async def confirm(
cancel_style: ButtonStyleType = Confirm.DEFAULT_CANCEL_STYLE, cancel_style: ButtonStyleType = Confirm.DEFAULT_CANCEL_STYLE,
major_confirm: bool = False, major_confirm: bool = False,
) -> bool: ) -> bool:
await button_request(ctx, code=code)
if content.__class__.__name__ == "Paginated": if content.__class__.__name__ == "Paginated":
# The following works because asserts are omitted in non-debug builds. # The following works because asserts are omitted in non-debug builds.
@ -46,13 +45,15 @@ async def confirm(
cancel_style, cancel_style,
major_confirm, major_confirm,
) )
dialog: ui.Layout = content result = await content.interact(ctx, code=code)
else: else:
await button_request(ctx, code=code)
dialog = Confirm( dialog = Confirm(
content, confirm, confirm_style, cancel, cancel_style, major_confirm content, confirm, confirm_style, cancel, cancel_style, major_confirm
) )
result = await ctx.wait(dialog)
return await ctx.wait(dialog) is CONFIRMED return result is CONFIRMED
async def info_confirm( async def info_confirm(
@ -92,21 +93,21 @@ async def hold_to_confirm(
loader_style: LoaderStyleType = HoldToConfirm.DEFAULT_LOADER_STYLE, loader_style: LoaderStyleType = HoldToConfirm.DEFAULT_LOADER_STYLE,
cancel: bool = True, cancel: bool = True,
) -> bool: ) -> bool:
await button_request(ctx, code=code)
if content.__class__.__name__ == "Paginated": if content.__class__.__name__ == "Paginated":
# The following works because asserts are omitted in non-debug builds. # The following works because asserts are omitted in non-debug builds.
# IOW if the assert runs, that means __debug__ is True and Paginated is imported # IOW if the assert runs, that means __debug__ is True and Paginated is imported
assert isinstance(content, Paginated) assert isinstance(content, Paginated)
content.pages[-1] = HoldToConfirm( content.pages[-1] = HoldToConfirm(
content.pages[-1], confirm, confirm_style, loader_style, cancel content.pages[-1], confirm, confirm_style, loader_style, cancel
) )
dialog: ui.Layout = content result = await content.interact(ctx, code=code)
else: else:
await button_request(ctx, code=code)
dialog = HoldToConfirm(content, confirm, confirm_style, loader_style, cancel) dialog = HoldToConfirm(content, confirm, confirm_style, loader_style, cancel)
result = await ctx.wait(dialog)
return await ctx.wait(dialog) is CONFIRMED return result is CONFIRMED
async def require_confirm(*args: Any, **kwargs: Any) -> None: async def require_confirm(*args: Any, **kwargs: Any) -> None:

View File

@ -282,11 +282,15 @@ if TYPE_CHECKING:
class ButtonRequest(protobuf.MessageType): class ButtonRequest(protobuf.MessageType):
code: ButtonRequestType | None code: ButtonRequestType | None
pages: int | None
page_number: int | None
def __init__( def __init__(
self, self,
*, *,
code: ButtonRequestType | None = None, code: ButtonRequestType | None = None,
pages: int | None = None,
page_number: int | None = None,
) -> None: ) -> None:
pass pass

View File

@ -1,6 +1,8 @@
from micropython import const from micropython import const
from trezor import loop, res, ui, utils from trezor import loop, res, ui, utils, wire, workflow
from trezor.enums import ButtonRequestType
from trezor.messages import ButtonAck, ButtonRequest
from .button import Button, ButtonCancel, ButtonConfirm, ButtonDefault from .button import Button, ButtonCancel, ButtonConfirm, ButtonDefault
from .confirm import CANCELLED, CONFIRMED, Confirm from .confirm import CANCELLED, CONFIRMED, Confirm
@ -9,6 +11,12 @@ from .text import TEXT_MAX_LINES, Span, Text
_PAGINATED_LINE_WIDTH = const(204) _PAGINATED_LINE_WIDTH = const(204)
WAS_PAGED = object()
if False:
from typing import Any
def render_scrollbar(pages: int, page: int) -> None: def render_scrollbar(pages: int, page: int) -> None:
BBOX = const(220) BBOX = const(220)
@ -46,20 +54,19 @@ def render_swipe_text() -> None:
class Paginated(ui.Layout): class Paginated(ui.Layout):
def __init__( def __init__(self, pages: list[ui.Component], page: int = 0):
self, pages: list[ui.Component], page: int = 0, one_by_one: bool = False
):
super().__init__() super().__init__()
self.pages = pages self.pages = pages
self.page = page self.page = page
self.one_by_one = one_by_one
def dispatch(self, event: int, x: int, y: int) -> None: def dispatch(self, event: int, x: int, y: int) -> None:
pages = self.pages pages = self.pages
page = self.page page = self.page
pages[page].dispatch(event, x, y) pages[page].dispatch(event, x, y)
if event is ui.RENDER: if event is ui.REPAINT:
self.repaint = True
elif event is ui.RENDER:
length = len(pages) length = len(pages)
if page < length - 1: if page < length - 1:
render_swipe_icon() render_swipe_icon()
@ -89,15 +96,24 @@ class Paginated(ui.Layout):
elif swipe is SWIPE_DOWN: elif swipe is SWIPE_DOWN:
self.page = max(self.page - 1, 0) self.page = max(self.page - 1, 0)
self.pages[self.page].dispatch(ui.REPAINT, 0, 0)
self.repaint = True
if __debug__:
from apps.debug import notify_layout_change
notify_layout_change(self)
self.on_change() self.on_change()
raise ui.Result(WAS_PAGED)
async def interact(
self,
ctx: wire.GenericContext,
code: ButtonRequestType = ButtonRequestType.Other,
) -> Any:
workflow.close_others()
result = WAS_PAGED
while result is WAS_PAGED:
br = ButtonRequest(
code=code, pages=len(self.pages), page_number=self.page + 1
)
await ctx.call(br, ButtonAck)
result = await self
return result
def create_tasks(self) -> tuple[loop.Task, ...]: def create_tasks(self) -> tuple[loop.Task, ...]:
tasks: tuple[loop.Task, ...] = ( tasks: tuple[loop.Task, ...] = (
@ -118,8 +134,7 @@ class Paginated(ui.Layout):
return tasks return tasks
def on_change(self) -> None: def on_change(self) -> None:
if self.one_by_one: pass
raise ui.Result(self.page)
if __debug__: if __debug__:

View File

@ -8,6 +8,10 @@ if False:
LayoutType = Awaitable[Any] LayoutType = Awaitable[Any]
if __debug__:
from ..components.tt.scroll import Paginated
async def interact( async def interact(
ctx: wire.GenericContext, ctx: wire.GenericContext,
layout: LayoutType, layout: LayoutType,
@ -15,6 +19,10 @@ async def interact(
brcode: ButtonRequestType = ButtonRequestType.Other, brcode: ButtonRequestType = ButtonRequestType.Other,
) -> Any: ) -> Any:
log.debug(__name__, "ButtonRequest.type={}".format(brtype)) log.debug(__name__, "ButtonRequest.type={}".format(brtype))
workflow.close_others() if layout.__class__.__name__ == "Paginated":
await ctx.call(ButtonRequest(code=brcode), ButtonAck) assert isinstance(layout, Paginated)
return await ctx.wait(layout) return await layout.interact(ctx, code=brcode)
else:
workflow.close_others()
await ctx.call(ButtonRequest(code=brcode), ButtonAck)
return await ctx.wait(layout)

View File

@ -700,14 +700,20 @@ class ButtonRequest(protobuf.MessageType):
MESSAGE_WIRE_TYPE = 26 MESSAGE_WIRE_TYPE = 26
FIELDS = { FIELDS = {
1: protobuf.Field("code", ButtonRequestType, repeated=False, required=False), 1: protobuf.Field("code", ButtonRequestType, repeated=False, required=False),
2: protobuf.Field("pages", "uint32", repeated=False, required=False),
3: protobuf.Field("page_number", "uint32", repeated=False, required=False),
} }
def __init__( def __init__(
self, self,
*, *,
code: Optional[ButtonRequestType] = None, code: Optional[ButtonRequestType] = None,
pages: Optional[int] = None,
page_number: Optional[int] = None,
) -> None: ) -> None:
self.code = code self.code = code
self.pages = pages
self.page_number = page_number
class ButtonAck(protobuf.MessageType): class ButtonAck(protobuf.MessageType):