pull/3695/merge
cepetr 3 weeks ago committed by GitHub
commit c1c05af1f9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -233,18 +233,25 @@ build_embed: build_boardloader build_bootloader build_firmware # build boardload
build_boardloader: ## build boardloader
$(SCONS) CFLAGS="$(CFLAGS)" PRODUCTION="$(PRODUCTION)" TREZOR_MODEL="$(TREZOR_MODEL)" \
CMAKELISTS="$(CMAKELISTS)" $(BOARDLOADER_BUILD_DIR)/boardloader.bin
CMAKELISTS="$(CMAKELISTS)" NEW_RENDERING="$(NEW_RENDERING)" $(BOARDLOADER_BUILD_DIR)/boardloader.bin
build_bootloader: ## build bootloader
$(SCONS) CFLAGS="$(CFLAGS)" PRODUCTION="$(PRODUCTION)" TREZOR_MODEL="$(TREZOR_MODEL)" \
CMAKELISTS="$(CMAKELISTS)" BOOTLOADER_QA="$(BOOTLOADER_QA)" BOOTLOADER_DEVEL="$(BOOTLOADER_DEVEL)" $(BOOTLOADER_BUILD_DIR)/bootloader.bin
CMAKELISTS="$(CMAKELISTS)" BOOTLOADER_QA="$(BOOTLOADER_QA)" BOOTLOADER_DEVEL="$(BOOTLOADER_DEVEL)" \
NEW_RENDERING="$(NEW_RENDERING)" $(BOOTLOADER_BUILD_DIR)/bootloader.bin
build_bootloader_ci: ## build CI device testing bootloader
$(SCONS) CFLAGS="$(CFLAGS)" PRODUCTION="$(PRODUCTION)" TREZOR_MODEL="$(TREZOR_MODEL)" \
CMAKELISTS="$(CMAKELISTS)" $(BOOTLOADER_CI_BUILD_DIR)/bootloader.bin
build_bootloader_emu: ## build the unix bootloader emulator
$(SCONS) CFLAGS="$(CFLAGS)" PRODUCTION="$(PRODUCTION)" TREZOR_MODEL="$(TREZOR_MODEL)" CMAKELISTS="$(CMAKELISTS)" $(BOOTLOADER_EMU_BUILD_DIR)/bootloader.elf
$(SCONS) CFLAGS="$(CFLAGS)" PRODUCTION="$(PRODUCTION)" TREZOR_MODEL="$(TREZOR_MODEL)" \
CMAKELISTS="$(CMAKELISTS)" NEW_RENDERING="$(NEW_RENDERING)" $(BOOTLOADER_EMU_BUILD_DIR)/bootloader.elf
build_bootloader_emu_debug: ## build the unix bootloader emulator
$(SCONS) CFLAGS="$(CFLAGS)" PRODUCTION="$(PRODUCTION)" TREZOR_MODEL="$(TREZOR_MODEL)" \
CMAKELISTS="$(CMAKELISTS)" NEW_RENDERING="$(NEW_RENDERING)" TREZOR_EMULATOR_DEBUGGABLE=1 \
$(BOOTLOADER_EMU_BUILD_DIR)/bootloader.elf
build_prodtest: ## build production test firmware
$(SCONS) CFLAGS="$(CFLAGS)" PRODUCTION="$(PRODUCTION)" TREZOR_MODEL="$(TREZOR_MODEL)" \
@ -252,7 +259,7 @@ build_prodtest: ## build production test firmware
build_reflash: ## build reflash firmware + reflash image
$(SCONS) CFLAGS="$(CFLAGS)" PRODUCTION="$(PRODUCTION)" TREZOR_MODEL="$(TREZOR_MODEL)" \
CMAKELISTS="$(CMAKELISTS)" $(REFLASH_BUILD_DIR)/reflash.bin
CMAKELISTS="$(CMAKELISTS)" NEW_RENDERING="$(NEW_RENDERING)" $(REFLASH_BUILD_DIR)/reflash.bin
dd if=build/boardloader/boardloader.bin of=$(REFLASH_BUILD_DIR)/sdimage.bin bs=1 seek=0
dd if=build/bootloader/bootloader.bin of=$(REFLASH_BUILD_DIR)/sdimage.bin bs=1 seek=49152
@ -261,24 +268,26 @@ build_firmware: templates build_cross ## build firmware with frozen modules
TREZOR_MODEL="$(TREZOR_MODEL)" CMAKELISTS="$(CMAKELISTS)" \
PYOPT="$(PYOPT)" BITCOIN_ONLY="$(BITCOIN_ONLY)" \
BOOTLOADER_QA="$(BOOTLOADER_QA)" BOOTLOADER_DEVEL="$(BOOTLOADER_DEVEL)" \
DISABLE_OPTIGA="$(DISABLE_OPTIGA)" \
DISABLE_OPTIGA="$(DISABLE_OPTIGA)" NEW_RENDERING="$(NEW_RENDERING)"\
$(FIRMWARE_BUILD_DIR)/firmware.bin
build_unix: templates ## build unix port
$(SCONS) CFLAGS="$(CFLAGS)" $(UNIX_BUILD_DIR)/trezor-emu-core $(UNIX_PORT_OPTS) \
TREZOR_MODEL="$(TREZOR_MODEL)" CMAKELISTS="$(CMAKELISTS)" \
PYOPT="0" BITCOIN_ONLY="$(BITCOIN_ONLY)" TREZOR_EMULATOR_ASAN="$(ADDRESS_SANITIZER)"
PYOPT="0" BITCOIN_ONLY="$(BITCOIN_ONLY)" TREZOR_EMULATOR_ASAN="$(ADDRESS_SANITIZER)" \
NEW_RENDERING="$(NEW_RENDERING)"
build_unix_frozen: templates build_cross ## build unix port with frozen modules
$(SCONS) CFLAGS="$(CFLAGS)" $(UNIX_BUILD_DIR)/trezor-emu-core $(UNIX_PORT_OPTS) \
TREZOR_MODEL="$(TREZOR_MODEL)" CMAKELISTS="$(CMAKELISTS)" \
PYOPT="$(PYOPT)" BITCOIN_ONLY="$(BITCOIN_ONLY)" TREZOR_EMULATOR_ASAN="$(ADDRESS_SANITIZER)" \
TREZOR_MEMPERF="$(TREZOR_MEMPERF)" TREZOR_EMULATOR_FROZEN=1
TREZOR_MEMPERF="$(TREZOR_MEMPERF)" TREZOR_EMULATOR_FROZEN=1 NEW_RENDERING="$(NEW_RENDERING)"
build_unix_debug: templates ## build unix port
$(SCONS) --max-drift=1 CFLAGS="$(CFLAGS)" $(UNIX_BUILD_DIR)/trezor-emu-core $(UNIX_PORT_OPTS) \
TREZOR_MODEL="$(TREZOR_MODEL)" CMAKELISTS="$(CMAKELISTS)" \
BITCOIN_ONLY="$(BITCOIN_ONLY)" TREZOR_EMULATOR_ASAN=1 TREZOR_EMULATOR_DEBUGGABLE=1
BITCOIN_ONLY="$(BITCOIN_ONLY)" TREZOR_EMULATOR_ASAN=1 TREZOR_EMULATOR_DEBUGGABLE=1 \
NEW_RENDERING="$(NEW_RENDERING)"
build_cross: ## build mpy-cross port
$(MAKE) -C vendor/micropython/mpy-cross $(CROSS_PORT_OPTS)

@ -5,6 +5,7 @@ import tools
TREZOR_MODEL = ARGUMENTS.get('TREZOR_MODEL', 'T')
CMAKELISTS = int(ARGUMENTS.get('CMAKELISTS', 0))
NEW_RENDERING = ARGUMENTS.get('NEW_RENDERING', '1') == '1'
if TREZOR_MODEL in ('1', ):
# skip boardloader build
@ -20,6 +21,9 @@ if TREZOR_MODEL in ('1', ):
FEATURES_WANTED = ["sd_card"]
if NEW_RENDERING:
FEATURES_WANTED.append("new_rendering")
CCFLAGS_MOD = ''
CPPPATH_MOD = []
CPPDEFINES_MOD = ["BOARDLOADER"]
@ -60,13 +64,27 @@ CPPPATH_MOD += [
SOURCE_MOD += [
'embed/lib/colors.c',
'embed/lib/display_utils.c',
'embed/lib/display.c',
'embed/lib/fonts/font_bitmap.c',
'embed/lib/fonts/fonts.c',
'embed/lib/gl_color.c',
'embed/lib/gl_bitblt_rgb565.c',
'embed/lib/gl_bitblt_rgba8888.c',
'embed/lib/gl_bitblt_mono8.c',
'embed/lib/image.c',
'embed/lib/mini_printf.c',
'embed/lib/terminal.c',
]
if NEW_RENDERING:
CPPDEFINES_MOD += ['NEW_RENDERING']
SOURCE_MOD += [
'embed/lib/gl_draw.c',
]
else:
SOURCE_MOD += [
'embed/lib/display_draw.c',
]
env = Environment(ENV=os.environ,
CFLAGS='%s -DPRODUCTION=%s' % (ARGUMENTS.get('CFLAGS', ''), ARGUMENTS.get('PRODUCTION', '0')),

@ -7,6 +7,7 @@ TREZOR_MODEL = ARGUMENTS.get('TREZOR_MODEL', 'T')
CMAKELISTS = int(ARGUMENTS.get('CMAKELISTS', 0))
BOOTLOADER_QA = ARGUMENTS.get('BOOTLOADER_QA', '0') == '1'
PRODUCTION = 0 if BOOTLOADER_QA else ARGUMENTS.get('PRODUCTION', '0') == '1'
NEW_RENDERING = ARGUMENTS.get('NEW_RENDERING', '1') == '1'
if TREZOR_MODEL in ('1', ):
# skip bootloader build
@ -22,6 +23,9 @@ if TREZOR_MODEL in ('1', ):
FEATURES_WANTED = ["input", "rgb_led", "consumption_mask", "usb", "optiga", "dma2d"]
if NEW_RENDERING:
FEATURES_WANTED.append("new_rendering")
CCFLAGS_MOD = ''
CPPPATH_MOD = []
CPPDEFINES_MOD = []
@ -90,9 +94,12 @@ SOURCE_MOD += [
'embed/lib/buffers.c',
'embed/lib/colors.c',
'embed/lib/display_utils.c',
'embed/lib/display.c',
'embed/lib/fonts/font_bitmap.c',
'embed/lib/fonts/fonts.c',
'embed/lib/gl_color.c',
'embed/lib/gl_bitblt_mono8.c',
'embed/lib/gl_bitblt_rgb565.c',
'embed/lib/gl_bitblt_rgba8888.c',
'embed/lib/image.c',
'embed/lib/mini_printf.c',
'embed/lib/terminal.c',
@ -102,6 +109,17 @@ SOURCE_MOD += [
'vendor/micropython/lib/uzlib/tinflate.c',
]
if NEW_RENDERING:
CPPDEFINES_MOD += ['NEW_RENDERING']
SOURCE_MOD += [
'embed/lib/gl_draw.c',
]
else:
SOURCE_MOD += [
'embed/lib/display_draw.c',
]
SOURCE_NANOPB = [
'vendor/nanopb/pb_common.c',
'vendor/nanopb/pb_decode.c',
@ -228,6 +246,9 @@ def cargo_build():
features.append("bootloader")
features.extend(FEATURES_AVAILABLE)
if NEW_RENDERING:
features.append('new_rendering')
cargo_opts = [
f'--target={env.get("ENV")["RUST_TARGET"]}',
f'--target-dir=../../build/bootloader/rust',

@ -5,6 +5,7 @@ import tools
TREZOR_MODEL = ARGUMENTS.get('TREZOR_MODEL', 'T')
CMAKELISTS = int(ARGUMENTS.get('CMAKELISTS', 0))
NEW_RENDERING = ARGUMENTS.get('NEW_RENDERING', '1') == '1'
if TREZOR_MODEL in ('1', 'DISC1', 'DISC2'):
# skip bootloader_ci build
@ -82,8 +83,8 @@ CPPPATH_MOD += [
SOURCE_MOD += [
'embed/extmod/modtrezorcrypto/rand.c',
'embed/lib/colors.c',
'embed/lib/display_draw.c',
'embed/lib/display_utils.c',
'embed/lib/display.c',
'embed/lib/fonts/font_bitmap.c',
'embed/lib/fonts/fonts.c',
'embed/lib/image.c',

@ -6,6 +6,7 @@ import boards
TREZOR_MODEL = ARGUMENTS.get('TREZOR_MODEL', 'T')
CMAKELISTS = int(ARGUMENTS.get('CMAKELISTS', 0))
NEW_RENDERING = ARGUMENTS.get('NEW_RENDERING', '1') == '1'
DMA2D = False
if TREZOR_MODEL in ('1', 'DISC1'):
@ -22,6 +23,9 @@ if TREZOR_MODEL in ('1', 'DISC1'):
FEATURES_WANTED = ["input", "rgb_led", "dma2d"]
if NEW_RENDERING:
FEATURES_WANTED.append("new_rendering")
CCFLAGS_MOD = ''
CPPPATH_MOD = []
CPPDEFINES_MOD = []
@ -87,10 +91,12 @@ SOURCE_MOD += [
'embed/lib/buffers.c',
'embed/lib/colors.c',
'embed/lib/display_utils.c',
'embed/lib/display.c',
'embed/lib/dma2d_emul.c',
'embed/lib/fonts/font_bitmap.c',
'embed/lib/fonts/fonts.c',
'embed/lib/gl_color.c',
'embed/lib/gl_bitblt_mono8.c',
'embed/lib/gl_bitblt_rgb565.c',
'embed/lib/gl_bitblt_rgba8888.c',
'embed/lib/image.c',
'embed/lib/terminal.c',
'embed/lib/touch.c',
@ -101,6 +107,23 @@ SOURCE_MOD += [
'vendor/trezor-storage/flash_area.c',
]
if NEW_RENDERING:
CPPDEFINES_MOD += ['NEW_RENDERING']
if TREZOR_MODEL in ('T',):
CPPDEFINES_MOD += ['DISPLAY_RGB565']
elif TREZOR_MODEL in ('R', '1',):
CPPDEFINES_MOD += ['XFRAMEBUFFER', 'DISPLAY_MONO']
elif TREZOR_MODEL in ('T3T1',):
CPPDEFINES_MOD += ['XFRAMEBUFFER', 'DISPLAY_RGB565']
SOURCE_MOD += [
'embed/lib/gl_draw.c',
]
else:
SOURCE_MOD += [
'embed/lib/display_draw.c',
'embed/lib/dma2d_emul.c',
]
if TREZOR_MODEL in ('1', ):
SOURCE_MOD += [
'embed/models/model_T1B1_layout.c',
@ -135,7 +158,6 @@ SOURCE_BOOTLOADER = [
SOURCE_TREZORHAL = [
'embed/trezorhal/unix/boot_args.c',
'embed/trezorhal/unix/display-unix.c',
'embed/trezorhal/unix/fault_handlers.c',
'embed/trezorhal/unix/flash.c',
'embed/trezorhal/unix/flash_otp.c',
@ -147,6 +169,17 @@ SOURCE_TREZORHAL = [
'embed/trezorhal/unix/secret.c',
]
if NEW_RENDERING:
SOURCE_TREZORHAL += [
'embed/trezorhal/unix/display_driver.c',
'embed/trezorhal/unix/dma2d_bitblt.c',
'embed/trezorhal/xdisplay_legacy.c',
]
else:
SOURCE_TREZORHAL += [
'embed/trezorhal/unix/display-unix.c',
]
if TREZOR_MODEL in ('R', 'T3T1'):
SOURCE_TREZORHAL += [
'embed/trezorhal/unix/optiga_hal.c',
@ -260,17 +293,18 @@ cmake_gen = env.Command(
#
RUST_TARGET = 'x86_64-unknown-linux-gnu'
RUST_PROFILE = 'release'
RUST_LIB = 'trezor_lib'
RUST_LIBDIR = f'build/bootloader_emu/rust/{RUST_TARGET}/{RUST_PROFILE}'
if ARGUMENTS.get('TREZOR_EMULATOR_DEBUGGABLE', '0') == '1':
RUST_PROFILE = 'dev'
RUST_LIBDIR = f'build/bootloader_emu/rust/{RUST_TARGET}/debug'
else:
RUST_PROFILE = 'release'
RUST_LIBDIR = f'build/bootloader_emu/rust/{RUST_TARGET}/release'
RUST_LIBPATH = f'{RUST_LIBDIR}/lib{RUST_LIB}.a'
def cargo_build():
# Determine the profile build flags.
if RUST_PROFILE == 'release':
profile = '--release'
else:
profile = ''
if TREZOR_MODEL in ("1",):
features = ["model_t1"]
elif TREZOR_MODEL in ("R",):
@ -280,6 +314,17 @@ def cargo_build():
else:
features = ["model_tt"]
if NEW_RENDERING:
features.append('new_rendering')
if TREZOR_MODEL in ('T',):
features.append('display_rgb565')
elif TREZOR_MODEL in ('R', '1',):
features.append('display_mono')
features.append('xframebuffer')
elif TREZOR_MODEL in ('T3T1',):
features.append('display_rgb565')
features.append('xframebuffer')
if TREZOR_MODEL in ('T', 'T3T1'):
features.append('touch')
features.append('backlight')
@ -299,7 +344,7 @@ def cargo_build():
'-Z build-std-features=panic_immediate_abort',
]
return f'cd embed/rust; cargo build {profile} ' + ' '.join(cargo_opts)
return f'cd embed/rust; cargo build --profile {RUST_PROFILE} ' + ' '.join(cargo_opts)
rust = env.Command(
target=RUST_LIBPATH,

@ -13,6 +13,7 @@ TREZOR_MODEL = ARGUMENTS.get('TREZOR_MODEL', 'T')
CMAKELISTS = int(ARGUMENTS.get('CMAKELISTS', 0))
PYOPT = ARGUMENTS.get('PYOPT', '1')
DISABLE_OPTIGA = ARGUMENTS.get('DISABLE_OPTIGA', '0') == '1'
NEW_RENDERING = ARGUMENTS.get('NEW_RENDERING', '1') == '1'
FEATURE_FLAGS = {
@ -25,6 +26,8 @@ FEATURE_FLAGS = {
FEATURES_WANTED = ["input", "sbu", "sd_card", "rgb_led", "dma2d", "consumption_mask", "usb" ,"optiga", "haptic"]
if DISABLE_OPTIGA and PYOPT == '0':
FEATURES_WANTED.remove("optiga")
if NEW_RENDERING:
FEATURES_WANTED.append("new_rendering")
CCFLAGS_MOD = ''
CPPPATH_MOD = []
@ -203,9 +206,12 @@ SOURCE_MOD += [
'embed/lib/buffers.c',
'embed/lib/colors.c',
'embed/lib/display_utils.c',
'embed/lib/display.c',
'embed/lib/fonts/font_bitmap.c',
'embed/lib/fonts/fonts.c',
'embed/lib/gl_color.c',
'embed/lib/gl_bitblt_rgb565.c',
'embed/lib/gl_bitblt_rgba8888.c',
'embed/lib/gl_bitblt_mono8.c',
'embed/lib/image.c',
'embed/lib/mini_printf.c',
'embed/lib/terminal.c',
@ -216,6 +222,17 @@ SOURCE_MOD += [
'vendor/micropython/lib/uzlib/tinflate.c',
]
if NEW_RENDERING:
CPPDEFINES_MOD += ['NEW_RENDERING']
SOURCE_MOD += [
'embed/lib/gl_draw.c',
]
else:
SOURCE_MOD += [
'embed/lib/display_draw.c',
]
CPPDEFINES_MOD += [
'TREZOR_UI2',
'TRANSLATIONS',
@ -744,8 +761,16 @@ def cargo_build():
features.append('universal_fw')
features.append('ui')
features.append('translations')
if NEW_RENDERING:
features.append('new_rendering')
if PYOPT == '0':
features.append('debug')
features.append('ui_debug')
if TREZOR_MODEL in ('T', 'T3T1', 'DISC1', 'DISC2'):
features.append('ui_blurring')
features.append('ui_jpeg_decoder')
features.extend(FEATURES_AVAILABLE)

@ -81,8 +81,8 @@ CPPPATH_MOD += [
SOURCE_MOD += [
'embed/lib/colors.c',
'embed/lib/display_draw.c',
'embed/lib/display_utils.c',
'embed/lib/display.c',
'embed/lib/fonts/font_bitmap.c',
'embed/lib/fonts/fonts.c',
'embed/lib/image.c',

@ -55,8 +55,8 @@ CPPPATH_MOD += [
]
SOURCE_MOD += [
'embed/lib/colors.c',
'embed/lib/display_draw.c',
'embed/lib/display_utils.c',
'embed/lib/display.c',
'embed/lib/fonts/font_bitmap.c',
'embed/lib/fonts/fonts.c',
'embed/lib/image.c',

@ -10,6 +10,7 @@ TREZOR_MODEL = ARGUMENTS.get('TREZOR_MODEL', 'T')
DMA2D = TREZOR_MODEL in ('T', 'T3T1')
OPTIGA = TREZOR_MODEL in ('R', 'T3T1')
CMAKELISTS = int(ARGUMENTS.get('CMAKELISTS', 0))
NEW_RENDERING = ARGUMENTS.get('NEW_RENDERING', '1') == '1'
if TREZOR_MODEL in ('DISC1', 'DISC2'):
# skip unix build
@ -205,9 +206,12 @@ SOURCE_MOD += [
'embed/lib/buffers.c',
'embed/lib/colors.c',
'embed/lib/display_utils.c',
'embed/lib/display.c',
'embed/lib/fonts/font_bitmap.c',
'embed/lib/fonts/fonts.c',
'embed/lib/gl_color.c',
'embed/lib/gl_bitblt_rgb565.c',
'embed/lib/gl_bitblt_rgba8888.c',
'embed/lib/gl_bitblt_mono8.c',
'embed/lib/image.c',
'embed/lib/terminal.c',
'embed/lib/translations.c',
@ -217,6 +221,16 @@ SOURCE_MOD += [
'vendor/micropython/lib/uzlib/tinflate.c',
]
if NEW_RENDERING:
SOURCE_MOD += [
'embed/lib/gl_draw.c',
]
else:
SOURCE_MOD += [
'embed/lib/display_draw.c',
]
if TREZOR_MODEL in ('1', ):
SOURCE_MOD += [
'embed/models/model_T1B1_layout.c',
@ -250,6 +264,17 @@ if FROZEN:
if RASPI:
CPPDEFINES_MOD += ['TREZOR_EMULATOR_RASPI']
if NEW_RENDERING:
CPPDEFINES_MOD += ['NEW_RENDERING']
if TREZOR_MODEL in ('T',):
CPPDEFINES_MOD += ['DISPLAY_RGB565']
elif TREZOR_MODEL in ('R', '1',):
CPPDEFINES_MOD += ['XFRAMEBUFFER', 'DISPLAY_MONO']
elif TREZOR_MODEL in ('T3T1',):
CPPDEFINES_MOD += ['XFRAMEBUFFER', 'DISPLAY_RGB565']
# modtrezorutils
SOURCE_MOD += [
'embed/extmod/modtrezorutils/modtrezorutils.c',
@ -394,7 +419,6 @@ SOURCE_MICROPYTHON = [
SOURCE_UNIX = [
'embed/trezorhal/unix/boot_args.c',
'embed/trezorhal/unix/common.c',
'embed/trezorhal/unix/display-unix.c',
'embed/trezorhal/unix/flash.c',
'embed/trezorhal/unix/flash_otp.c',
'embed/trezorhal/unix/random_delays.c',
@ -410,6 +434,20 @@ SOURCE_UNIX = [
'vendor/micropython/ports/unix/input.c',
'vendor/micropython/ports/unix/unix_mphal.c',
]
if NEW_RENDERING:
SOURCE_MOD += [
'embed/trezorhal/unix/display_driver.c',
'embed/trezorhal/unix/dma2d_bitblt.c',
'embed/trezorhal/xdisplay_legacy.c',
]
else:
SOURCE_MOD += [
'embed/trezorhal/unix/display-unix.c',
'embed/lib/dma2d_emul.c',
]
if TREZOR_MODEL in ('T', 'R', 'T3T1'):
SOURCE_UNIX += [
'embed/trezorhal/unix/sbu.c',
@ -424,9 +462,6 @@ if DMA2D:
CPPDEFINES_MOD += [
'USE_DMA2D',
]
SOURCE_UNIX += [
'embed/lib/dma2d_emul.c',
]
TRANSLATION_DATA = [
@ -831,6 +866,7 @@ def cargo_build():
features.append('universal_fw')
features.append('ui')
features.append('translations')
if PYOPT == '0':
features.append('debug')
if DMA2D:
@ -839,9 +875,22 @@ def cargo_build():
if TREZOR_MODEL in ('T', 'T3T1'):
features.append('touch')
features.append('sd_card')
features.append('ui_blurring')
features.append('ui_jpeg_decoder')
if TREZOR_MODEL in ('R', '1'):
features.append('button')
if NEW_RENDERING:
features.append('new_rendering')
if TREZOR_MODEL in ('T',):
features.append('display_rgb565')
elif TREZOR_MODEL in ('R', '1',):
features.append('display_mono')
features.append('xframebuffer')
elif TREZOR_MODEL in ('T3T1',):
features.append('display_rgb565')
features.append('xframebuffer')
env.get('ENV')['TREZOR_MODEL'] = TREZOR_MODEL
return f'cd embed/rust; cargo build --profile {RUST_PROFILE} --target-dir=../../build/unix/rust --no-default-features --features "{" ".join(features)}" --target {TARGET}'

@ -21,9 +21,11 @@
#include TREZOR_BOARD
#include "board_capabilities.h"
#include "buffers.h"
#include "common.h"
#include "compiler_traits.h"
#include "display.h"
#include "display_draw.h"
#include "fault_handlers.h"
#include "flash.h"
#include "image.h"

@ -22,8 +22,11 @@
#include TREZOR_BOARD
#include "bootui.h"
#include "colors.h"
#include "display.h"
#include "display_draw.h"
#include "display_utils.h"
#include "fonts/fonts.h"
#ifdef TREZOR_EMULATOR
#include "emulator.h"
#else
@ -69,13 +72,17 @@ static void format_ver(const char *format, uint32_t version, char *buffer,
// boot UI
#ifndef NEW_RENDERING
static uint16_t boot_background;
#endif
static bool initial_setup = true;
void ui_set_initial_setup(bool initial) { initial_setup = initial; }
void ui_screen_boot(const vendor_header *const vhdr,
const image_header *const hdr) {
#ifndef NEW_RENDERING
static void ui_screen_boot_old(const vendor_header *const vhdr,
const image_header *const hdr) {
const int show_string = ((vhdr->vtrust & VTRUST_STRING) == 0);
if ((vhdr->vtrust & VTRUST_RED) == 0) {
boot_background = COLOR_BL_FAIL;
@ -127,9 +134,11 @@ void ui_screen_boot(const vendor_header *const vhdr,
display_pixeldata_dirty();
display_refresh();
}
#endif
void ui_screen_boot_wait(int wait_seconds) {
char wait_str[16];
#ifndef NEW_RENDERING
static void ui_screen_boot_wait(int wait_seconds) {
char wait_str[32];
mini_snprintf(wait_str, sizeof(wait_str), "starting in %d s", wait_seconds);
display_bar(0, BOOT_WAIT_Y_TOP, DISPLAY_RESX, BOOT_WAIT_HEIGHT,
boot_background);
@ -138,6 +147,7 @@ void ui_screen_boot_wait(int wait_seconds) {
display_pixeldata_dirty();
display_refresh();
}
#endif
#if defined USE_TOUCH
#include "touch.h"
@ -179,7 +189,8 @@ void ui_click(void) {
#error "No input method defined"
#endif
void ui_screen_boot_click(void) {
#ifndef NEW_RENDERING
static void ui_screen_boot_click(void) {
display_bar(0, BOOT_WAIT_Y_TOP, DISPLAY_RESX, BOOT_WAIT_HEIGHT,
boot_background);
bld_continue_label(boot_background);
@ -187,6 +198,33 @@ void ui_screen_boot_click(void) {
display_refresh();
ui_click();
}
#endif
#ifdef NEW_RENDERING
void ui_screen_boot(const vendor_header *const vhdr,
const image_header *const hdr, int wait) {
bool show_string = ((vhdr->vtrust & VTRUST_STRING) == 0);
const char *vendor_str = show_string ? vhdr->vstr : NULL;
const size_t vendor_str_len = show_string ? vhdr->vstr_len : 0;
bool red_screen = ((vhdr->vtrust & VTRUST_RED) == 0);
uint32_t vimg_len = *(uint32_t *)(vhdr->vimg + 8);
screen_boot(red_screen, vendor_str, vendor_str_len, hdr->version, vhdr->vimg,
vimg_len, wait);
}
#else // NEW_RENDERING
void ui_screen_boot(const vendor_header *const vhdr,
const image_header *const hdr, int wait) {
if (wait == 0) {
ui_screen_boot_old(vhdr, hdr);
} else if (wait > 0) {
ui_screen_boot_wait(wait);
} else {
ui_screen_boot_click();
}
}
#endif
// welcome UI

@ -34,10 +34,22 @@ typedef enum {
SCREEN_WELCOME = 5,
} screen_t;
// Displays a warning screeen before jumping to the untrusted firmware
//
// Shows vendor image, vendor string and firmware version
// and optional message to the user (see `wait` argument)
//
// `wait` argument specifies a message to the user
// 0 do not show any message
// > 0 show a message like "starting in %d s"
// < 0 show a message like "press button to continue"
void ui_screen_boot(const vendor_header* const vhdr,
const image_header* const hdr);
void ui_screen_boot_wait(int wait_seconds);
void ui_screen_boot_click(void);
const image_header* const hdr, int wait);
// Waits until the user confirms the untrusted firmware
//
// Implementation is device specific - it wait's until
// the user presses a button, touches the display
void ui_click(void);
void ui_screen_welcome(void);

@ -110,6 +110,7 @@ __attribute__((noreturn)) void display_error_and_die(const char *message,
}
__attribute__((noreturn)) int main(int argc, char **argv) {
display_init();
flash_init();
flash_otp_init();
@ -187,10 +188,6 @@ __attribute__((noreturn)) int main(int argc, char **argv) {
jump_to(NULL);
}
void display_set_little_endian(void) {}
void display_reinit(void) {}
void mpu_config_bootloader(void) {}
void mpu_config_off(void) {}

@ -72,7 +72,6 @@
#include "emulator.h"
#else
#include "compiler_traits.h"
#include "mini_printf.h"
#include "mpu.h"
#include "platform.h"
#endif
@ -332,13 +331,13 @@ void real_jump_to_firmware(void) {
// if all VTRUST flags are unset = ultimate trust => skip the procedure
if ((vhdr.vtrust & VTRUST_ALL) != VTRUST_ALL) {
ui_fadeout();
ui_screen_boot(&vhdr, hdr);
ui_screen_boot(&vhdr, hdr, 0);
ui_fadein();
int delay = (vhdr.vtrust & VTRUST_WAIT) ^ VTRUST_WAIT;
if (delay > 1) {
while (delay > 0) {
ui_screen_boot_wait(delay);
ui_screen_boot(&vhdr, hdr, delay);
hal_delay(1000);
delay--;
}
@ -347,7 +346,8 @@ void real_jump_to_firmware(void) {
}
if ((vhdr.vtrust & VTRUST_CLICK) == 0) {
ui_screen_boot_click();
ui_screen_boot(&vhdr, hdr, -1);
ui_click();
}
ui_screen_boot_stage_1(false);

@ -67,6 +67,8 @@ SECTIONS {
.buf : ALIGN(4) {
*(.buf*);
. = ALIGN(4);
*(.no_dma_buffers*);
. = ALIGN(4);
} >SRAM
.boot_args : ALIGN(8) {

@ -78,6 +78,8 @@ SECTIONS {
.buf : ALIGN(4) {
*(.buf*);
. = ALIGN(4);
*(.no_dma_buffers*);
. = ALIGN(4);
} >SRAM1
.stack : ALIGN(8) {

@ -78,6 +78,8 @@ SECTIONS {
.buf : ALIGN(4) {
*(.buf*);
. = ALIGN(4);
*(.no_dma_buffers*);
. = ALIGN(4);
} >SRAM1
.stack : ALIGN(8) {

@ -21,6 +21,7 @@
#include "bootui.h"
#include "display.h"
#include "display_draw.h"
#include "display_utils.h"
#include "icon_done.h"
#include "icon_fail.h"

@ -22,6 +22,7 @@
#include "common.h"
#include "display.h"
#include "display_draw.h"
#include "flash.h"
#include "flash_otp.h"
#include "image.h"

@ -73,4 +73,10 @@ SECTIONS {
*(.boot_args*);
. = ALIGN(8);
} >BOOT_ARGS
.data_ccm : ALIGN(4) {
*(.no_dma_buffers*);
. = ALIGN(4);
} >CCMRAM
}

@ -18,6 +18,8 @@
*/
#include "display.h"
#include "display_draw.h"
#include "fonts/fonts.h"
/// class Display:
/// """
@ -128,11 +130,13 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_trezorui_Display_backlight_obj,
/// Saves current display contents to PNG file with given prefix.
/// """
STATIC mp_obj_t mod_trezorui_Display_save(mp_obj_t self, mp_obj_t prefix) {
#ifdef TREZOR_EMULATOR
mp_buffer_info_t pfx = {0};
mp_get_buffer_raise(prefix, &pfx, MP_BUFFER_READ);
if (pfx.len > 0) {
display_save(pfx.buf);
}
#endif
return mp_const_none;
}
STATIC MP_DEFINE_CONST_FUN_OBJ_2(mod_trezorui_Display_save_obj,
@ -143,7 +147,9 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_2(mod_trezorui_Display_save_obj,
/// Clears buffers in display saving.
/// """
STATIC mp_obj_t mod_trezorui_Display_clear_save(mp_obj_t self) {
#ifdef TREZOR_EMULATOR
display_clear_save();
#endif
return mp_const_none;
}
STATIC MP_DEFINE_CONST_FUN_OBJ_1(mod_trezorui_Display_clear_save_obj,

@ -89,6 +89,8 @@ SECTIONS {
.data_ccm : ALIGN(4) {
*(.no_dma_buffers*);
. = ALIGN(4);
*(.buf*);
. = ALIGN(4);
} >SRAM1
.heap : ALIGN(4) {

@ -87,6 +87,11 @@ SECTIONS {
. = ALIGN(4);
} >SRAM
.buf : ALIGN(4) {
*(.buf*);
. = ALIGN(4);
} >SRAM
.heap : ALIGN(4) {
. = 37K; /* this acts as a build time assertion that at least this much memory is available for heap use */
. = ABSOLUTE(sram_end); /* this explicitly sets the end of the heap */

@ -19,7 +19,7 @@
#define _GNU_SOURCE
#include "display.h"
#include "display_draw.h"
#include "buffers.h"
#include "common.h"
@ -34,7 +34,7 @@
#include "memzero.h"
#include "display_interface.h"
#include "display.h"
static struct { int x, y; } DISPLAY_OFFSET;
@ -61,8 +61,8 @@ void display_clear(void) {
// set MADCTL first so that we can set the window correctly next
display_orientation(0);
// address the complete frame memory
display_set_window(0, 0, MAX_DISPLAY_RESX - 1, MAX_DISPLAY_RESY - 1);
for (uint32_t i = 0; i < MAX_DISPLAY_RESX * MAX_DISPLAY_RESY; i++) {
display_set_window(0, 0, DISPLAY_RESX - 1, DISPLAY_RESY - 1);
for (uint32_t i = 0; i < DISPLAY_RESX * DISPLAY_RESY; i++) {
// 2 bytes per pixel because we're using RGB 5-6-5 format
PIXELDATA(0x0000);
}

@ -17,8 +17,8 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef __DISPLAY_H__
#define __DISPLAY_H__
#ifndef __DISPLAY_DRAW_H__
#define __DISPLAY_DRAW_H__
#include <stdbool.h>
#include <stddef.h>
@ -27,7 +27,6 @@
#include "buffers.h"
#include "colors.h"
#include TREZOR_BOARD
#include "display_interface.h"
#include "fonts/fonts.h"
// provided by common

@ -18,7 +18,7 @@
*/
#include "colors.h"
#include "display_interface.h"
#include "display.h"
typedef enum {
DMA2D_LAYER_FG = 1,

@ -0,0 +1,120 @@
/*
* This file is part of the Trezor project, https://trezor.io/
*
* Copyright (c) SatoshiLabs
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef GL_BITBLT_H
#define GL_BITBLT_H
#include <stdbool.h>
#include <stdint.h>
#include "gl_color.h"
// These module provides low-level bit block transfer (bitblt)
// operations on different bitmap/framebuffer types.
//
// `fill` - fills a rectangle with a solid color (with an optional
// alpha, allowing color blending).
//
// `copy` - copies a bitmap or part of it to the destination bitmap.
//
// `blend` - blends a bitmap with a 1- or 4-bit alpha channel to the
// destination using background and foreground colors.
//
// These operations might be accelerated using DMA2D (ChromART accelerator)
// on the STM32 platform.
// Represents a set of parameters for a bit block transfer operation.
typedef struct {
// Pointer to the destination bitmap's first row
void* dst_row;
// Number of bytes per line in the destination bitmap
uint16_t dst_stride;
// X-coordinate of the top-left corner inside the destination
uint16_t dst_x;
// Y-coordinate of the top-left corner inside the destination
uint16_t dst_y;
// Height of the filled/copied/blended area
uint16_t height;
// Width of the filled/copied/blended area
uint16_t width;
// Pointer to the source bitmap's first row
// (unused for fill operations)
void* src_row;
// Number of bytes per line in the source bitmap
// (unused for fill operations)
uint16_t src_stride;
// X-coordinate of the origin in the source bitmap
// (unused for fill operations)
uint16_t src_x;
// Y-coordinate of the origin in the source bitmap
// (unused for fill operations)
uint16_t src_y;
// Foreground color used when copying/blending/filling
gl_color_t src_fg;
// Background color used when copying mono bitmaps
gl_color_t src_bg;
// Alpha value for fill operation (255 => normal fill, 0 => noop)
uint8_t src_alpha;
} gl_bitblt_t;
// Functions for RGB565 bitmap/framebuffer
// Fills a rectangle with a solid color
void gl_rgb565_fill(const gl_bitblt_t* bb);
// Copies a mono bitmap (with 1-bit alpha channel)
void gl_rgb565_copy_mono1p(const gl_bitblt_t* bb);
// Copies a mono bitmap (with 4-bit alpha channel)
void gl_rgb565_copy_mono4(const gl_bitblt_t* bb);
// Copies an RGB565 bitmap
void gl_rgb565_copy_rgb565(const gl_bitblt_t* bb);
// Blends a mono bitmap (with 4-bit alpha channel)
// with the destination bitmap
void gl_rgb565_blend_mono4(const gl_bitblt_t* bb);
// Functions for RGBA8888 bitmap/framebuffer
void gl_rgba8888_fill(const gl_bitblt_t* bb);
// Copies a mono bitmap (with 1-bit alpha channel)
void gl_rgba8888_copy_mono1p(const gl_bitblt_t* bb);
// Copies a mono bitmap (with 4-bit alpha channel)
void gl_rgba8888_copy_mono4(const gl_bitblt_t* bb);
// Copies an RGB565 bitmap
void gl_rgba8888_copy_rgb565(const gl_bitblt_t* bb);
// Copies an RGBA8888 bitmap
void gl_rgba8888_copy_rgba8888(const gl_bitblt_t* bb);
// Blends a mono bitmap (with 4-bit alpha channel)
// with the destination bitmap
void gl_rgba8888_blend_mono4(const gl_bitblt_t* bb);
// Functions for Mono8 bitmap/framebuffer
void gl_mono8_fill(const gl_bitblt_t* bb);
// Copies a mono bitmap (with 1-bit alpha channel)
void gl_mono8_copy_mono1p(const gl_bitblt_t* bb);
// Copies a mono bitmap (with 4-bit alpha channel)
void gl_mono8_copy_mono4(const gl_bitblt_t* bb);
// Blends a mono bitmap (with 1-bit alpha channel)
// with the destination bitmap
void gl_mono8_blend_mono1p(const gl_bitblt_t* bb);
// Blends a mono bitmap (with 4-bit alpha channel)
// with the destination bitmap
void gl_mono8_blend_mono4(const gl_bitblt_t* bb);
#endif // GL_BITBLT_H

@ -0,0 +1,110 @@
/*
* This file is part of the Trezor project, https://trezor.io/
*
* Copyright (c) SatoshiLabs
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "gl_bitblt.h"
void gl_mono8_fill(const gl_bitblt_t* bb) {
uint8_t* dst_ptr = (uint8_t*)bb->dst_row + bb->dst_x;
uint16_t height = bb->height;
uint8_t fg = gl_color_lum(bb->src_fg);
while (height-- > 0) {
for (int x = 0; x < bb->width; x++) {
dst_ptr[x] = fg;
}
dst_ptr += bb->dst_stride / sizeof(*dst_ptr);
}
}
void gl_mono8_copy_mono1p(const gl_bitblt_t* bb) {
uint8_t* dst_ptr = (uint8_t*)bb->dst_row + bb->dst_x;
uint8_t* src = (uint8_t*)bb->src_row;
uint16_t src_ofs = bb->src_stride * bb->src_y + bb->src_x;
uint16_t height = bb->height;
uint8_t fg = gl_color_lum(bb->src_fg);
uint8_t bg = gl_color_lum(bb->src_bg);
while (height-- > 0) {
for (int x = 0; x < bb->width; x++) {
uint8_t mask = 1 << (7 - ((src_ofs + x) & 7));
uint8_t data = src[(src_ofs + x) / 8];
dst_ptr[x] = (data & mask) ? fg : bg;
}
dst_ptr += bb->dst_stride / sizeof(*dst_ptr);
src_ofs += bb->src_stride;
}
}
void gl_mono8_copy_mono4(const gl_bitblt_t* bb) {
uint8_t* dst_ptr = (uint8_t*)bb->dst_row + bb->dst_x;
uint8_t* src_row = (uint8_t*)bb->src_row;
uint16_t height = bb->height;
uint8_t fg = gl_color_lum(bb->src_fg);
uint8_t bg = gl_color_lum(bb->src_bg);
while (height-- > 0) {
for (int x = 0; x < bb->width; x++) {
uint8_t src_data = src_row[(x + bb->src_x) / 2];
uint8_t src_lum = (x + bb->src_x) & 1 ? src_data >> 4 : src_data & 0xF;
dst_ptr[x] = (fg * src_lum + bg * (15 - src_lum)) / 15;
}
dst_ptr += bb->dst_stride / sizeof(*dst_ptr);
src_row += bb->src_stride / sizeof(*src_row);
}
}
void gl_mono8_blend_mono1p(const gl_bitblt_t* bb) {
uint8_t* dst_ptr = (uint8_t*)bb->dst_row + bb->dst_x;
uint8_t* src = (uint8_t*)bb->src_row;
uint16_t src_ofs = bb->src_stride * bb->src_y + bb->src_x;
uint16_t height = bb->height;
uint8_t fg = gl_color_lum(bb->src_fg);
while (height-- > 0) {
for (int x = 0; x < bb->width; x++) {
uint8_t mask = 1 << (7 - ((src_ofs + x) & 7));
uint8_t data = src[(src_ofs + x) / 8];
dst_ptr[x] = (data & mask) ? fg : dst_ptr[x];
}
dst_ptr += bb->dst_stride / sizeof(*dst_ptr);
src_ofs += bb->src_stride;
}
}
void gl_mono8_blend_mono4(const gl_bitblt_t* bb) {
uint8_t* dst_ptr = (uint8_t*)bb->dst_row + bb->dst_x;
uint8_t* src_row = (uint8_t*)bb->src_row;
uint16_t height = bb->height;
uint8_t fg = gl_color_lum(bb->src_fg);
while (height-- > 0) {
for (int x = 0; x < bb->width; x++) {
uint8_t src_data = src_row[(x + bb->src_x) / 2];
uint8_t src_alpha = (x + bb->src_x) & 1 ? src_data >> 4 : src_data & 0x0F;
dst_ptr[x] = (fg * src_alpha + dst_ptr[x] * (15 - src_alpha)) / 15;
}
dst_ptr += bb->dst_stride / sizeof(*dst_ptr);
src_row += bb->src_stride / sizeof(*src_row);
}
}

@ -0,0 +1,136 @@
/*
* This file is part of the Trezor project, https://trezor.io/
*
* Copyright (c) SatoshiLabs
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "gl_bitblt.h"
#if USE_DMA2D
#include "dma2d_bitblt.h"
#endif
void gl_rgb565_fill(const gl_bitblt_t* bb) {
#if defined(USE_DMA2D) && !defined(TREZOR_EMULATOR)
if (!dma2d_rgb565_fill(bb))
#endif
{
uint16_t* dst_ptr = (uint16_t*)bb->dst_row + bb->dst_x;
uint16_t height = bb->height;
if (bb->src_alpha == 255) {
while (height-- > 0) {
for (int x = 0; x < bb->width; x++) {
dst_ptr[x] = bb->src_fg;
}
dst_ptr += bb->dst_stride / sizeof(*dst_ptr);
}
} else {
uint8_t alpha = bb->src_alpha;
while (height-- > 0) {
for (int x = 0; x < bb->width; x++) {
dst_ptr[x] = gl_color16_blend_a8(bb->src_fg, dst_ptr[x], alpha);
}
dst_ptr += bb->dst_stride / sizeof(*dst_ptr);
}
}
}
}
void gl_rgb565_copy_mono1p(const gl_bitblt_t* bb) {
uint16_t* dst_ptr = (uint16_t*)bb->dst_row + bb->dst_x;
uint8_t* src = (uint8_t*)bb->src_row;
uint16_t src_ofs = bb->src_stride * bb->src_y + bb->src_x;
uint16_t height = bb->height;
uint16_t fg = gl_color_to_color16(bb->src_fg);
uint16_t bg = gl_color_to_color16(bb->src_bg);
while (height-- > 0) {
for (int x = 0; x < bb->width; x++) {
uint8_t mask = 1 << (7 - ((src_ofs + x) & 7));
uint8_t data = src[(src_ofs + x) / 8];
dst_ptr[x] = (data & mask) ? fg : bg;
}
dst_ptr += bb->dst_stride / sizeof(*dst_ptr);
src_ofs += bb->src_stride;
}
}
void gl_rgb565_copy_mono4(const gl_bitblt_t* bb) {
#if defined(USE_DMA2D) && !defined(TREZOR_EMULATOR)
if (!dma2d_rgb565_copy_mono4(bb))
#endif
{
const gl_color16_t* gradient =
gl_color16_gradient_a4(bb->src_fg, bb->src_bg);
uint16_t* dst_ptr = (uint16_t*)bb->dst_row + bb->dst_x;
uint8_t* src_row = (uint8_t*)bb->src_row;
uint16_t height = bb->height;
while (height-- > 0) {
for (int x = 0; x < bb->width; x++) {
uint8_t fg_data = src_row[(x + bb->src_x) / 2];
uint8_t fg_lum = (x + bb->src_x) & 1 ? fg_data >> 4 : fg_data & 0xF;
dst_ptr[x] = gradient[fg_lum];
}
dst_ptr += bb->dst_stride / sizeof(*dst_ptr);
src_row += bb->src_stride / sizeof(*src_row);
}
}
}
void gl_rgb565_copy_rgb565(const gl_bitblt_t* bb) {
#if defined(USE_DMA2D) && !defined(TREZOR_EMULATOR)
if (!dma2d_rgb565_copy_rgb565(bb))
#endif
{
uint16_t* dst_ptr = (uint16_t*)bb->dst_row + bb->dst_x;
uint16_t* src_ptr = (uint16_t*)bb->src_row + bb->src_x;
uint16_t height = bb->height;
while (height-- > 0) {
for (int x = 0; x < bb->width; x++) {
dst_ptr[x] = src_ptr[x];
}
dst_ptr += bb->dst_stride / sizeof(*dst_ptr);
src_ptr += bb->src_stride / sizeof(*src_ptr);
}
}
}
void gl_rgb565_blend_mono4(const gl_bitblt_t* bb) {
#if defined(USE_DMA2D) && !defined(TREZOR_EMULATOR)
if (!dma2d_rgb565_blend_mono4(bb))
#endif
{
uint16_t* dst_ptr = (uint16_t*)bb->dst_row + bb->dst_x;
uint8_t* src_row = (uint8_t*)bb->src_row;
uint16_t height = bb->height;
while (height-- > 0) {
for (int x = 0; x < bb->width; x++) {
uint8_t fg_data = src_row[(x + bb->src_x) / 2];
uint8_t fg_alpha = (x + bb->src_x) & 1 ? fg_data >> 4 : fg_data & 0x0F;
dst_ptr[x] = gl_color16_blend_a4(
bb->src_fg, gl_color16_to_color(dst_ptr[x]), fg_alpha);
}
dst_ptr += bb->dst_stride / sizeof(*dst_ptr);
src_row += bb->src_stride / sizeof(*src_row);
}
}
}

@ -0,0 +1,156 @@
/*
* This file is part of the Trezor project, https://trezor.io/
*
* Copyright (c) SatoshiLabs
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "gl_bitblt.h"
#if USE_DMA2D
#include "dma2d_bitblt.h"
#endif
void gl_rgba8888_fill(const gl_bitblt_t* bb) {
#if defined(USE_DMA2D) && !defined(TREZOR_EMULATOR)
if (!dma2d_rgba8888_fill(bb))
#endif
{
uint32_t* dst_ptr = (uint32_t*)bb->dst_row + bb->dst_x;
uint16_t height = bb->height;
if (bb->src_alpha == 255) {
while (height-- > 0) {
for (int x = 0; x < bb->width; x++) {
dst_ptr[x] = gl_color_to_color32(bb->src_fg);
}
dst_ptr += bb->dst_stride / sizeof(*dst_ptr);
}
} else {
uint8_t alpha = bb->src_alpha;
while (height-- > 0) {
for (int x = 0; x < bb->width; x++) {
dst_ptr[x] = gl_color32_blend_a8(
bb->src_fg, gl_color32_to_color(dst_ptr[x]), alpha);
}
dst_ptr += bb->dst_stride / sizeof(*dst_ptr);
}
}
}
}
void gl_rgba8888_copy_mono1p(const gl_bitblt_t* bb) {
uint32_t* dst_ptr = (uint32_t*)bb->dst_row + bb->dst_x;
uint8_t* src = (uint8_t*)bb->src_row;
uint16_t src_ofs = bb->src_stride * bb->src_y + bb->src_x;
uint16_t height = bb->height;
uint32_t fg = gl_color_to_color32(bb->src_fg);
uint32_t bg = gl_color_to_color32(bb->src_bg);
while (height-- > 0) {
for (int x = 0; x < bb->width; x++) {
uint8_t mask = 1 << (7 - ((src_ofs + x) & 7));
uint8_t data = src[(src_ofs + x) / 8];
dst_ptr[x] = (data & mask) ? fg : bg;
}
dst_ptr += bb->dst_stride / sizeof(*dst_ptr);
src_ofs += bb->src_stride;
}
}
void gl_rgba8888_copy_mono4(const gl_bitblt_t* bb) {
#if defined(USE_DMA2D) && !defined(TREZOR_EMULATOR)
if (!dma2d_rgba8888_copy_mono4(bb))
#endif
{
const gl_color32_t* gradient =
gl_color32_gradient_a4(bb->src_fg, bb->src_bg);
uint32_t* dst_ptr = (uint32_t*)bb->dst_row + bb->dst_x;
uint8_t* src_row = (uint8_t*)bb->src_row;
uint16_t height = bb->height;
while (height-- > 0) {
for (int x = 0; x < bb->width; x++) {
uint8_t fg_data = src_row[(x + bb->src_x) / 2];
uint8_t fg_lum = (x + bb->src_x) & 1 ? fg_data >> 4 : fg_data & 0xF;
dst_ptr[x] = gradient[fg_lum];
}
dst_ptr += bb->dst_stride / sizeof(*dst_ptr);
src_row += bb->src_stride / sizeof(*src_row);
}
}
}
void gl_rgba8888_copy_rgb565(const gl_bitblt_t* bb) {
#if defined(USE_DMA2D) && !defined(TREZOR_EMULATOR)
if (!dma2d_rgba8888_copy_rgb565(bb))
#endif
{
uint32_t* dst_ptr = (uint32_t*)bb->dst_row + bb->dst_x;
uint16_t* src_ptr = (uint16_t*)bb->src_row + bb->src_x;
uint16_t height = bb->height;
while (height-- > 0) {
for (int x = 0; x < bb->width; x++) {
dst_ptr[x] = gl_color16_to_color32(src_ptr[x]);
}
dst_ptr += bb->dst_stride / sizeof(*dst_ptr);
src_ptr += bb->src_stride / sizeof(*src_ptr);
}
}
}
void gl_rgba8888_copy_rgba8888(const gl_bitblt_t* bb) {
#if defined(USE_DMA2D) && !defined(TREZOR_EMULATOR)
if (!dma2d_rgba8888_copy_rgba8888(bb))
#endif
{
uint32_t* dst_ptr = (uint32_t*)bb->dst_row + bb->dst_x;
uint32_t* src_ptr = (uint32_t*)bb->src_row + bb->src_x;
uint16_t height = bb->height;
while (height-- > 0) {
for (int x = 0; x < bb->width; x++) {
dst_ptr[x] = src_ptr[x];
}
dst_ptr += bb->dst_stride / sizeof(*dst_ptr);
src_ptr += bb->src_stride / sizeof(*src_ptr);
}
}
}
void gl_rgba8888_blend_mono4(const gl_bitblt_t* bb) {
#if defined(USE_DMA2D) && !defined(TREZOR_EMULATOR)
if (!dma2d_rgba8888_blend_mono4(bb))
#endif
{
uint32_t* dst_ptr = (uint32_t*)bb->dst_row + bb->dst_x;
uint8_t* src_row = (uint8_t*)bb->src_row;
uint16_t height = bb->height;
while (height-- > 0) {
for (int x = 0; x < bb->width; x++) {
uint8_t fg_data = src_row[(x + bb->src_x) / 2];
uint8_t fg_alpha = (x + bb->src_x) & 1 ? fg_data >> 4 : fg_data & 0x0F;
dst_ptr[x] = gl_color32_blend_a4(
bb->src_fg, gl_color32_to_color(dst_ptr[x]), fg_alpha);
}
dst_ptr += bb->dst_stride / sizeof(*dst_ptr);
src_row += bb->src_stride / sizeof(*src_row);
}
}
}

@ -0,0 +1,49 @@
/*
* This file is part of the Trezor project, https://trezor.io/
*
* Copyright (c) SatoshiLabs
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "gl_color.h"
#include "colors.h"
const gl_color16_t* gl_color16_gradient_a4(gl_color_t fg_color,
gl_color_t bg_color) {
static gl_color16_t cache[16] = {0};
if (gl_color_to_color16(bg_color) != cache[0] ||
gl_color_to_color16(fg_color) != cache[15]) {
for (int alpha = 0; alpha < 16; alpha++) {
cache[alpha] = gl_color16_blend_a4(fg_color, bg_color, alpha);
}
}
return cache;
}
const gl_color32_t* gl_color32_gradient_a4(gl_color_t fg_color,
gl_color_t bg_color) {
static gl_color32_t cache[16] = {0};
if (bg_color != gl_color32_to_color(cache[0]) ||
fg_color != gl_color32_to_color(cache[15])) {
for (int alpha = 0; alpha < 16; alpha++) {
cache[alpha] = gl_color32_blend_a4(fg_color, bg_color, alpha);
}
}
return cache;
}

@ -0,0 +1,337 @@
/*
* This file is part of the Trezor project, https://trezor.io/
*
* Copyright (c) SatoshiLabs
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef GL_COLOR_H
#define GL_COLOR_H
#include <stdint.h>
#define GL_COLOR_16BIT
// #define GL_COLOR_32BIT
// Color in RGB565 format
//
// |15 8 | 7 0|
// |---------------------------------|
// |r r r r r g g g | g g g b b b b b|
// |---------------------------------|
typedef uint16_t gl_color16_t;
// Color in RGBA8888 format
//
// |31 24 |23 16 |15 8 | 7 0 |
// |----------------------------------------------------------------------|
// |a a a a a a a a | r r r r r r r r | g g g g g g g g | b b b b b b b b |
// |----------------------------------------------------------------------|
//
typedef uint32_t gl_color32_t;
#ifdef GL_COLOR_16BIT
#define gl_color_t gl_color16_t
#define gl_color_to_color16(c) (c)
#define gl_color16_to_color(c) (c)
#define gl_color_to_color32(c) (gl_color16_to_color32(c))
#define gl_color32_to_color(c) (gl_color32_to_color16(c))
#define gl_color_lum(c) (gl_color16_lum(c))
#elif GL_COLOR_32BIT
#define gl_color_t gl_color32_t
#define gl_color_to_color16(c) (gl_color32_to_color16(c))
#define gl_color16_to_color(c) (gl_color16_to_color32(c))
#define gl_color_to_color32(c) (c)
#define gl_color32_to_color(c) (c)
#define gl_color_lum(c) (gl_color32_lum(c))
#else
#error "GL_COLOR_16BIT/32BIT not specified"
#endif
// Extracts red component from gl_color16_t and converts it to 8-bit value
#define gl_color16_to_r(c) ((((c)&0xF800) >> 8) | (((c)&0xF800) >> 13))
// Extracts green component from gl_color16_t and converts it to 8-bit value
#define gl_color16_to_g(c) ((((c)&0x07E0) >> 3) | (((c)&0x07E0) >> 9))
// Extracts blue component from gl_color16_t and converts it to 8-bit value
#define gl_color16_to_b(c) ((((c)&0x001F) << 3) | (((c)&0x001F) >> 2))
// Extracts red component from gl_color32_t
#define gl_color32_to_r(c) (((c)&0x00FF0000) >> 16)
// Extracts green component from gl_color32_t
#define gl_color32_to_g(c) (((c)&0x0000FF00) >> 8)
// Extracts blue component from gl_color32_t
#define gl_color32_to_b(c) (((c)&0x000000FF) >> 0)
// 4-bit linear interpolation between `fg` and `bg`
#define a4_lerp(fg, bg, alpha) (((fg) * (alpha) + ((bg) * (15 - (alpha)))) / 15)
// 8-bit linear interpolation between `fg` and `bg`
#define a8_lerp(fg, bg, alpha) \
(((fg) * (alpha) + ((bg) * (255 - (alpha)))) / 255)
// Constructs a 16-bit color from the given red (r),
// green (g), and blue (b) values in the range 0..255
static inline gl_color16_t gl_color16_rgb(uint8_t r, uint8_t g, uint8_t b) {
return ((r & 0xF8U) << 8) | ((g & 0xFCU) << 3) | ((b & 0xF8U) >> 3);
}
// Constructs a 32-bit color from the given red (r),
// green (g), and blue (b) values in the range 0..255.
// Alpha is set to 255.
static inline gl_color32_t gl_color32_rgb(uint8_t r, uint8_t g, uint8_t b) {
return (0xFFU << 24) | ((uint32_t)r << 16) | ((uint32_t)g << 8) | b;
}
// Converts a 16-bit color to a 32-bit color; alpha is set to 255
static inline gl_color32_t gl_color16_to_color32(gl_color16_t color) {
uint32_t r = gl_color16_to_r(color);
uint32_t g = gl_color16_to_g(color);
uint32_t b = gl_color16_to_b(color);
return gl_color32_rgb(r, g, b);
}
// Converts 32-bit color to 16-bit color, alpha is ignored
static inline gl_color16_t gl_color32_to_color16(gl_color32_t color) {
uint16_t r = (color & 0x00F80000) >> 8;
uint16_t g = (color & 0x0000FC00) >> 5;
uint16_t b = (color & 0x000000F8) >> 3;
return r | g | b;
}
// Converts 16-bit color into luminance (ranging from 0 to 255)
static inline uint8_t gl_color16_lum(gl_color16_t color) {
uint32_t r = gl_color16_to_r(color);
uint32_t g = gl_color16_to_g(color);
uint32_t b = gl_color16_to_b(color);
return (r + g + b) / 3;
}
// Converts 32-bit color into luminance (ranging from 0 to 255)
static inline uint8_t gl_color32_lum(gl_color16_t color) {
uint32_t r = gl_color32_to_r(color);
uint32_t g = gl_color32_to_g(color);
uint32_t b = gl_color32_to_b(color);
return (r + g + b) / 3;
}
#ifdef GL_COLOR_16BIT
// Blends foreground and background colors with 4-bit alpha
//
// Returns a color in 16-bit format
//
// If `alpha` is 0, the function returns the background color
// If `alpha` is 15, the function returns the foreground color
static inline gl_color16_t gl_color16_blend_a4(gl_color16_t fg, gl_color16_t bg,
uint8_t alpha) {
uint16_t fg_r = (fg & 0xF800) >> 11;
uint16_t bg_r = (bg & 0xF800) >> 11;
uint16_t r = a4_lerp(fg_r, bg_r, alpha);
uint16_t fg_g = (fg & 0x07E0) >> 5;
uint16_t bg_g = (bg & 0x07E0) >> 5;
uint16_t g = a4_lerp(fg_g, bg_g, alpha);
uint16_t fg_b = (fg & 0x001F) >> 0;
uint16_t bg_b = (bg & 0x001F) >> 0;
uint16_t b = a4_lerp(fg_b, bg_b, alpha);
return (r << 11) | (g << 5) | b;
}
// Blends foreground and background colors with 8-bit alpha
//
// Returns a color in 16-bit format
//
// If `alpha` is 0, the function returns the background color
// If `alpha` is 15, the function returns the foreground color
static inline gl_color16_t gl_color16_blend_a8(gl_color16_t fg, gl_color16_t bg,
uint8_t alpha) {
uint16_t fg_r = (fg & 0xF800) >> 11;
uint16_t bg_r = (bg & 0xF800) >> 11;
uint16_t r = a8_lerp(fg_r, bg_r, alpha);
uint16_t fg_g = (fg & 0x07E0) >> 5;
uint16_t bg_g = (bg & 0x07E0) >> 5;
uint16_t g = a8_lerp(fg_g, bg_g, alpha);
uint16_t fg_b = (fg & 0x001F) >> 0;
uint16_t bg_b = (bg & 0x001F) >> 0;
uint16_t b = a8_lerp(fg_b, bg_b, alpha);
return (r << 11) | (g << 5) | b;
}
// Blends foreground and background colors with 4-bit alpha
//
// Returns a color in 32-bit format
//
// If alpha is 0, the function returns the background color
// If alpha is 15, the function returns the foreground color
static inline gl_color32_t gl_color32_blend_a4(gl_color16_t fg, gl_color16_t bg,
uint8_t alpha) {
uint16_t fg_r = gl_color16_to_r(fg);
uint16_t bg_r = gl_color16_to_r(bg);
uint16_t r = a4_lerp(fg_r, bg_r, alpha);
uint16_t fg_g = gl_color16_to_g(fg);
uint16_t bg_g = gl_color16_to_g(bg);
uint16_t g = a4_lerp(fg_g, bg_g, alpha);
uint16_t fg_b = gl_color16_to_b(fg);
uint16_t bg_b = gl_color16_to_b(bg);
uint16_t b = a4_lerp(fg_b, bg_b, alpha);
return gl_color32_rgb(r, g, b);
}
// Blends foreground and background colors with 8-bit alpha
//
// Returns a color in 32-bit format
//
// If `alpha` is 0, the function returns the background color
// If `alpha` is 255, the function returns the foreground color
static inline gl_color32_t gl_color32_blend_a8(gl_color16_t fg, gl_color16_t bg,
uint8_t alpha) {
uint16_t fg_r = gl_color16_to_r(fg);
uint16_t bg_r = gl_color16_to_r(bg);
uint16_t r = a8_lerp(fg_r, bg_r, alpha);
uint16_t fg_g = gl_color16_to_g(fg);
uint16_t bg_g = gl_color16_to_g(bg);
uint16_t g = a8_lerp(fg_g, bg_g, alpha);
uint16_t fg_b = gl_color16_to_b(fg);
uint16_t bg_b = gl_color16_to_b(bg);
uint16_t b = a8_lerp(fg_b, bg_b, alpha);
return gl_color32_rgb(r, g, b);
}
#elif GL_COLOR_32BIT
// Blends foreground and background colors with 4-bit alpha
//
// Returns a color in 16-bit format
//
// If `alpha` is 0, the function returns the background color
// If `alpha` is 15, the function returns the foreground color
static inline gl_color16_t gl_color16_blend_a4(gl_color32_t fg, gl_color32_t bg,
uint8_t alpha) {
uint16_t fg_r = gl_color32_to_r(fg);
uint16_t bg_r = gl_color32_to_r(bg);
uint16_t r = a4_lerp(fg_r, bg_r, alpha);
uint16_t fg_g = gl_color32_to_g(fg);
uint16_t bg_g = gl_color32_to_g(bg);
uint16_t g = a4_lerp(fg_g, bg_g, alpha);
uint16_t fg_b = gl_color32_to_b(fg);
uint16_t bg_b = gl_color32_to_b(bg);
uint16_t b = a4_lerp(fg_b, bg_b, alpha);
return gl_color16_rgb(r, g, b);
}
// Blends foreground and background colors with 8-bit alpha
//
// Returns a color in 16-bit format
//
// If `alpha` is 0, the function returns the background color
// If `alpha` is 255, the function returns the foreground color
static inline gl_color16_t gl_color16_blend_a8(gl_color32_t fg, gl_color32_t bg,
uint8_t alpha) {
uint16_t fg_r = gl_color32_to_r(fg);
uint16_t bg_r = gl_color32_to_r(bg);
uint16_t r = a8_lerp(fg_r, bg_r, alpha);
uint16_t fg_g = gl_color32_to_g(fg);
uint16_t bg_g = gl_color32_to_g(bg);
uint16_t g = a8_lerp(fg_g, bg_g, alpha);
uint16_t fg_b = gl_color32_to_b(fg);
uint16_t bg_b = gl_color32_to_b(bg);
uint16_t b = g = a8_lerp(fg_b, bg_b, alpha);
return gl_color16_rgb(r, g, b);
}
// Blends foreground and background colors with 4-bit alpha
//
// Returns a color in 32-bit format
//
// If `alpha` is 0, the function returns the background color
// If `alpha` is 15, the function returns the foreground color
static inline gl_color32_t gl_color32_blend_a4(gl_color32_t fg, gl_color32_t bg,
uint8_t alpha) {
uint16_t fg_r = gl_color32_to_r(fg);
uint16_t bg_r = gl_color32_to_r(bg);
uint16_t r = a4_lerp(fg_r, bg_r, alpha);
uint16_t fg_g = gl_color32_to_g(fg);
uint16_t bg_g = gl_color32_to_g(bg);
uint16_t g = a4_lerp(fg_g, bg_g, alpha);
uint16_t fg_b = gl_color32_to_b(fg);
uint16_t bg_b = gl_color32_to_b(bg);
uint16_t b = a4_lerp(fg_b, bg_b, alpha);
return gl_color32_rgb(r, g, b);
}
// Blends foreground and background colors with 8-bit alpha
//
// Returns a color in 32-bit format
//
// If `alpha` is 0, the function returns the background color
// If `alpha` is 15, the function returns the foreground color
static inline gl_color32_t gl_color32_blend_a8(gl_color32_t fg, gl_color32_t bg,
uint8_t alpha) {
uint16_t fg_r = gl_color32_to_r(fg);
uint16_t bg_r = gl_color32_to_r(bg);
uint16_t r = a8_lerp(fg_r, bg_r, alpha);
uint16_t fg_g = gl_color32_to_g(fg);
uint16_t bg_g = gl_color32_to_g(bg);
uint16_t g = a8_lerp(fg_g, bg_g, alpha);
uint16_t fg_b = gl_color32_to_b(fg);
uint16_t bg_b = gl_color32_to_b(bg);
uint16_t b = a8_lerp(fg_b, bg_b, alpha);
return gl_color32_rgb(r, g, b);
}
#else
#error "GL_COLOR_16BIT/32BIT not specified"
#endif
// Returns a gradient as an array of 16 consecutive 16-bit colors
//
// Each element in the array represents a color, with `retval[0]` being
// the background (`bg`) color and `retval[15]` the foreground (`fg`) color
const gl_color16_t* gl_color16_gradient_a4(gl_color_t fg, gl_color_t bg);
// Returns a gradient as an array of 16 consecutive 32-bit colors
//
// Each element in the array represents a color, with `retval[0]` being
// the background (`bg`) color and `retval[15]` the foreground (`fg`) color
const gl_color32_t* gl_color32_gradient_a4(gl_color_t fg, gl_color_t bg);
#endif // TREZORHAL_GL_COLOR_H

@ -0,0 +1,275 @@
/*
* This file is part of the Trezor project, https://trezor.io/
*
* Copyright (c) SatoshiLabs
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <display.h>
#include "display_draw.h"
#include "fonts/fonts.h"
#include "gl_draw.h"
typedef struct {
int16_t dst_x;
int16_t dst_y;
int16_t src_x;
int16_t src_y;
int16_t width;
int16_t height;
} gl_clip_t;
static inline gl_clip_t gl_clip(gl_rect_t dst, const gl_bitmap_t* bitmap) {
int16_t dst_x = dst.x0;
int16_t dst_y = dst.y0;
int16_t src_x = 0;
int16_t src_y = 0;
if (bitmap != NULL) {
src_x += bitmap->offset.x;
src_y += bitmap->offset.y;
// Normalize negative x-offset of bitmap
if (src_x < 0) {
dst_x -= src_x;
src_x = 0;
}
// Normalize negative y-offset of src bitmap
if (src_y < 0) {
dst_y -= src_y;
src_y = 0;
}
}
// Normalize negative top-left of destination rectangle
if (dst_x < 0) {
src_x -= dst_x;
dst_x = 0;
}
if (dst_y < 0) {
src_y -= dst_y;
dst_y = 0;
}
// Calculate dimension of effective rectangle
int16_t width = MIN(DISPLAY_RESX, dst.x1) - dst_x;
int16_t height = MIN(DISPLAY_RESY, dst.y1) - dst_y;
if (bitmap != NULL) {
width = MIN(width, bitmap->size.x - src_x);
height = MIN(height, bitmap->size.y - src_y);
}
gl_clip_t clip = {
.dst_x = dst_x,
.dst_y = dst_y,
.src_x = src_x,
.src_y = src_y,
.width = width,
.height = height,
};
return clip;
}
void gl_clear(void) {
gl_bitblt_t bb = {
// Destination bitmap
.height = DISPLAY_RESX,
.width = DISPLAY_RESY,
.dst_row = NULL,
.dst_x = 0,
.dst_y = 0,
.dst_stride = 0,
// Source bitmap
.src_fg = 0,
.src_alpha = 255,
};
display_fill(&bb);
}
void gl_draw_bar(gl_rect_t rect, gl_color_t color) {
gl_clip_t clip = gl_clip(rect, NULL);
if (clip.width <= 0 || clip.height <= 0) {
return;
}
gl_bitblt_t bb = {
// Destination bitmap
.height = clip.height,
.width = clip.width,
.dst_row = NULL,
.dst_x = clip.dst_x,
.dst_y = clip.dst_y,
.dst_stride = 0,
// Source bitmap
.src_fg = color,
.src_alpha = 255,
};
display_fill(&bb);
}
void gl_draw_bitmap(gl_rect_t rect, const gl_bitmap_t* bitmap) {
gl_clip_t clip = gl_clip(rect, bitmap);
if (clip.width <= 0 || clip.height <= 0) {
return;
}
gl_bitblt_t bb = {
// Destination bitmap
.height = clip.height,
.width = clip.width,
.dst_row = NULL,
.dst_x = clip.dst_x,
.dst_y = clip.dst_y,
.dst_stride = 0,
// Source bitmap
.src_row = (uint8_t*)bitmap->ptr + bitmap->stride * clip.src_y,
.src_x = clip.src_x,
.src_y = clip.src_y,
.src_stride = bitmap->stride,
.src_fg = bitmap->fg_color,
.src_bg = bitmap->bg_color,
.src_alpha = 255,
};
// Currently, we use `gl_draw_bitmap` exclusively for text rendering.
// Therefore, we are including the variant of `display_copy_xxx()` that is
// specifically needed for drawing glyphs in the format we are using
// to save some space in the flash memory.
#if TREZOR_FONT_BPP == 1
if (bitmap->format == GL_FORMAT_MONO1P) {
display_copy_mono1p(&bb);
}
#endif
#if TREZOR_FONT_BPP == 4
if (bitmap->format == GL_FORMAT_MONO4) {
display_copy_mono4(&bb);
}
#endif
}
#if TREZOR_FONT_BPP == 1
#define GLYPH_FORMAT GL_FORMAT_MONO1P
#define GLYPH_STRIDE(w) (((w) + 7) / 8)
#elif TREZOR_FONT_BPP == 2
#error Unsupported TREZOR_FONT_BPP value
#define GLYPH_FORMAT GL_FORMAT_MONO2
#define GLYPH_STRIDE(w) (((w) + 3) / 4)
#elif TREZOR_FONT_BPP == 4
#define GLYPH_FORMAT GL_FORMAT_MONO4
#define GLYPH_STRIDE(w) (((w) + 1) / 2)
#elif TREZOR_FONT_BPP == 8
#error Unsupported TREZOR_FONT_BPP value
#define GLYPH_FORMAT GL_FORMAT_MONO8
#define GLYPH_STRIDE(w) (w)
#else
#error Unsupported TREZOR_FONT_BPP value
#endif
#define GLYPH_WIDTH(g) ((g)[0])
#define GLYPH_HEIGHT(g) ((g)[1])
#define GLYPH_ADVANCE(g) ((g)[2])
#define GLYPH_BEARING_X(g) ((g)[3])
#define GLYPH_BEARING_Y(g) ((g)[4])
#define GLYPH_DATA(g) ((void*)&(g)[5])
void gl_draw_text(gl_offset_t pos, const char* text, size_t maxlen,
const gl_text_attr_t* attr) {
if (text == NULL) {
return;
}
gl_bitmap_t bitmap = {
.format = GLYPH_FORMAT,
.fg_color = attr->fg_color,
.bg_color = attr->bg_color,
};
int max_height = font_max_height(attr->font);
int baseline = font_baseline(attr->font);
for (int i = 0; i < maxlen; i++) {
uint8_t ch = (uint8_t)text[i];
if (ch == 0 || pos.x >= DISPLAY_RESX) {
break;
}
const uint8_t* glyph = font_get_glyph(attr->font, ch);
if (glyph == NULL) {
continue;
}
bitmap.ptr = GLYPH_DATA(glyph);
bitmap.stride = GLYPH_STRIDE(GLYPH_WIDTH(glyph));
bitmap.size.x = GLYPH_WIDTH(glyph);
bitmap.size.y = GLYPH_HEIGHT(glyph);
bitmap.offset.x = -GLYPH_BEARING_X(glyph);
bitmap.offset.y = -(max_height - baseline - GLYPH_BEARING_Y(glyph));
gl_draw_bitmap(gl_rect(pos.x, pos.y, DISPLAY_RESX, DISPLAY_RESY), &bitmap);
pos.x += GLYPH_ADVANCE(glyph);
}
}
// ===============================================================
// emulation of legacy functions
void display_clear(void) { gl_clear(); }
void display_bar(int x, int y, int w, int h, uint16_t c) {
gl_draw_bar(gl_rect_wh(x, y, w, h), c);
}
void display_text(int x, int y, const char* text, int textlen, int font,
uint16_t fg_color, uint16_t bg_color) {
gl_text_attr_t attr = {
.font = font,
.fg_color = fg_color,
.bg_color = bg_color,
};
size_t maxlen = textlen < 0 ? UINT32_MAX : textlen;
gl_draw_text(gl_offset(x, y), text, maxlen, &attr);
}
void display_text_center(int x, int y, const char* text, int textlen, int font,
uint16_t fg_color, uint16_t bg_color) {
gl_text_attr_t attr = {
.font = font,
.fg_color = fg_color,
.bg_color = bg_color,
};
size_t maxlen = textlen < 0 ? UINT32_MAX : textlen;
int w = font_text_width(font, text, textlen);
gl_draw_text(gl_offset(x - w / 2, y), text, maxlen, &attr);
}

@ -0,0 +1,152 @@
/*
* This file is part of the Trezor project, https://trezor.io/
*
* Copyright (c) SatoshiLabs
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef GL_DRAW_H
#define GL_DRAW_H
#include "gl_color.h"
// 2D rectangle coordinates
//
// `x0`, `y0` - top-left coordinates
// `x1`, `y1` - bottom-right coordinates point (not included)
typedef struct {
int16_t x0;
int16_t y0;
int16_t x1;
int16_t y1;
} gl_rect_t;
// Builds a rectangle (`gl_rect_t`) from top-left coordinates and dimensions
static inline gl_rect_t gl_rect_wh(int16_t x, int16_t y, int16_t w, int16_t h) {
gl_rect_t rect = {
.x0 = x,
.y0 = y,
.x1 = x + w,
.y1 = y + h,
};
return rect;
}
// Builds a rectangle (`gl_rect_t`) from top-left and bottom-right coordinates
static inline gl_rect_t gl_rect(int16_t x0, int16_t y0, int16_t x1,
int16_t y1) {
gl_rect_t rect = {
.x0 = x0,
.y0 = y0,
.x1 = x1,
.y1 = y1,
};
return rect;
}
// 2D offset/ coordinates
typedef struct {
int16_t x;
int16_t y;
} gl_offset_t;
// Builds a `gl_offset_t` structure
static inline gl_offset_t gl_offset(int16_t x, int16_t y) {
gl_offset_t offset = {
.x = x,
.y = y,
};
return offset;
}
// 2D size in pixels
typedef struct {
int16_t x;
int16_t y;
} gl_size_t;
// Builds a `gl_size_t` structure
static inline gl_size_t gl_size(int16_t x, int16_t y) {
gl_size_t size = {
.x = x,
.y = y,
};
return size;
}
// Format of pixels in a bitmap
typedef enum {
GL_FORMAT_UNKNOWN, //
GL_FORMAT_MONO1P, // 1-bpp per pixel (packed)
GL_FORMAT_MONO4, // 4-bpp per pixel
GL_FORMAT_RGB565, // 16-bpp per pixel
GL_FORMAT_RGBA8888, // 32-bpp
} gl_format_t;
// 2D bitmap reference
typedef struct {
// pointer to top-left pixel
void* ptr;
// stride in bytes
size_t stride;
// size in pixels
gl_size_t size;
// format of pixels, GL_FORMAT_xxx
uint8_t format;
// offset used when bitmap is drawed using gl_draw_bitmap()
gl_offset_t offset;
// foreground color (used with MONOx formats)
gl_color_t fg_color;
// background color (used with MONOx formats)
gl_color_t bg_color;
} gl_bitmap_t;
// Text attributes (font and color)
typedef struct {
// Font identifier
int font;
// Foreground color
gl_color_t fg_color;
// Background color
gl_color_t bg_color;
} gl_text_attr_t;
// Fills a rectangle with a specified color.
void gl_draw_bar(gl_rect_t rect, gl_color_t color);
// Draws a bitmap into the specified rectangle.
//
// The destination rectangle may not be fully filled if the source bitmap
// is smaller than destination rectangle or if the bitmap is translated by
// an offset partially or completely outside the destination rectangle.
//
// Currently, we use `gl_draw_bitmap` exclusively for text rendering.
// Not all bitmap formats are supported now. Please see the implementation.
void gl_draw_bitmap(gl_rect_t rect, const gl_bitmap_t* bitmap);
// Draws a text to the specified position.
//
// `offset` - the most left point on the font baseline
// `text` - utf-8 text
// `maxlen` - maximum number of characters displayed (use SIZE_MAX when not
// specified) `attr` - font & text color
void gl_draw_text(gl_offset_t offset, const char* text, size_t maxlen,
const gl_text_attr_t* attr);
#endif // GL_DRAW_H

@ -24,7 +24,8 @@
#include "display.h"
#include TREZOR_BOARD
#ifndef TREZOR_PRINT_DISABLE
#include "fonts/fonts.h"
#include "gl_draw.h"
#define TERMINAL_COLS (DISPLAY_RESX / 6)
#define TERMINAL_ROWS (DISPLAY_RESY / 8)
@ -39,6 +40,62 @@ void term_set_color(uint16_t fgcolor, uint16_t bgcolor) {
terminal_bgcolor = bgcolor;
}
#ifdef NEW_RENDERING
// Font_Bitmap contains 96 (0x20 - 0x7F) 5x7 glyphs
// Each glyph consists of 5 bytes (each byte represents one column)
//
// This function converts the glyph into the format compatible
// with `display_copy_mono1p()` functions.
static uint64_t term_glyph_bits(char ch) {
union {
uint64_t u64;
uint8_t bytes[8];
} result = {0};
if (ch > ' ' && ch < 128) {
const uint8_t *b = &Font_Bitmap[(ch - ' ') * 5];
for (int y = 0; y < 7; y++) {
uint8_t mask = 1 << y;
result.bytes[y] |= ((b[0] & mask) ? 128 : 0) + ((b[1] & mask) ? 64 : 0) +
((b[2] & mask) ? 32 : 0) + ((b[3] & mask) ? 16 : 0) +
((b[4] & mask) ? 8 : 0);
}
}
return result.u64;
}
// Redraws specified rows to the display
static void term_redraw_rows(int start_row, int row_count) {
uint64_t glyph_bits = 0;
gl_bitblt_t bb = {
.height = 8,
.width = 6,
.dst_row = NULL,
.dst_x = 0,
.dst_y = 0,
.dst_stride = 0,
.src_row = &glyph_bits,
.src_x = 0,
.src_y = 0,
.src_stride = 8,
.src_fg = terminal_fgcolor,
.src_bg = terminal_bgcolor,
};
for (int y = start_row; y < start_row + row_count; y++) {
bb.dst_y = y * 8;
for (int x = 0; x < TERMINAL_COLS; x++) {
glyph_bits = term_glyph_bits(terminal_fb[y][x]);
bb.dst_x = x * 6;
display_copy_mono1p(&bb);
}
}
}
#endif // NEW_RENDERING
// display text using bitmap font
void term_print(const char *text, int textlen) {
static uint8_t row = 0, col = 0;
@ -77,6 +134,10 @@ void term_print(const char *text, int textlen) {
}
}
#ifdef NEW_RENDERING
term_redraw_rows(0, TERMINAL_ROWS);
display_refresh();
#else // NEW RENDERING
// render buffer to display
display_set_window(0, 0, DISPLAY_RESX - 1, DISPLAY_RESY - 1);
for (int i = 0; i < DISPLAY_RESX * DISPLAY_RESY; i++) {
@ -105,6 +166,7 @@ void term_print(const char *text, int textlen) {
}
display_pixeldata_dirty();
display_refresh();
#endif
}
#ifdef TREZOR_EMULATOR
@ -127,5 +189,3 @@ void term_printf(const char *fmt, ...) {
va_end(va);
}
}
#endif // TREZOR_PRINT_DISABLE

@ -22,11 +22,9 @@
#include "colors.h"
#ifndef TREZOR_PRINT_DISABLE
void term_set_color(uint16_t fgcolor, uint16_t bgcolor);
void term_print(const char *text, int textlen);
void term_printf(const char *fmt, ...)
__attribute__((__format__(__printf__, 1, 2)));
#endif
#endif // LIB_TERMINAL_H

@ -26,6 +26,7 @@
#include "button.h"
#include "common.h"
#include "display.h"
#include "display_draw.h"
#include "display_utils.h"
#include "fault_handlers.h"
#include "flash.h"

@ -25,6 +25,7 @@
#include "common.h"
#include "display.h"
#include "display_draw.h"
#include "flash.h"
#include "image.h"
#include "model.h"

@ -2,6 +2,12 @@
# It is not intended for manual editing.
version = 3
[[package]]
name = "alloc-traits"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6b2d54853319fd101b8dd81de382bcbf3e03410a64d8928bbee85a3e7dcde483"
[[package]]
name = "autocfg"
version = "1.1.0"
@ -279,6 +285,15 @@ version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
[[package]]
name = "static-alloc"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "570b7e840addf99f80c5b26abba410e21537002316fc82f2747fd87c171e9d7e"
dependencies = [
"alloc-traits",
]
[[package]]
name = "syn"
version = "1.0.80"
@ -309,8 +324,11 @@ dependencies = [
"qrcodegen",
"serde_json",
"spin",
"static-alloc",
"trezor-tjpgdec",
"ufmt",
"unsize",
"without-alloc",
"zeroize",
]
@ -347,6 +365,15 @@ version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
[[package]]
name = "unsize"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4fa7a7a734c1a5664a662ddcea0b6c9472a21da8888c957c7f1eaa09dba7a939"
dependencies = [
"autocfg",
]
[[package]]
name = "winapi"
version = "0.3.9"
@ -369,6 +396,16 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "without-alloc"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "375db0478b203b950ef10d1cce23cdbe5f30c2454fd9e7673ff56656df23adbb"
dependencies = [
"alloc-traits",
"unsize",
]
[[package]]
name = "zeroize"
version = "1.7.0"

@ -15,10 +15,18 @@ micropython = []
protobuf = ["micropython"]
ui = []
dma2d = []
xframebuffer = []
display_mono = []
display_rgb565 = ["ui_antialiasing"]
display_rgba8888 = ["ui_antialiasing"]
framebuffer = []
framebuffer32bit = []
ui_debug = []
ui_bounds = []
ui_antialiasing = []
ui_blurring = []
ui_jpeg_decoder = ["jpeg"]
new_rendering = []
bootloader = []
button = []
touch = []
@ -44,6 +52,8 @@ test = [
"micropython",
"protobuf",
"ui",
"ui_jpeg_decoder",
"ui_blurring",
"dma2d",
"touch",
"backlight",
@ -105,6 +115,15 @@ version = "0.2.6"
default-features = false
features = ["nightly"]
[dependencies.static-alloc]
version = "0.2.4"
[dependencies.without-alloc]
version = "0.2.2"
[dependencies.unsize]
version = "1.1.0"
# Build dependencies
[build-dependencies.bindgen]

@ -110,6 +110,16 @@ fn prepare_bindings() -> bindgen::Builder {
format!("-DTREZOR_BOARD=\"{}\"", board()).as_str(),
]);
#[cfg(feature = "xframebuffer")]
{
bindings = bindings.clang_args(&["-DXFRAMEBUFFER"]);
}
#[cfg(feature = "new_rendering")]
{
bindings = bindings.clang_args(["-DNEW_RENDERING"]);
}
// Pass in correct include paths and defines.
if is_firmware() {
let mut clang_args: Vec<&str> = Vec::new();
@ -338,6 +348,27 @@ fn generate_trezorhal_bindings() {
.allowlist_var("DISPLAY_FRAMEBUFFER_OFFSET_Y")
.allowlist_var("DISPLAY_RESX")
.allowlist_var("DISPLAY_RESY")
.allowlist_type("display_fb_info_t")
.allowlist_function("display_get_frame_buffer")
.allowlist_function("display_fill")
.allowlist_function("display_copy_rgb565")
// gl_bitblt
.allowlist_type("gl_bitblt_t")
.allowlist_function("gl_rgb565_fill")
.allowlist_function("gl_rgb565_copy_mono4")
.allowlist_function("gl_rgb565_copy_rgb565")
.allowlist_function("gl_rgb565_blend_mono4")
.allowlist_function("gl_rgba8888_fill")
.allowlist_function("gl_rgba8888_copy_mono4")
.allowlist_function("gl_rgba8888_copy_rgb565")
.allowlist_function("gl_rgba8888_copy_rgba8888")
.allowlist_function("gl_rgba8888_blend_mono4")
.allowlist_function("gl_mono8_fill")
.allowlist_function("gl_mono8_copy_mono1p")
.allowlist_function("gl_mono8_copy_mono4")
.allowlist_function("gl_mono8_blend_mono1p")
.allowlist_function("gl_mono8_blend_mono4")
.allowlist_function("dma2d_wait")
// fonts
.allowlist_function("font_height")
.allowlist_function("font_max_height")

@ -1,5 +1,6 @@
#include <stdbool.h>
#include "common.h"
#include "rust_ui_bootloader.h"
#include "rust_ui_common.h"

@ -24,3 +24,6 @@ void screen_boot_stage_1(bool fading);
uint32_t screen_unlock_bootloader_confirm(void);
void screen_unlock_bootloader_success(void);
void bld_continue_label(uint16_t bg_color);
void screen_boot(bool warning, const char* vendor_str, size_t vendor_str_len,
uint32_t version, const void* vendor_img,
size_t vendor_img_len, int wait);

@ -0,0 +1,216 @@
use super::ffi;
use crate::ui::{
display::Color,
geometry::Rect,
shape::{Bitmap, BitmapFormat, BitmapView},
};
pub type BitBlt = ffi::gl_bitblt_t;
impl Default for BitBlt {
fn default() -> Self {
Self {
width: 0,
height: 0,
dst_row: core::ptr::null_mut(),
dst_stride: 0,
dst_x: 0,
dst_y: 0,
src_row: core::ptr::null_mut(),
src_bg: 0,
src_fg: 0,
src_stride: 0,
src_x: 0,
src_y: 0,
src_alpha: 255,
}
}
}
impl BitBlt {
pub fn new_fill(r: Rect, clip: Rect, color: Color, alpha: u8) -> Option<Self> {
let r = r.clamp(clip);
if !r.is_empty() {
Some(
Self::default()
.with_rect(r)
.with_fg(color)
.with_alpha(alpha),
)
} else {
None
}
}
pub fn new_copy(r: Rect, clip: Rect, src: &BitmapView) -> Option<Self> {
let mut offset = src.offset;
let mut r_dst = r;
// Normalize negative x & y-offset of the bitmap
if offset.x < 0 {
r_dst.x0 -= offset.x;
offset.x = 0;
}
if offset.y < 0 {
r_dst.y0 -= offset.y;
offset.y = 0;
}
// Clip with the canvas viewport
let mut r = r_dst.clamp(clip);
// Clip with the bitmap top-left
if r.x0 > r_dst.x0 {
offset.x += r.x0 - r_dst.x0;
}
if r.y0 > r_dst.y0 {
offset.y += r.y0 - r_dst.y0;
}
// Clip with the bitmap size
r.x1 = core::cmp::min(r.x0 + src.size().x - offset.x, r.x1);
r.y1 = core::cmp::min(r.y0 + src.size().y - offset.y, r.y1);
if !r.is_empty() {
Some(
BitBlt::default()
.with_rect(r)
.with_src(src.bitmap, offset.x, offset.y)
.with_bg(src.bg_color)
.with_fg(src.fg_color),
)
} else {
None
}
}
pub fn with_dst(self, dst: &mut Bitmap) -> Self {
Self {
dst_row: unsafe { dst.row_ptr(self.dst_y) },
dst_stride: dst.stride() as u16,
..self
}
}
fn with_rect(self, r: Rect) -> Self {
Self {
width: r.width() as u16,
height: r.height() as u16,
dst_x: r.x0 as u16,
dst_y: r.y0 as u16,
..self
}
}
fn with_src(self, bitmap: &Bitmap, x: i16, y: i16) -> Self {
let bitmap_stride = match bitmap.format() {
BitmapFormat::MONO1P => bitmap.width() as u16, // packed bits
_ => bitmap.stride() as u16,
};
Self {
src_row: unsafe { bitmap.row_ptr(y as u16) },
src_stride: bitmap_stride,
src_x: x as u16,
src_y: y as u16,
..self
}
}
fn with_fg(self, fg_color: Color) -> Self {
Self {
src_fg: fg_color.into(),
..self
}
}
fn with_bg(self, bg_color: Color) -> Self {
Self {
src_bg: bg_color.into(),
..self
}
}
fn with_alpha(self, alpha: u8) -> Self {
Self {
src_alpha: alpha,
..self
}
}
pub fn wait_for_transfer() {
#[cfg(feature = "dma2d")]
unsafe {
ffi::dma2d_wait()
}
}
pub unsafe fn rgb565_fill(&self) {
unsafe { ffi::gl_rgb565_fill(self) };
}
pub unsafe fn rgb565_copy_mono4(&self) {
unsafe { ffi::gl_rgb565_copy_mono4(self) };
}
pub unsafe fn rgb565_copy_rgb565(&self) {
unsafe { ffi::gl_rgb565_copy_rgb565(self) };
}
pub unsafe fn rgb565_blend_mono4(&self) {
unsafe { ffi::gl_rgb565_blend_mono4(self) };
}
pub unsafe fn rgba8888_fill(&self) {
unsafe { ffi::gl_rgba8888_fill(self) };
}
pub unsafe fn rgba8888_copy_mono4(&self) {
unsafe { ffi::gl_rgba8888_copy_mono4(self) };
}
pub unsafe fn rgba8888_copy_rgb565(&self) {
unsafe { ffi::gl_rgba8888_copy_rgb565(self) };
}
pub unsafe fn rgba8888_copy_rgba8888(&self) {
unsafe { ffi::gl_rgba8888_copy_rgba8888(self) };
}
pub unsafe fn rgba8888_blend_mono4(&self) {
unsafe { ffi::gl_rgba8888_blend_mono4(self) };
}
pub unsafe fn mono8_fill(&self) {
unsafe { ffi::gl_mono8_fill(self) };
}
pub unsafe fn mono8_copy_mono1p(&self) {
unsafe { ffi::gl_mono8_copy_mono1p(self) };
}
pub unsafe fn mono8_copy_mono4(&self) {
unsafe { ffi::gl_mono8_copy_mono4(self) };
}
pub unsafe fn mono8_blend_mono1p(&self) {
unsafe { ffi::gl_mono8_blend_mono1p(self) };
}
pub unsafe fn mono8_blend_mono4(&self) {
unsafe { ffi::gl_mono8_blend_mono4(self) };
}
#[cfg(all(not(feature = "xframebuffer"), feature = "new_rendering"))]
pub unsafe fn display_fill(&self) {
unsafe { ffi::display_fill(self) };
}
#[cfg(all(not(feature = "xframebuffer"), feature = "new_rendering"))]
pub unsafe fn display_copy_rgb565(&self) {
unsafe { ffi::display_copy_rgb565(self) };
}
}

@ -13,11 +13,11 @@ pub use ffi::{
#[cfg(all(feature = "framebuffer", not(feature = "framebuffer32bit")))]
#[derive(Copy, Clone)]
pub struct FrameBuffer(*mut u16);
pub struct FrameBuffer(pub *mut u16);
#[cfg(all(feature = "framebuffer", feature = "framebuffer32bit"))]
#[derive(Copy, Clone)]
pub struct FrameBuffer(*mut u32);
pub struct FrameBuffer(pub *mut u32);
pub fn backlight(val: i32) -> i32 {
unsafe { ffi::display_backlight(val) }
@ -98,7 +98,9 @@ pub fn get_fb_addr() -> FrameBuffer {
#[inline(always)]
#[cfg(all(not(feature = "framebuffer"), feature = "disp_i8080_8bit_dw"))]
#[allow(unused_variables)]
pub fn pixeldata(c: u16) {
#[cfg(not(feature = "new_rendering"))]
unsafe {
ffi::DISPLAY_DATA_ADDRESS.write_volatile((c & 0xff) as u8);
ffi::DISPLAY_DATA_ADDRESS.write_volatile((c >> 8) as u8);
@ -178,3 +180,17 @@ pub fn clear() {
ffi::display_clear();
}
}
#[cfg(feature = "xframebuffer")]
pub fn get_frame_buffer() -> (&'static mut [u8], usize) {
let fb_info = unsafe { ffi::display_get_frame_buffer() };
let fb = unsafe {
core::slice::from_raw_parts_mut(
fb_info.ptr as *mut u8,
DISPLAY_RESY as usize * fb_info.stride,
)
};
(fb, fb_info.stride)
}

@ -2,6 +2,7 @@ pub mod bip39;
#[macro_use]
#[allow(unused_macros)]
pub mod fatal_error;
pub mod bitblt;
#[cfg(feature = "ui")]
pub mod display;
#[cfg(feature = "dma2d")]
@ -9,6 +10,7 @@ pub mod dma2d;
mod ffi;
#[cfg(feature = "haptic")]
pub mod haptic;
pub mod io;
pub mod model;
pub mod random;

@ -13,6 +13,7 @@ extern "C" fn screen_welcome() {
}
#[no_mangle]
#[cfg(not(feature = "new_rendering"))]
extern "C" fn bld_continue_label(bg_color: cty::uint16_t) {
ModelUI::bld_continue_label(bg_color.into());
}
@ -101,6 +102,24 @@ extern "C" fn screen_boot_stage_1(fading: bool) {
ModelUI::screen_boot_stage_1(fading)
}
#[no_mangle]
#[cfg(feature = "new_rendering")]
extern "C" fn screen_boot(
warning: bool,
vendor_str: *const cty::c_char,
vendor_str_len: usize,
version: u32,
vendor_img: *const cty::c_void,
vendor_img_len: usize,
wait: i32,
) {
let vendor_str = unsafe { from_c_array(vendor_str, vendor_str_len) };
let vendor_img =
unsafe { core::slice::from_raw_parts(vendor_img as *const u8, vendor_img_len) };
ModelUI::screen_boot(warning, vendor_str, version, vendor_img, wait);
}
#[no_mangle]
extern "C" fn screen_wipe_progress(progress: u16, initialize: bool) {
ModelUI::screen_wipe_progress(progress, initialize)

@ -1,11 +1,13 @@
//! Reexporting the `screens` module according to the
//! current feature (Trezor model)
use crate::ui::ui_features::{ModelUI, UIFeaturesCommon};
#[cfg(not(feature = "new_rendering"))]
use crate::ui::{
component::image::Image,
display::{Color, Icon},
geometry::{Alignment2D, Point},
ui_features::{ModelUI, UIFeaturesCommon},
};
use crate::ui::util::from_c_str;
@ -29,6 +31,7 @@ extern "C" fn screen_boot_stage_2() {
}
#[no_mangle]
#[cfg(not(feature = "new_rendering"))]
extern "C" fn display_icon(
x: cty::int16_t,
y: cty::int16_t,
@ -48,6 +51,7 @@ extern "C" fn display_icon(
}
#[no_mangle]
#[cfg(not(feature = "new_rendering"))]
extern "C" fn display_image(
x: cty::int16_t,
y: cty::int16_t,

@ -0,0 +1,62 @@
use crate::ui::{
component::{Component, Event, EventCtx, Never},
display,
display::Color,
geometry::Rect,
shape,
shape::Renderer,
};
pub struct Bar {
area: Rect,
color: Color,
bg_color: Color,
radius: i16,
}
impl Bar {
pub fn new(color: Color, bg_color: Color, radius: i16) -> Self {
Self {
area: Rect::zero(),
color,
bg_color,
radius,
}
}
}
impl Component for Bar {
type Msg = Never;
fn place(&mut self, bounds: Rect) -> Rect {
self.area = bounds;
self.area
}
fn event(&mut self, _ctx: &mut EventCtx, _event: Event) -> Option<Self::Msg> {
None
}
fn paint(&mut self) {
display::rect_fill_rounded(self.area, self.color, self.bg_color, self.radius as u8);
}
fn render<'s>(&'s self, target: &mut impl Renderer<'s>) {
shape::Bar::new(self.area)
.with_bg(self.color)
.with_radius(self.radius)
.render(target);
}
#[cfg(feature = "ui_bounds")]
fn bounds(&self, sink: &mut dyn FnMut(Rect)) {
sink(self.area)
}
}
#[cfg(feature = "ui_debug")]
impl crate::trace::Trace for Bar {
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
t.component("Bar");
}
}

@ -9,6 +9,7 @@ use crate::{
component::{maybe::PaintOverlapping, MsgMap},
display::{self, Color},
geometry::{Offset, Rect},
shape::Renderer,
},
};
@ -61,6 +62,8 @@ pub trait Component {
/// the `Child` wrapper.
fn paint(&mut self);
fn render<'s>(&'s self, _target: &mut impl Renderer<'s>);
#[cfg(feature = "ui_bounds")]
/// Report current paint bounds of this component. Used for debugging.
fn bounds(&self, _sink: &mut dyn FnMut(Rect)) {}
@ -154,6 +157,10 @@ where
}
}
fn render<'s>(&'s self, target: &mut impl Renderer<'s>) {
self.component.render(target);
}
#[cfg(feature = "ui_bounds")]
fn bounds(&self, sink: &mut dyn FnMut(Rect)) {
self.component.bounds(sink)
@ -254,6 +261,10 @@ where
self.inner.paint();
}
fn render<'s>(&'s self, target: &mut impl Renderer<'s>) {
self.inner.render(target);
}
#[cfg(feature = "ui_bounds")]
fn bounds(&self, sink: &mut dyn FnMut(Rect)) {
self.inner.bounds(sink)
@ -292,6 +303,11 @@ where
self.1.paint();
}
fn render<'s>(&'s self, target: &mut impl Renderer<'s>) {
self.0.render(target);
self.1.render(target);
}
#[cfg(feature = "ui_bounds")]
fn bounds(&self, sink: &mut dyn FnMut(Rect)) {
self.0.bounds(sink);
@ -341,6 +357,12 @@ where
self.2.paint();
}
fn render<'s>(&'s self, target: &mut impl Renderer<'s>) {
self.0.render(target);
self.1.render(target);
self.2.render(target);
}
#[cfg(feature = "ui_bounds")]
fn bounds(&self, sink: &mut dyn FnMut(Rect)) {
self.0.bounds(sink);
@ -368,6 +390,12 @@ where
}
}
fn render<'s>(&'s self, target: &mut impl Renderer<'s>) {
if let Some(ref c) = self {
c.render(target)
}
}
fn place(&mut self, bounds: Rect) -> Rect {
match self {
Some(ref mut c) => c.place(bounds),

@ -1,5 +1,8 @@
use super::{Component, Event, EventCtx};
use crate::ui::geometry::{Insets, Rect};
use crate::ui::{
geometry::{Insets, Rect},
shape::Renderer,
};
pub struct Border<T> {
border: Insets,
@ -39,6 +42,10 @@ where
self.inner.paint()
}
fn render<'s>(&'s self, target: &mut impl Renderer<'s>) {
self.inner.render(target);
}
#[cfg(feature = "ui_bounds")]
fn bounds(&self, sink: &mut dyn FnMut(Rect)) {
self.inner.bounds(sink);

@ -3,7 +3,8 @@ use crate::{
ui::{
component::{Component, Event, EventCtx, Never, Pad},
display::{self, Color, Font},
geometry::{Offset, Rect},
geometry::{Alignment, Offset, Rect},
shape::{self, Renderer},
},
};
@ -55,6 +56,20 @@ impl Component for Connect {
)
});
}
fn render<'s>(&'s self, target: &mut impl Renderer<'s>) {
let font = Font::NORMAL;
self.bg.render(target);
self.message.map(|t| {
shape::Text::new(self.bg.area.center() + Offset::y(font.text_height() / 2), t)
.with_fg(self.fg)
.with_font(font)
.with_align(Alignment::Center)
.render(target);
});
}
}
#[cfg(feature = "micropython")]

@ -1,5 +1,5 @@
use super::{Component, Event, EventCtx, Never};
use crate::ui::geometry::Rect;
use crate::ui::{geometry::Rect, shape::Renderer};
pub struct Empty;
@ -15,6 +15,8 @@ impl Component for Empty {
}
fn paint(&mut self) {}
fn render<'s>(&'s self, _target: &mut impl Renderer<'s>) {}
}
#[cfg(feature = "ui_debug")]

@ -6,6 +6,8 @@ use crate::ui::{
Color, Icon,
},
geometry::{Alignment2D, Offset, Point, Rect},
shape,
shape::Renderer,
};
#[derive(PartialEq, Eq, Clone, Copy)]
@ -48,6 +50,12 @@ impl Component for Image {
self.draw(self.area.center(), Alignment2D::CENTER);
}
fn render<'s>(&'s self, target: &mut impl Renderer<'s>) {
shape::ToifImage::new(self.area.center(), self.toif)
.with_align(Alignment2D::CENTER)
.render(target);
}
#[cfg(feature = "ui_bounds")]
fn bounds(&self, sink: &mut dyn FnMut(Rect)) {
sink(Rect::from_center_and_size(
@ -130,6 +138,15 @@ impl Component for BlendedImage {
self.paint_image();
}
fn render<'s>(&'s self, target: &mut impl Renderer<'s>) {
shape::ToifImage::new(self.bg_top_left, self.bg.toif)
.with_fg(self.bg_color)
.render(target);
shape::ToifImage::new(self.bg_top_left + self.fg_offset, self.fg.toif)
.with_fg(self.fg_color)
.render(target);
}
#[cfg(feature = "ui_bounds")]
fn bounds(&self, sink: &mut dyn FnMut(Rect)) {
sink(Rect::from_top_left_and_size(

@ -0,0 +1,98 @@
use crate::{
error::Error,
micropython::{buffer::get_buffer, obj::Obj},
};
use crate::ui::{
component::{Component, Event, EventCtx, Never},
display,
geometry::{Alignment2D, Offset, Rect},
shape,
shape::Renderer,
};
pub enum ImageBuffer {
Object { obj: Obj },
Slice { data: &'static [u8] },
}
impl ImageBuffer {
pub fn from_object(obj: Obj) -> Result<Self, Error> {
if !obj.is_bytes() {
return Err(Error::TypeError);
}
Ok(ImageBuffer::Object { obj })
}
pub fn from_slice(data: &'static [u8]) -> Self {
ImageBuffer::Slice { data }
}
pub fn is_empty(&self) -> bool {
self.data().is_empty()
}
pub fn data(&self) -> &[u8] {
match self {
// SAFETY: We expect no existing mutable reference. Resulting reference is
// discarded before returning to micropython.
ImageBuffer::Object { obj } => unsafe { unwrap!(get_buffer(*obj)) },
ImageBuffer::Slice { data } => data,
}
}
}
pub struct Jpeg {
area: Rect,
data: ImageBuffer,
scale: u8,
}
impl Jpeg {
pub fn new(data: ImageBuffer, scale: u8) -> Self {
Self {
area: Rect::zero(),
data,
scale,
}
}
}
impl Component for Jpeg {
type Msg = Never;
fn place(&mut self, bounds: Rect) -> Rect {
self.area = bounds;
self.area
}
fn event(&mut self, _ctx: &mut EventCtx, _event: Event) -> Option<Self::Msg> {
None
}
fn paint(&mut self) {
if let Some((size, _)) = display::tjpgd::jpeg_info(self.data.data()) {
let off = Offset::new(size.x / (2 << self.scale), size.y / (2 << self.scale));
display::tjpgd::jpeg(self.data.data(), self.area.center() - off, self.scale);
}
}
fn render<'s>(&'s self, target: &mut impl Renderer<'s>) {
shape::JpegImage::new(self.area.center(), self.data.data())
.with_align(Alignment2D::CENTER)
.with_scale(self.scale)
.render(target);
}
#[cfg(feature = "ui_bounds")]
fn bounds(&self, sink: &mut dyn FnMut(Rect)) {
sink(self.area)
}
}
#[cfg(feature = "ui_debug")]
impl crate::trace::Trace for Jpeg {
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
t.component("Jpeg");
}
}

@ -4,6 +4,7 @@ use crate::{
component::{Component, Event, EventCtx, Never},
display::Font,
geometry::{Alignment, Insets, Offset, Point, Rect},
shape::Renderer,
},
};
@ -114,6 +115,10 @@ impl Component for Label<'_> {
self.text.map(|c| self.layout.render_text(c));
}
fn render<'s>(&'s self, target: &mut impl Renderer<'s>) {
self.text.map(|c| self.layout.render_text2(c, target));
}
#[cfg(feature = "ui_bounds")]
fn bounds(&self, sink: &mut dyn FnMut(Rect)) {
sink(self.layout.bounds)

@ -1,5 +1,5 @@
use super::{Component, Event, EventCtx};
use crate::ui::geometry::Rect;
use crate::ui::{geometry::Rect, shape::Renderer};
pub struct MsgMap<T, F> {
inner: T,
@ -31,6 +31,10 @@ where
self.inner.paint()
}
fn render<'s>(&'s self, target: &mut impl Renderer<'s>) {
self.inner.render(target);
}
#[cfg(feature = "ui_bounds")]
fn bounds(&self, sink: &mut dyn FnMut(Rect)) {
self.inner.bounds(sink);

@ -4,9 +4,9 @@ use crate::{
ui::{
animation::Animation,
component::{Component, Event, EventCtx, Never, TimerToken},
display,
display::{Color, Font},
geometry::Rect,
display::{self, Color, Font},
geometry::{Offset, Rect},
shape::{self, Renderer},
util::animation_disabled,
},
};
@ -123,6 +123,19 @@ impl Marquee {
self.text
.map(|t| display::marquee(self.area, t, offset, self.font, self.fg, self.bg));
}
pub fn render_anim<'s>(&'s self, target: &mut impl Renderer<'s>, offset: i16) {
target.in_window(self.area, &|target| {
let text_height = self.font.text_height();
let pos = self.area.top_left() + Offset::new(offset, text_height - 1);
self.text.map(|t| {
shape::Text::new(pos, t)
.with_font(self.font)
.with_fg(self.fg)
.render(target);
});
});
}
}
impl Component for Marquee {
@ -214,6 +227,30 @@ impl Component for Marquee {
}
}
}
fn render<'s>(&'s self, target: &mut impl Renderer<'s>) {
let now = Instant::now();
match self.state {
State::Initial => {
self.render_anim(target, 0);
}
State::PauseRight => {
self.render_anim(target, self.min_offset);
}
State::PauseLeft => {
self.render_anim(target, self.max_offset);
}
_ => {
let progress = self.progress(now);
if let Some(done) = progress {
self.render_anim(target, done);
} else {
self.render_anim(target, 0);
}
}
}
}
}
#[cfg(feature = "ui_debug")]

@ -2,6 +2,7 @@ use crate::ui::{
component::{Component, ComponentExt, Event, EventCtx, Pad},
display::{self, Color},
geometry::Rect,
shape::Renderer,
};
pub struct Maybe<T> {
@ -94,6 +95,13 @@ where
}
}
fn render<'s>(&'s self, target: &mut impl Renderer<'s>) {
self.pad.render(target);
if self.visible {
self.inner.render(target);
}
}
#[cfg(feature = "ui_bounds")]
fn bounds(&self, sink: &mut dyn FnMut(Rect)) {
sink(self.pad.area);

@ -1,32 +1,36 @@
#![forbid(unsafe_code)]
//#![forbid(unsafe_code)]
pub mod bar;
pub mod base;
pub mod border;
pub mod connect;
pub mod empty;
pub mod image;
#[cfg(all(feature = "jpeg", feature = "micropython"))]
pub mod jpeg;
pub mod label;
pub mod map;
pub mod marquee;
pub mod maybe;
pub mod pad;
pub mod paginated;
pub mod painter;
pub mod placed;
pub mod qr_code;
pub mod text;
pub mod timeout;
pub use bar::Bar;
pub use base::{Child, Component, ComponentExt, Event, EventCtx, Never, Root, TimerToken};
pub use border::Border;
pub use empty::Empty;
#[cfg(all(feature = "jpeg", feature = "micropython"))]
pub use jpeg::Jpeg;
pub use label::Label;
pub use map::MsgMap;
pub use marquee::Marquee;
pub use maybe::Maybe;
pub use pad::Pad;
pub use paginated::{PageMsg, Paginate};
pub use painter::Painter;
pub use placed::{FixedHeightBar, Floating, GridPlaced, Split};
pub use qr_code::Qr;
pub use text::{

@ -1,6 +1,8 @@
use crate::ui::{
display::{self, Color},
geometry::Rect,
shape,
shape::Renderer,
};
pub struct Pad {
@ -52,4 +54,10 @@ impl Pad {
display::rect_fill(self.area, self.color);
}
}
pub fn render<'s>(&'s self, target: &mut impl Renderer<'s>) {
shape::Bar::new(self.area)
.with_bg(self.color)
.render(target);
}
}

@ -1,74 +0,0 @@
#[cfg(feature = "jpeg")]
use crate::ui::geometry::Offset;
use crate::ui::{
component::{image::Image, Component, Event, EventCtx, Never},
display,
geometry::{Alignment2D, Rect},
};
pub struct Painter<F> {
area: Rect,
func: F,
}
impl<F> Painter<F> {
pub fn new(func: F) -> Self {
Self {
func,
area: Rect::zero(),
}
}
}
impl<F> Component for Painter<F>
where
F: FnMut(Rect),
{
type Msg = Never;
fn place(&mut self, bounds: Rect) -> Rect {
self.area = bounds;
self.area
}
fn event(&mut self, _ctx: &mut EventCtx, _event: Event) -> Option<Self::Msg> {
None
}
fn paint(&mut self) {
(self.func)(self.area);
}
#[cfg(feature = "ui_bounds")]
fn bounds(&self, sink: &mut dyn FnMut(Rect)) {
sink(self.area)
}
}
#[cfg(feature = "ui_debug")]
impl<F> crate::trace::Trace for Painter<F> {
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
t.component("Painter");
}
}
pub fn image_painter(image: Image) -> Painter<impl FnMut(Rect)> {
let f = move |area: Rect| image.draw(area.center(), Alignment2D::CENTER);
Painter::new(f)
}
#[cfg(feature = "jpeg")]
pub fn jpeg_painter<'a>(
image: impl Fn() -> &'a [u8],
size: Offset,
scale: u8,
) -> Painter<impl FnMut(Rect)> {
let off = Offset::new(size.x / (2 << scale), size.y / (2 << scale));
let f = move |area: Rect| display::tjpgd::jpeg(image(), area.center() - off, scale);
Painter::new(f)
}
pub fn rect_painter(fg: display::Color, bg: display::Color) -> Painter<impl FnMut(Rect)> {
let f = move |area: Rect| display::rect_fill_rounded(area, fg, bg, 2);
Painter::new(f)
}

@ -1,6 +1,7 @@
use crate::ui::{
component::{Component, Event, EventCtx},
geometry::{Alignment, Alignment2D, Axis, Grid, GridCellSpan, Insets, Offset, Rect},
shape::Renderer,
};
pub struct GridPlaced<T> {
@ -63,6 +64,10 @@ where
fn paint(&mut self) {
self.inner.paint()
}
fn render<'s>(&'s self, target: &mut impl Renderer<'s>) {
self.inner.render(target);
}
}
#[cfg(feature = "ui_debug")]
@ -106,6 +111,10 @@ where
fn paint(&mut self) {
self.inner.paint()
}
fn render<'s>(&'s self, target: &mut impl Renderer<'s>) {
self.inner.render(target);
}
}
#[cfg(feature = "ui_debug")]
@ -178,6 +187,10 @@ where
fn paint(&mut self) {
self.inner.paint()
}
fn render<'s>(&'s self, target: &mut impl Renderer<'s>) {
self.inner.render(target);
}
}
#[cfg(feature = "ui_debug")]
@ -269,6 +282,11 @@ where
self.first.paint();
self.second.paint();
}
fn render<'s>(&'s self, target: &mut impl Renderer<'s>) {
self.first.render(target);
self.second.render(target);
}
}
#[cfg(feature = "ui_debug")]

@ -8,6 +8,8 @@ use crate::{
constant,
display::{pixeldata, pixeldata_dirty, rect_fill_rounded, set_window, Color},
geometry::{Insets, Offset, Rect},
shape,
shape::Renderer,
},
};
@ -141,6 +143,39 @@ impl Component for Qr {
Self::draw(&qr, area, self.border, scale);
}
fn render<'s>(&'s self, target: &mut impl Renderer<'s>) {
let mut outbuffer = [0u8; QR_MAX_VERSION.buffer_len()];
let mut tempbuffer = [0u8; QR_MAX_VERSION.buffer_len()];
let qr = QrCode::encode_text(
self.text.as_ref(),
&mut tempbuffer,
&mut outbuffer,
QrCodeEcc::Medium,
Version::MIN,
QR_MAX_VERSION,
None,
true,
);
let qr = unwrap!(qr);
let scale = (self.area.width().min(self.area.height()) - self.border) / (qr.size() as i16);
let side = scale * qr.size() as i16;
let qr_area = Rect::from_center_and_size(self.area.center(), Offset::uniform(side));
if self.border > 0 {
shape::Bar::new(qr_area.expand(self.border))
.with_bg(LIGHT)
.with_radius(CORNER_RADIUS as i16 + 1) // !@# + 1 to fix difference on TR
.render(target);
}
shape::QrImage::new(qr_area, &qr)
.with_fg(LIGHT)
.with_bg(DARK)
.render(target);
}
#[cfg(feature = "ui_bounds")]
fn bounds(&self, sink: &mut dyn FnMut(Rect)) {
sink(self.area)

@ -1,10 +1,11 @@
use crate::ui::{
component::{Component, Event, EventCtx, Never, Paginate},
geometry::{Alignment, Offset, Rect},
shape::Renderer,
};
use super::{
layout::{LayoutFit, LayoutSink, TextNoOp, TextRenderer},
layout::{LayoutFit, LayoutSink, TextNoOp, TextRenderer, TextRenderer2},
op::OpTextLayout,
};
@ -133,6 +134,10 @@ impl Component for FormattedText {
self.layout_content(&mut TextRenderer);
}
fn render<'s>(&'s self, target: &mut impl Renderer<'s>) {
self.layout_content(&mut TextRenderer2::new(target));
}
#[cfg(feature = "ui_bounds")]
fn bounds(&self, sink: &mut dyn FnMut(Rect)) {
sink(self.op_layout.layout.bounds)

@ -2,6 +2,8 @@ use crate::ui::{
display,
display::{toif::Icon, Color, Font, GlyphMetrics},
geometry::{Alignment, Alignment2D, Dimensions, Offset, Point, Rect},
shape,
shape::Renderer,
};
const ELLIPSIS: &str = "...";
@ -235,6 +237,15 @@ impl TextLayout {
self.layout_text(text, &mut self.initial_cursor(), &mut TextRenderer)
}
/// Draw as much text as possible on the current screen.
pub fn render_text2<'s>(&self, text: &str, target: &mut impl Renderer<'s>) -> LayoutFit {
self.layout_text(
text,
&mut self.initial_cursor(),
&mut TextRenderer2::new(target),
)
}
/// Loop through the `text` and try to fit it on the current screen,
/// reporting events to `sink`, which may do something with them (e.g. draw
/// on screen).
@ -530,6 +541,67 @@ impl LayoutSink for TextRenderer {
}
}
pub struct TextRenderer2<'a, 's, R>(pub &'a mut R, core::marker::PhantomData<&'s ()>)
where
R: Renderer<'s>;
impl<'a, 's, R> TextRenderer2<'a, 's, R>
where
R: Renderer<'s>,
{
pub fn new(target: &'a mut R) -> Self {
Self(target, core::marker::PhantomData)
}
}
impl<'a, 's, R> LayoutSink for TextRenderer2<'a, 's, R>
where
R: Renderer<'s>,
{
fn text(&mut self, cursor: Point, layout: &TextLayout, text: &str) {
shape::Text::new(cursor, text)
.with_font(layout.style.text_font)
.with_fg(layout.style.text_color)
.render(self.0);
}
fn hyphen(&mut self, cursor: Point, layout: &TextLayout) {
shape::Text::new(cursor, "-")
.with_font(layout.style.text_font)
.with_fg(layout.style.hyphen_color)
.render(self.0);
}
fn ellipsis(&mut self, cursor: Point, layout: &TextLayout) {
if let Some((icon, margin)) = layout.style.ellipsis_icon {
let bottom_left = cursor + Offset::x(margin);
shape::ToifImage::new(bottom_left, icon.toif)
.with_align(Alignment2D::BOTTOM_LEFT)
.with_fg(layout.style.ellipsis_color)
.render(self.0);
} else {
shape::Text::new(cursor, ELLIPSIS)
.with_font(layout.style.text_font)
.with_fg(layout.style.ellipsis_color)
.render(self.0);
}
}
fn prev_page_ellipsis(&mut self, cursor: Point, layout: &TextLayout) {
if let Some((icon, _margin)) = layout.style.prev_page_ellipsis_icon {
shape::ToifImage::new(cursor, icon.toif)
.with_align(Alignment2D::BOTTOM_LEFT)
.with_fg(layout.style.ellipsis_color)
.render(self.0);
} else {
shape::Text::new(cursor, ELLIPSIS)
.with_font(layout.style.text_font)
.with_fg(layout.style.ellipsis_color)
.render(self.0);
}
}
}
#[cfg(feature = "ui_debug")]
pub mod trace {
use crate::{trace::ListTracer, ui::geometry::Point};

@ -8,6 +8,8 @@ use crate::{
geometry::{
Alignment, Alignment2D, Dimensions, Insets, LinearPlacement, Offset, Point, Rect,
},
shape,
shape::Renderer,
},
};
@ -185,6 +187,17 @@ where
)
}
fn render<'s>(&'s self, target: &mut impl Renderer<'s>) {
Self::foreach_visible(
&self.source,
&self.visible,
self.offset,
&mut |layout, content| {
layout.render_text2(content, target);
},
)
}
#[cfg(feature = "ui_bounds")]
fn bounds(&self, sink: &mut dyn FnMut(Rect)) {
sink(self.area);
@ -593,6 +606,19 @@ impl<T> Checklist<T> {
layout.style.background_color,
);
}
fn render_icon<'s>(
&self,
layout: &TextLayout,
icon: Icon,
offset: Offset,
target: &mut impl Renderer<'s>,
) {
let top_left = Point::new(self.area.x0, layout.bounds.y0);
shape::ToifImage::new(top_left + offset, icon.toif)
.with_fg(layout.style.text_color)
.render(target);
}
}
impl<'a, T> Component for Checklist<T>
@ -632,6 +658,28 @@ where
}
}
fn render<'s>(&'s self, target: &mut impl Renderer<'s>) {
self.paragraphs.render(target);
let current_visible = self.current.saturating_sub(self.paragraphs.offset.par);
for layout in self.paragraphs.visible.iter().take(current_visible) {
self.render_icon(
&layout.layout(&self.paragraphs.source),
self.icon_done,
self.done_offset,
target,
);
}
if let Some(layout) = self.paragraphs.visible.iter().nth(current_visible) {
self.render_icon(
&layout.layout(&self.paragraphs.source),
self.icon_current,
self.current_offset,
target,
);
}
}
#[cfg(feature = "ui_bounds")]
fn bounds(&self, sink: &mut dyn FnMut(Rect)) {
sink(self.area);

@ -3,6 +3,7 @@ use crate::{
ui::{
display::{Color, Font},
geometry::{Alignment, Rect},
shape::Renderer,
},
};
@ -37,6 +38,33 @@ pub fn text_multiline(
}
}
/// Draws longer multiline texts inside an area.
/// Splits lines on word boundaries/whitespace.
/// When a word is too long to fit one line, splitting
/// it on multiple lines with "-" at the line-ends.
///
/// If it fits, returns the rest of the area.
/// If it does not fit, returns `None`.
pub fn text_multiline2<'s>(
target: &mut impl Renderer<'s>,
area: Rect,
text: TString<'_>,
font: Font,
fg_color: Color,
bg_color: Color,
alignment: Alignment,
) -> Option<Rect> {
let text_style = TextStyle::new(font, fg_color, bg_color, fg_color, fg_color);
let text_layout = TextLayout::new(text_style)
.with_bounds(area)
.with_align(alignment);
let layout_fit = text.map(|t| text_layout.render_text2(t, target));
match layout_fit {
LayoutFit::Fitting { height, .. } => Some(area.split_top(height).1),
LayoutFit::OutOfBounds { .. } => None,
}
}
/// Same as `text_multiline` above, but aligns the text to the bottom of the
/// area.
pub fn text_multiline_bottom(
@ -66,3 +94,34 @@ pub fn text_multiline_bottom(
}
})
}
/// Same as `text_multiline` above, but aligns the text to the bottom of the
/// area.
pub fn text_multiline_bottom2<'s>(
target: &mut impl Renderer<'s>,
area: Rect,
text: TString<'_>,
font: Font,
fg_color: Color,
bg_color: Color,
alignment: Alignment,
) -> Option<Rect> {
let text_style = TextStyle::new(font, fg_color, bg_color, fg_color, fg_color);
let mut text_layout = TextLayout::new(text_style)
.with_bounds(area)
.with_align(alignment);
// When text fits the area, displaying it in the bottom part.
// When not, render it "normally".
text.map(|t| match text_layout.fit_text(t) {
LayoutFit::Fitting { height, .. } => {
let (top, bottom) = area.split_bottom(height);
text_layout = text_layout.with_bounds(bottom);
text_layout.render_text2(t, target);
Some(top)
}
LayoutFit::OutOfBounds { .. } => {
text_layout.render_text2(t, target);
None
}
})
}

@ -3,6 +3,7 @@ use crate::{
ui::{
component::{Component, Event, EventCtx, TimerToken},
geometry::Rect,
shape::Renderer,
},
};
@ -44,6 +45,8 @@ impl Component for Timeout {
}
fn paint(&mut self) {}
fn render<'s>(&'s self, _target: &mut impl Renderer<'s>) {}
}
#[cfg(feature = "ui_debug")]

@ -98,7 +98,7 @@ impl Color {
let r = (fg.r() as u16) * fg_mul + (self.r() as u16) * bg_mul;
let g = (fg.g() as u16) * fg_mul + (self.g() as u16) * bg_mul;
let b = (fg.b() as u16) * fg_mul + (self.b() as u16) * bg_mul;
Color::rgb((r >> 8) as u8, (g >> 8) as u8, (b >> 8) as u8)
Color::rgb((r / 255) as u8, (g / 255) as u8, (b / 255) as u8)
}
}

@ -3,6 +3,7 @@ use crate::{
ui::{
constant,
geometry::{Offset, Point, Rect},
shape::{Bitmap, BitmapFormat},
},
};
use core::slice;
@ -41,12 +42,12 @@ impl Glyph {
let width = *data.offset(0) as i16;
let height = *data.offset(1) as i16;
let data_bits = constant::FONT_BPP * width * height;
let data_bytes = if data_bits % 8 == 0 {
data_bits / 8
} else {
(data_bits / 8) + 1
let data_bytes = match constant::FONT_BPP {
1 => (width * height + 7) / 8, // packed bits
2 => (width * height + 3) / 4, // packed bits
4 => (width + 1) / 2 * height, // row aligned to bytes
8 => width * height,
_ => panic!(),
};
Glyph {
@ -119,6 +120,28 @@ impl Glyph {
_ => 0,
}
}
pub fn bitmap(&self) -> Bitmap<'static> {
match constant::FONT_BPP {
1 => unwrap!(Bitmap::new(
BitmapFormat::MONO1P,
None,
Offset::new(self.width, self.height),
None,
self.data,
)),
2 => panic!(),
4 => unwrap!(Bitmap::new(
BitmapFormat::MONO4,
None,
Offset::new(self.width, self.height),
None,
self.data,
)),
8 => panic!(),
_ => panic!(),
}
}
}
/// Font constants. Keep in sync with FONT_ definitions in
@ -240,6 +263,16 @@ impl Font {
constant::LINE_SPACE + self.text_height()
}
// Returns x-coordinate of the text start (including left bearing)
pub fn horz_center(&self, start: i16, end: i16, text: &str) -> i16 {
(start + end - self.visible_text_width(text)) / 2 - self.start_x_bearing(text)
}
// Returns y-coordinate of the text baseline
pub fn vert_center(&self, start: i16, end: i16, text: &str) -> i16 {
(start + end + self.visible_text_height(text)) / 2
}
pub fn get_glyph(self, ch: char) -> Glyph {
let gl_data = display::get_char_glyph(ch as u16, self.into());

@ -20,10 +20,11 @@ pub fn jpeg(data: &[u8], pos: Point, scale: u8) {
let mut inp = BufferInput(data);
if let Ok(mut jd) = JDEC::new(&mut inp, pool) {
let _ = jd.set_scale(scale);
let _ = jd.decomp(&mut out);
let _ = jd.decomp(&mut inp, &mut out);
}
}
#[cfg(not(feature = "new_rendering"))]
pub fn jpeg_info(data: &[u8]) -> Option<(Offset, i16)> {
let mut buffer = BufferJpegWork::get_cleared();
let pool = buffer.buffer.as_mut_slice();
@ -40,6 +41,82 @@ pub fn jpeg_info(data: &[u8]) -> Option<(Offset, i16)> {
result
}
#[cfg(feature = "new_rendering")]
pub fn jpeg_info(data: &[u8]) -> Option<(Offset, i16)> {
const M_SOI: u16 = 0xFFD8;
const M_SOF0: u16 = 0xFFC0;
const M_DRI: u16 = 0xFFDD;
const M_RST0: u16 = 0xFFD0;
const M_RST7: u16 = 0xFFD7;
const M_SOS: u16 = 0xFFDA;
const M_EOI: u16 = 0xFFD9;
let mut result = None;
let mut ofs = 0;
let read_u16 = |ofs| -> Option<u16> {
if ofs + 1 < data.len() {
Some(((data[ofs] as u16) << 8) + data[ofs + 1] as u16)
} else {
None
}
};
let read_u8 = |ofs| -> Option<u8> {
if ofs < data.len() {
Some(data[ofs])
} else {
None
}
};
while ofs < data.len() {
if read_u16(ofs)? == M_SOI {
break;
}
ofs += 1;
}
loop {
let marker = read_u16(ofs)?;
if (marker & 0xFF00) != 0xFF00 {
return None;
}
ofs += 2;
ofs += match marker {
M_SOI => 0,
M_SOF0 => {
let w = read_u16(ofs + 3)? as i16;
let h = read_u16(ofs + 5)? as i16;
// Number of components
let nc = read_u8(ofs + 7)?;
if (nc != 1) && (nc != 3) {
return None;
}
// Sampling factor of the first component
let c1 = read_u8(ofs + 9)?;
if (c1 != 0x11) && (c1 != 0x21) & (c1 != 0x22) {
return None;
};
let mcu_height = (8 * (c1 & 15)) as i16;
result = Some((Offset::new(w, h), mcu_height));
read_u16(ofs)?
}
M_DRI => 4,
M_EOI => return None,
M_RST0..=M_RST7 => 0,
M_SOS => break,
_ => read_u16(ofs)?,
} as usize;
}
result
}
pub fn jpeg_test(data: &[u8]) -> bool {
let mut buffer = BufferJpegWork::get_cleared();
let pool = buffer.buffer.as_mut_slice();
@ -50,9 +127,9 @@ pub fn jpeg_test(data: &[u8]) -> bool {
}
let mut out = BlackHoleOutput;
let mut res = jd.decomp(&mut out);
let mut res = jd.decomp(&mut inp, &mut out);
while res == Err(Error::Interrupted) {
res = jd.decomp(&mut out);
res = jd.decomp(&mut inp, &mut out);
}
res.is_ok()
} else {

@ -545,6 +545,7 @@ pub enum Alignment {
End,
}
#[derive(Copy, Clone)]
pub struct Alignment2D(pub Alignment, pub Alignment);
impl Alignment2D {
@ -552,6 +553,8 @@ impl Alignment2D {
pub const TOP_RIGHT: Alignment2D = Alignment2D(Alignment::End, Alignment::Start);
pub const TOP_CENTER: Alignment2D = Alignment2D(Alignment::Center, Alignment::Start);
pub const CENTER: Alignment2D = Alignment2D(Alignment::Center, Alignment::Center);
pub const CENTER_LEFT: Alignment2D = Alignment2D(Alignment::Start, Alignment::Center);
pub const CENTER_RIGHT: Alignment2D = Alignment2D(Alignment::End, Alignment::Center);
pub const BOTTOM_LEFT: Alignment2D = Alignment2D(Alignment::Start, Alignment::End);
pub const BOTTOM_RIGHT: Alignment2D = Alignment2D(Alignment::End, Alignment::End);
pub const BOTTOM_CENTER: Alignment2D = Alignment2D(Alignment::Center, Alignment::End);

@ -19,8 +19,9 @@ use crate::{
ui::{
component::{Component, Event, EventCtx, Never, Root, TimerToken},
constant,
display::sync,
display::{sync, Color},
geometry::Rect,
shape::render_on_display,
},
};
@ -69,9 +70,26 @@ where
}
fn obj_paint(&mut self) -> bool {
let will_paint = self.inner().will_paint();
self.paint();
will_paint
#[cfg(not(feature = "new_rendering"))]
let legacy_mode = true;
#[cfg(feature = "new_rendering")]
let legacy_mode = false;
if legacy_mode {
let will_paint = self.inner().will_paint();
self.paint();
will_paint
} else {
let will_paint = self.inner().will_paint();
if will_paint {
render_on_display(None, Some(Color::black()), |target| {
self.render(target);
});
self.skip_paint();
}
will_paint
}
}
#[cfg(feature = "ui_bounds")]

@ -12,8 +12,12 @@ use crate::ui::{
ui_features::ModelUI,
UIFeaturesCommon,
};
use num_traits::ToPrimitive;
#[cfg(feature = "new_rendering")]
use crate::ui::{display::color::Color, shape::render_on_display};
pub trait ReturnToC {
fn return_to_c(self) -> u32;
}
@ -63,6 +67,27 @@ fn touch_eval() -> Option<TouchEvent> {
TouchEvent::new(event_type, ex as _, ey as _).ok()
}
fn render<F>(frame: &mut F)
where
F: Component,
{
#[cfg(not(feature = "new_rendering"))]
{
display::sync();
frame.paint();
display::refresh();
}
#[cfg(feature = "new_rendering")]
{
display::sync();
render_on_display(None, Some(Color::black()), |target| {
frame.render(target);
});
display::refresh();
}
}
pub fn run<F>(frame: &mut F) -> u32
where
F: Component,
@ -70,9 +95,7 @@ where
{
frame.place(ModelUI::SCREEN);
ModelUI::fadeout();
display::sync();
frame.paint();
display::refresh();
render(frame);
ModelUI::fadein();
#[cfg(feature = "button")]
@ -93,9 +116,7 @@ where
if let Some(message) = msg {
return message.return_to_c();
}
display::sync();
frame.paint();
display::refresh();
render(frame);
}
}
}
@ -105,12 +126,13 @@ where
F: Component,
{
frame.place(ModelUI::SCREEN);
if fading {
ModelUI::fadeout()
};
display::sync();
frame.paint();
display::refresh();
render(frame);
if fading {
ModelUI::fadein()
};

@ -8,6 +8,7 @@ pub mod display;
pub mod event;
pub mod geometry;
pub mod lerp;
pub mod shape;
#[macro_use]
pub mod util;

@ -5,6 +5,7 @@ use crate::{
constant::screen,
display::Icon,
geometry::{Alignment, Insets, Point, Rect},
shape::Renderer,
},
};
@ -106,6 +107,15 @@ impl<'a> Component for Intro<'a> {
self.menu.paint();
}
fn render<'s>(&'s self, target: &mut impl Renderer<'s>) {
self.bg.render(target);
self.title.render(target);
self.text.render(target);
self.warn.render(target);
self.host.render(target);
self.menu.render(target);
}
#[cfg(feature = "ui_bounds")]
fn bounds(&self, sink: &mut dyn FnMut(Rect)) {
self.menu.bounds(sink);

@ -5,6 +5,7 @@ use crate::{
constant::{screen, WIDTH},
display::Icon,
geometry::{Insets, Point, Rect},
shape::Renderer,
},
};
@ -108,6 +109,14 @@ impl Component for Menu {
self.reset.paint();
}
fn render<'s>(&'s self, target: &mut impl Renderer<'s>) {
self.bg.render(target);
self.title.render(target);
self.close.render(target);
self.reboot.render(target);
self.reset.render(target);
}
#[cfg(feature = "ui_bounds")]
fn bounds(&self, sink: &mut dyn FnMut(Rect)) {
self.close.bounds(sink);

@ -23,12 +23,31 @@ use super::{
FIRE40, RESULT_FW_INSTALL, RESULT_WIPE, TEXT_BOLD, TEXT_NORMAL, TEXT_WIPE_BOLD,
TEXT_WIPE_NORMAL, WARNING40, WELCOME_COLOR, X24,
},
BACKLIGHT_NORMAL, BLACK, GREEN_LIGHT, GREY, WHITE,
BACKLIGHT_NORMAL, GREEN_LIGHT, GREY,
},
ModelMercuryFeatures,
};
use crate::ui::{ui_features::UIFeaturesBootloader, UIFeaturesCommon};
#[cfg(not(feature = "new_rendering"))]
use super::theme::BLACK;
#[cfg(feature = "new_rendering")]
use crate::ui::{
constant,
display::toif::Toif,
geometry::{Alignment, Alignment2D},
shape,
shape::render_on_display,
util::version_split,
};
#[cfg(feature = "new_rendering")]
use ufmt::uwrite;
#[cfg(feature = "new_rendering")]
use super::theme::bootloader::BLD_WARN_COLOR;
use intro::Intro;
use menu::Menu;
@ -44,6 +63,7 @@ const SCREEN: Rect = ModelMercuryFeatures::SCREEN;
const PROGRESS_TEXT_ORIGIN: Point = Point::new(2, 28);
impl ModelMercuryFeatures {
#[cfg(not(feature = "new_rendering"))]
fn screen_progress(
text: &str,
progress: u16,
@ -77,6 +97,70 @@ impl ModelMercuryFeatures {
Self::fadein();
}
}
#[cfg(feature = "new_rendering")]
fn screen_progress(
text: &str,
progress: u16,
initialize: bool,
fg_color: Color,
bg_color: Color,
icon: Option<(Icon, Color)>,
center_text: Option<&str>,
) {
if initialize {
Self::fadeout();
}
display::sync();
render_on_display(None, Some(bg_color), |target| {
shape::Text::new(PROGRESS_TEXT_ORIGIN, text)
.with_font(Font::NORMAL)
.with_fg(BLD_FG)
.render(target);
let loader_offset: i16 = 19;
let center_text_offset: i16 = 10;
let center = SCREEN.center() + Offset::y(loader_offset);
let inactive_color = bg_color.blend(fg_color, 85);
shape::Circle::new(center, constant::LOADER_OUTER)
.with_bg(inactive_color)
.render(target);
shape::Circle::new(center, constant::LOADER_OUTER)
.with_bg(fg_color)
.with_end_angle(((progress as i32 * shape::PI4 as i32 * 8) / 1000) as i16)
.render(target);
shape::Circle::new(center, constant::LOADER_INNER + 2)
.with_bg(bg_color)
.render(target);
if let Some((icon, color)) = icon {
shape::ToifImage::new(center, icon.toif)
.with_align(Alignment2D::CENTER)
.with_fg(color)
.render(target);
}
if let Some(center_text) = center_text {
shape::Text::new(
SCREEN.center() + Offset::y(loader_offset + center_text_offset),
center_text,
)
.with_align(Alignment::Center)
.with_font(Font::NORMAL)
.with_fg(GREY)
.render(target);
}
});
display::refresh();
if initialize {
Self::fadein();
}
}
}
impl UIFeaturesBootloader for ModelMercuryFeatures {
@ -85,12 +169,13 @@ impl UIFeaturesBootloader for ModelMercuryFeatures {
show(&mut frame, true);
}
#[cfg(not(feature = "new_rendering"))]
fn bld_continue_label(bg_color: Color) {
display::text_center(
Point::new(SCREEN.width() / 2, SCREEN.height() - 5),
"click to continue ...",
Font::NORMAL,
WHITE,
BLD_FG,
bg_color,
);
}
@ -127,8 +212,6 @@ impl UIFeaturesBootloader for ModelMercuryFeatures {
None,
);
}
display::refresh();
}
fn screen_install_fail() {
@ -252,6 +335,7 @@ impl UIFeaturesBootloader for ModelMercuryFeatures {
Self::fadeout();
}
#[cfg(not(feature = "new_rendering"))]
display::rect_fill(SCREEN, BLACK);
let mut frame = WelcomeScreen::new();
@ -262,7 +346,6 @@ impl UIFeaturesBootloader for ModelMercuryFeatures {
} else {
display::set_backlight(BACKLIGHT_NORMAL);
}
display::refresh();
}
fn screen_wipe_progress(progress: u16, initialize: bool) {
@ -322,4 +405,90 @@ impl UIFeaturesBootloader for ModelMercuryFeatures {
);
show(&mut frame, true);
}
#[cfg(feature = "new_rendering")]
fn screen_boot(
warning: bool,
vendor_str: Option<&str>,
version: u32,
vendor_img: &[u8],
wait: i32,
) {
let bg_color = if warning { BLD_WARN_COLOR } else { BLD_BG };
display::sync();
render_on_display(None, Some(bg_color), |target| {
// Draw vendor image if it's valid and has size of 120x120
if let Ok(toif) = Toif::new(vendor_img) {
if (toif.width() == 120) && (toif.height() == 120) {
// Image position depends on the vendor string presence
let pos = if vendor_str.is_some() {
Point::new(SCREEN.width() / 2, 30)
} else {
Point::new(SCREEN.width() / 2, 60)
};
shape::ToifImage::new(pos, toif)
.with_align(Alignment2D::TOP_CENTER)
.with_fg(BLD_FG)
.render(target);
}
}
// Draw vendor string if present
if let Some(text) = vendor_str {
let pos = Point::new(SCREEN.width() / 2, SCREEN.height() - 5 - 50);
shape::Text::new(pos, text)
.with_align(Alignment::Center)
.with_font(Font::NORMAL)
.with_fg(BLD_FG) //COLOR_BL_BG
.render(target);
let pos = Point::new(SCREEN.width() / 2, SCREEN.height() - 5 - 25);
let mut version_text: BootloaderString = String::new();
let ver_nums = version_split(version);
unwrap!(uwrite!(
version_text,
"{}.{}.{}",
ver_nums[0],
ver_nums[1],
ver_nums[2]
));
shape::Text::new(pos, version_text.as_str())
.with_align(Alignment::Center)
.with_font(Font::NORMAL)
.with_fg(BLD_FG)
.render(target);
}
// Draw a message
match wait.cmp(&0) {
core::cmp::Ordering::Equal => {}
core::cmp::Ordering::Greater => {
let mut text: BootloaderString = String::new();
unwrap!(uwrite!(text, "starting in {} s", wait));
let pos = Point::new(SCREEN.width() / 2, SCREEN.height() - 5);
shape::Text::new(pos, text.as_str())
.with_align(Alignment::Center)
.with_font(Font::NORMAL)
.with_fg(BLD_FG)
.render(target);
}
core::cmp::Ordering::Less => {
let pos = Point::new(SCREEN.width() / 2, SCREEN.height() - 5);
shape::Text::new(pos, "click to continue ...")
.with_align(Alignment::Center)
.with_font(Font::NORMAL)
.with_fg(BLD_FG)
.render(target);
}
}
});
display::refresh();
}
}

@ -3,6 +3,8 @@ use crate::ui::{
constant::screen,
display::{self, Font},
geometry::{Offset, Point, Rect},
shape,
shape::Renderer,
};
use super::super::theme::{BLACK, GREY, WHITE};
@ -61,4 +63,33 @@ impl Component for Welcome {
BLACK,
);
}
fn render<'s>(&'s self, target: &mut impl Renderer<'s>) {
self.bg.render(target);
shape::Text::new(TEXT_ORIGIN, "Get started")
.with_font(Font::NORMAL)
.with_fg(GREY)
.render(target);
shape::Text::new(TEXT_ORIGIN + Offset::y(STRIDE), "with your Trezor")
.with_font(Font::NORMAL)
.with_fg(GREY)
.render(target);
shape::Text::new(TEXT_ORIGIN + Offset::y(2 * STRIDE), "at")
.with_font(Font::NORMAL)
.with_fg(GREY)
.render(target);
let at_width = Font::NORMAL.text_width("at ");
shape::Text::new(
TEXT_ORIGIN + Offset::new(at_width, 2 * STRIDE),
"trezor.io/start",
)
.with_font(Font::NORMAL)
.with_fg(WHITE)
.render(target);
}
}

@ -6,6 +6,8 @@ use crate::{
constant::screen,
display::{Color, Icon},
geometry::{Alignment2D, Insets, Offset, Point, Rect},
shape,
shape::Renderer,
},
};
@ -239,6 +241,40 @@ impl Component for Confirm<'_> {
}
}
fn render<'s>(&'s self, target: &mut impl Renderer<'s>) {
self.bg.render(target);
self.content_pad.render(target);
if let Some(info) = self.info.as_ref() {
if self.show_info {
info.close_button.render(target);
info.title.render(target);
info.text.render(target);
self.left_button.render(target);
self.right_button.render(target);
// short-circuit before painting the main components
return;
} else {
info.info_button.render(target);
// pass through to the rest of the paint
}
}
self.message.render(target);
self.alert.render(target);
self.left_button.render(target);
self.right_button.render(target);
match &self.title {
ConfirmTitle::Text(label) => label.render(target),
ConfirmTitle::Icon(icon) => {
shape::ToifImage::new(Point::new(screen().center().x, ICON_TOP), icon.toif)
.with_align(Alignment2D::TOP_CENTER)
.with_fg(WHITE)
.render(target);
}
}
}
#[cfg(feature = "ui_bounds")]
fn bounds(&self, sink: &mut dyn FnMut(Rect)) {
self.left_button.bounds(sink);

@ -8,6 +8,8 @@ use crate::{
display::{self, toif::Icon, Color, Font},
event::TouchEvent,
geometry::{Alignment2D, Insets, Offset, Point, Rect},
shape,
shape::Renderer,
},
};
@ -185,6 +187,18 @@ impl Button {
}
}
pub fn render_background<'s>(&self, target: &mut impl Renderer<'s>, style: &ButtonStyle) {
match &self.content {
ButtonContent::IconBlend(_, _, _) => {}
_ => shape::Bar::new(self.area)
.with_bg(style.button_color)
.with_fg(style.border_color)
.with_thickness(style.border_width)
.with_radius(style.border_radius as i16)
.render(target),
}
}
pub fn paint_content(&self, style: &ButtonStyle) {
match &self.content {
ButtonContent::Empty => {}
@ -223,6 +237,45 @@ impl Button {
),
}
}
pub fn render_content<'s>(&self, target: &mut impl Renderer<'s>, style: &ButtonStyle) {
match &self.content {
ButtonContent::Empty => {}
ButtonContent::Text(text) => {
let width = text.map(|c| style.font.text_width(c));
let height = style.font.text_height();
let start_of_baseline = self.area.center()
+ Offset::new(-width / 2, height / 2)
+ Offset::y(Self::BASELINE_OFFSET);
text.map(|text| {
shape::Text::new(start_of_baseline, text)
.with_font(style.font)
.with_fg(style.text_color)
.render(target);
});
}
ButtonContent::Icon(icon) => {
shape::ToifImage::new(self.area.center(), icon.toif)
.with_align(Alignment2D::CENTER)
.with_fg(style.text_color)
.render(target);
}
ButtonContent::IconAndText(child) => {
child.render(target, self.area, self.style(), Self::BASELINE_OFFSET);
}
ButtonContent::IconBlend(bg, fg, offset) => {
shape::Bar::new(self.area)
.with_bg(style.background_color)
.render(target);
shape::ToifImage::new(self.area.top_left(), bg.toif)
.with_fg(style.button_color)
.render(target);
shape::ToifImage::new(self.area.top_left() + *offset, fg.toif)
.with_fg(style.text_color)
.render(target);
}
}
}
}
impl Component for Button {
@ -311,6 +364,12 @@ impl Component for Button {
self.paint_content(style);
}
fn render<'s>(&'s self, target: &mut impl Renderer<'s>) {
let style = self.style();
self.render_background(target, style);
self.render_content(target, style);
}
#[cfg(feature = "ui_bounds")]
fn bounds(&self, sink: &mut dyn FnMut(Rect)) {
sink(self.area);
@ -437,4 +496,52 @@ impl IconText {
);
}
}
pub fn render<'s>(
&self,
target: &mut impl Renderer<'s>,
area: Rect,
style: &ButtonStyle,
baseline_offset: i16,
) {
let width = style.font.text_width(self.text);
let height = style.font.text_height();
let mut use_icon = false;
let mut use_text = false;
let mut icon_pos = Point::new(
area.top_left().x + ((Self::ICON_SPACE + Self::ICON_MARGIN) / 2),
area.center().y,
);
let mut text_pos =
area.center() + Offset::new(-width / 2, height / 2) + Offset::y(baseline_offset);
if area.width() > (Self::ICON_SPACE + Self::TEXT_MARGIN + width) {
//display both icon and text
text_pos = Point::new(area.top_left().x + Self::ICON_SPACE, text_pos.y);
use_text = true;
use_icon = true;
} else if area.width() > (width + Self::TEXT_MARGIN) {
use_text = true;
} else {
//if we can't fit the text, retreat to centering the icon
icon_pos = area.center();
use_icon = true;
}
if use_text {
shape::Text::new(text_pos, self.text)
.with_font(style.font)
.with_fg(style.text_color)
.render(target);
}
if use_icon {
shape::ToifImage::new(icon_pos, self.icon.toif)
.with_align(Alignment2D::CENTER)
.with_fg(style.text_color)
.render(target);
}
}
}

@ -4,6 +4,8 @@ use crate::{
component::{Child, Component, Event, EventCtx, Label, Never, Pad},
constant::screen,
geometry::{Alignment2D, Point, Rect},
shape,
shape::Renderer,
},
};
@ -89,4 +91,19 @@ impl<'a> Component for ErrorScreen<'a> {
self.message.paint();
self.footer.paint();
}
fn render<'s>(&'s self, target: &mut impl Renderer<'s>) {
self.bg.render(target);
let icon = ICON_WARNING40;
shape::ToifImage::new(Point::new(screen().center().x, ICON_TOP), icon.toif)
.with_fg(WHITE)
.with_bg(FATAL_ERROR_COLOR)
.with_align(Alignment2D::TOP_CENTER)
.render(target);
self.title.render(target);
self.message.render(target);
self.footer.render(target);
}
}

@ -7,6 +7,7 @@ use crate::{
},
display::Icon,
geometry::{Alignment, Insets, Offset, Rect},
shape::Renderer,
},
};
@ -175,6 +176,12 @@ where
self.button.paint();
self.content.paint();
}
fn render<'s>(&'s self, target: &mut impl Renderer<'s>) {
self.title.render(target);
self.subtitle.render(target);
self.button.render(target);
self.content.render(target);
}
#[cfg(feature = "ui_bounds")]
fn bounds(&self, sink: &mut dyn FnMut(Rect)) {

@ -6,7 +6,8 @@ use crate::{
animation::Animation,
component::{Component, Event, EventCtx, Pad},
display::{self, toif::Icon, Color},
geometry::{Offset, Rect},
geometry::{Alignment2D, Offset, Rect},
shape::{self, Renderer},
util::animation_disabled,
},
};
@ -205,6 +206,49 @@ impl Component for Loader {
);
}
}
fn render<'s>(&'s self, target: &mut impl Renderer<'s>) {
// TODO: Consider passing the current instant along with the event -- that way,
// we could synchronize painting across the component tree. Also could be useful
// in automated tests.
// In practice, taking the current instant here is more precise in case some
// other component in the tree takes a long time to draw.
let now = Instant::now();
if let Some(progress) = self.progress(now) {
let style = if progress < display::LOADER_MAX {
self.styles.normal
} else {
self.styles.active
};
self.pad.render(target);
let center = self.pad.area.center();
let inactive_color = Color::black().blend(style.loader_color, 85);
shape::Circle::new(center, constant::LOADER_OUTER)
.with_bg(inactive_color)
.render(target);
shape::Circle::new(center, constant::LOADER_OUTER)
.with_bg(style.loader_color)
.with_end_angle(((progress as i32 * shape::PI4 as i32 * 8) / 1000) as i16)
.render(target);
shape::Circle::new(center, constant::LOADER_INNER)
.with_bg(style.background_color)
.render(target);
if let Some((icon, color)) = style.icon {
shape::ToifImage::new(center, icon.toif)
.with_align(Alignment2D::CENTER)
.with_fg(color)
.render(target);
}
}
}
}
pub struct LoaderStyleSheet {

@ -5,6 +5,8 @@ use crate::{
constant::screen,
display::{self, Color, Font, Icon},
geometry::{Alignment2D, Insets, Offset, Point, Rect},
shape,
shape::Renderer,
},
};
@ -94,6 +96,20 @@ impl Component for ResultFooter<'_> {
// footer text
self.text.paint();
}
fn render<'s>(&'s self, target: &mut impl Renderer<'s>) {
// divider line
let bar = Rect::from_center_and_size(
Point::new(self.area.center().x, self.area.y0),
Offset::new(self.area.width(), 1),
);
shape::Bar::new(bar)
.with_fg(self.style.divider_color)
.render(target);
// footer text
self.text.render(target);
}
}
pub struct ResultScreen<'a> {
@ -165,4 +181,21 @@ impl<'a> Component for ResultScreen<'a> {
self.message.paint();
self.footer.paint();
}
fn render<'s>(&'s self, target: &mut impl Renderer<'s>) {
self.bg.render(target);
self.footer_pad.render(target);
shape::ToifImage::new(
Point::new(screen().center().x, ICON_CENTER_Y),
self.icon.toif,
)
.with_align(Alignment2D::CENTER)
.with_fg(self.style.fg_color)
.with_bg(self.style.bg_color)
.render(target);
self.message.render(target);
self.footer.render(target);
}
}

@ -1,7 +1,10 @@
use crate::ui::{
component::{Component, Event, EventCtx, Never},
display,
geometry::{Alignment2D, Offset, Rect},
display::font::Font,
geometry::{Alignment, Alignment2D, Offset, Rect},
shape,
shape::Renderer,
};
use super::theme;
@ -50,6 +53,26 @@ impl Component for WelcomeScreen {
theme::BG,
);
}
fn render<'s>(&'s self, target: &mut impl Renderer<'s>) {
shape::ToifImage::new(
self.area.top_center() + Offset::y(ICON_TOP_MARGIN),
theme::ICON_LOGO.toif,
)
.with_align(Alignment2D::TOP_CENTER)
.with_fg(theme::FG)
.with_bg(theme::BG)
.render(target);
shape::Text::new(
self.area.bottom_center() - Offset::y(TEXT_BOTTOM_MARGIN),
"Trezor Safe 5",
)
.with_align(Alignment::Center)
.with_font(Font::NORMAL)
.with_fg(theme::FG)
.render(target);
}
}
#[cfg(feature = "ui_debug")]

@ -5,17 +5,37 @@ use super::{
constant,
};
#[cfg(feature = "new_rendering")]
use crate::ui::{display::Color, shape::render_on_display};
pub fn screen_fatal_error(title: &str, msg: &str, footer: &str) {
let mut frame = ErrorScreen::new(title.into(), msg.into(), footer.into());
frame.place(constant::screen());
#[cfg(feature = "new_rendering")]
render_on_display(None, Some(Color::black()), |target| {
frame.render(target);
});
#[cfg(not(feature = "new_rendering"))]
frame.paint();
display::refresh();
}
pub fn screen_boot_stage_2() {
let mut frame = WelcomeScreen::new();
frame.place(screen());
display::sync();
#[cfg(feature = "new_rendering")]
render_on_display(None, Some(Color::black()), |target| {
frame.render(target);
});
#[cfg(not(feature = "new_rendering"))]
frame.paint();
display::refresh();
}

@ -4,6 +4,8 @@ use crate::{
component::{Child, Component, Event, EventCtx, Label, Pad},
geometry::{Alignment, Alignment2D, Rect},
layout::simplified::ReturnToC,
shape,
shape::Renderer,
},
};
@ -104,6 +106,27 @@ impl<'a> Component for Intro<'a> {
self.buttons.paint();
}
fn render<'s>(&'s self, target: &mut impl Renderer<'s>) {
self.bg.render(target);
self.title.render(target);
let area = self.bg.area;
shape::ToifImage::new(area.top_left(), ICON_WARN_TITLE.toif)
.with_align(Alignment2D::TOP_LEFT)
.with_fg(BLD_FG)
.render(target);
shape::ToifImage::new(area.top_left(), ICON_WARN_TITLE.toif)
.with_align(Alignment2D::TOP_RIGHT)
.with_fg(BLD_FG)
.render(target);
self.warn.render(target);
self.text.render(target);
self.buttons.render(target);
}
#[cfg(feature = "ui_bounds")]
fn bounds(&self, sink: &mut dyn FnMut(Rect)) {
self.title.bounds(sink);

@ -7,8 +7,10 @@ use crate::{
constant::screen,
display,
display::{Font, Icon},
geometry::{Alignment2D, Offset, Point, Rect},
geometry::{Alignment, Alignment2D, Offset, Point, Rect},
layout::simplified::ReturnToC,
shape,
shape::Renderer,
},
};
@ -69,6 +71,26 @@ impl Choice for MenuChoice {
);
}
fn render_center<'s>(&self, target: &mut impl Renderer<'s>, _area: Rect, _inverse: bool) {
// Icon on top and two lines of text below
shape::ToifImage::new(SCREEN_CENTER + Offset::y(-20), self.icon.toif)
.with_align(Alignment2D::CENTER)
.with_fg(BLD_FG)
.render(target);
shape::Text::new(SCREEN_CENTER, self.first_line)
.with_align(Alignment::Center)
.with_font(Font::NORMAL)
.with_fg(BLD_FG)
.render(target);
shape::Text::new(SCREEN_CENTER + Offset::y(10), self.second_line)
.with_align(Alignment::Center)
.with_font(Font::NORMAL)
.with_fg(BLD_FG)
.render(target);
}
fn btn_layout(&self) -> ButtonLayout {
ButtonLayout::arrow_armed_arrow("SELECT".into())
}
@ -162,6 +184,11 @@ impl Component for Menu {
self.choice_page.paint();
}
fn render<'s>(&'s self, target: &mut impl Renderer<'s>) {
self.pad.render(target);
self.choice_page.render(target);
}
#[cfg(feature = "ui_bounds")]
fn bounds(&self, sink: &mut dyn FnMut(Rect)) {
self.choice_page.bounds(sink)

@ -7,7 +7,7 @@ use crate::{
constant,
constant::{HEIGHT, SCREEN},
display::{self, Color, Font, Icon},
geometry::{Alignment2D, Offset, Point, Rect},
geometry::{Alignment2D, Offset, Point},
layout::simplified::{run, show, ReturnToC},
},
};
@ -24,6 +24,18 @@ use super::{
ModelTRFeatures,
};
#[cfg(not(feature = "new_rendering"))]
use crate::ui::geometry::Rect;
#[cfg(feature = "new_rendering")]
use crate::ui::{
display::toif::Toif, geometry::Alignment, model_tr::cshape, shape, shape::render_on_display,
util::version_split,
};
#[cfg(feature = "new_rendering")]
use ufmt::uwrite;
mod intro;
mod menu;
mod welcome;
@ -42,6 +54,7 @@ impl ReturnToC for ConfirmMsg {
}
impl ModelTRFeatures {
#[cfg(not(feature = "new_rendering"))]
fn screen_progress(
text: &str,
text2: &str,
@ -84,6 +97,50 @@ impl ModelTRFeatures {
display::refresh();
}
#[cfg(feature = "new_rendering")]
fn screen_progress(
text: &str,
text2: &str,
progress: u16,
_initialize: bool,
fg_color: Color,
bg_color: Color,
icon: Option<(Icon, Color)>,
) {
let progress = if progress < 20 { 20 } else { progress };
display::sync();
render_on_display(None, Some(bg_color), |target| {
let center = SCREEN.top_center() + Offset::y(12);
cshape::LoaderCircular::new(center, progress)
.with_color(fg_color)
.render(target);
if let Some((icon, color)) = icon {
shape::ToifImage::new(center, icon.toif)
.with_align(Alignment2D::CENTER)
.with_fg(color)
.render(target);
}
shape::Text::new(SCREEN.center() + Offset::y(8), text)
.with_align(Alignment::Center)
.with_font(Font::BOLD)
.with_fg(fg_color)
.render(target);
shape::Text::new(SCREEN.center() + Offset::y(20), text2)
.with_align(Alignment::Center)
.with_font(Font::BOLD)
.with_fg(fg_color)
.render(target);
});
display::refresh();
}
}
impl UIFeaturesBootloader for ModelTRFeatures {
@ -92,6 +149,7 @@ impl UIFeaturesBootloader for ModelTRFeatures {
show(&mut frame, true);
}
#[cfg(not(feature = "new_rendering"))]
fn bld_continue_label(bg_color: Color) {
display::text_center(
Point::new(constant::WIDTH / 2, HEIGHT - 2),
@ -261,6 +319,7 @@ impl UIFeaturesBootloader for ModelTRFeatures {
}
fn screen_boot_stage_1(_fading: bool) {
#[cfg(not(feature = "new_rendering"))]
display::rect_fill(SCREEN, BLD_BG);
let mut frame = WelcomeScreen::new(true);
@ -315,4 +374,93 @@ impl UIFeaturesBootloader for ModelTRFeatures {
let mut frame = ResultScreen::new(BLD_FG, BLD_BG, ICON_ALERT, title, content, true);
show(&mut frame, false);
}
#[cfg(feature = "new_rendering")]
fn screen_boot(
_warning: bool,
vendor_str: Option<&str>,
version: u32,
vendor_img: &[u8],
wait: i32,
) {
display::sync();
render_on_display(None, Some(BLD_BG), |target| {
// Draw vendor image if it's valid and has size of 24x24
if let Ok(toif) = Toif::new(vendor_img) {
if (toif.width() == 24) && (toif.height() == 24) {
let pos = Point::new((constant::WIDTH - 22) / 2, 0);
shape::ToifImage::new(pos, toif)
.with_align(Alignment2D::TOP_CENTER)
.with_fg(BLD_FG)
.render(target);
}
}
// Draw vendor string if present
if let Some(text) = vendor_str {
let pos = Point::new(constant::WIDTH / 2, 36);
shape::Text::new(pos, text)
.with_align(Alignment::Center)
.with_font(Font::NORMAL)
.with_fg(BLD_FG) //COLOR_BL_BG
.render(target);
let pos = Point::new(constant::WIDTH / 2, 46);
let mut version_text: BootloaderString = String::new();
let ver_nums = version_split(version);
unwrap!(uwrite!(
version_text,
"{}.{}.{}",
ver_nums[0],
ver_nums[1],
ver_nums[2]
));
shape::Text::new(pos, version_text.as_str())
.with_align(Alignment::Center)
.with_font(Font::NORMAL)
.with_fg(BLD_FG)
.render(target);
}
// Draw a message
match wait.cmp(&0) {
core::cmp::Ordering::Equal => {}
core::cmp::Ordering::Greater => {
let mut text: BootloaderString = String::new();
unwrap!(uwrite!(text, "starting in {} s", wait));
let pos = Point::new(constant::WIDTH / 2, HEIGHT - 5);
shape::Text::new(pos, text.as_str())
.with_align(Alignment::Center)
.with_font(Font::NORMAL)
.with_fg(BLD_FG)
.render(target);
}
core::cmp::Ordering::Less => {
let pos = Point::new(constant::WIDTH / 2, HEIGHT - 2);
shape::Text::new(pos, "CONTINUE")
.with_align(Alignment::Center)
.with_fg(BLD_FG)
.render(target);
let pos = Point::new(constant::WIDTH / 2 - 36, HEIGHT - 6);
shape::ToifImage::new(pos, ICON_ARM_LEFT.toif)
.with_align(Alignment2D::TOP_LEFT)
.with_fg(BLD_FG)
.render(target);
let pos = Point::new(constant::WIDTH / 2 + 25, HEIGHT - 6);
shape::ToifImage::new(pos, ICON_ARM_RIGHT.toif)
.with_align(Alignment2D::TOP_LEFT)
.with_fg(BLD_FG)
.render(target);
}
}
});
display::refresh();
}
}

@ -1,7 +1,9 @@
use crate::ui::{
component::{Component, Event, EventCtx, Never, Pad},
display::{self, Font},
geometry::{Offset, Rect},
geometry::{Alignment, Offset, Rect},
shape,
shape::Renderer,
};
use super::super::theme::bootloader::{BLD_BG, BLD_FG};
@ -57,4 +59,28 @@ impl Component for Welcome {
BLD_BG,
);
}
fn render<'s>(&'s self, target: &mut impl Renderer<'s>) {
self.bg.render(target);
let top_center = self.bg.area.top_center();
shape::Text::new(top_center + Offset::y(24), "Get started with")
.with_align(Alignment::Center)
.with_font(Font::NORMAL)
.with_fg(BLD_FG)
.render(target);
shape::Text::new(top_center + Offset::y(32), "your Trezor at")
.with_align(Alignment::Center)
.with_font(Font::NORMAL)
.with_fg(BLD_FG)
.render(target);
shape::Text::new(top_center + Offset::y(48), "trezor.io/start")
.with_align(Alignment::Center)
.with_font(Font::BOLD)
.with_fg(BLD_FG)
.render(target);
}
}

@ -10,6 +10,7 @@ use crate::{
Child, Component, Event, EventCtx, Pad, Paginate, Qr,
},
geometry::Rect,
shape::Renderer,
},
};
@ -259,6 +260,16 @@ impl Component for AddressDetails {
}
}
fn render<'s>(&'s self, target: &mut impl Renderer<'s>) {
self.pad.render(target);
self.buttons.render(target);
match self.current_page {
0 => self.qr_code.render(target),
1 => self.details_view.render(target),
_ => self.xpub_view.render(target),
}
}
#[cfg(feature = "ui_bounds")]
fn bounds(&self, sink: &mut dyn FnMut(Rect)) {
sink(self.area)

@ -4,6 +4,8 @@ use crate::{
component::{Child, Component, ComponentExt, Event, EventCtx, Label, Pad},
display::{self, Color, Font},
geometry::{Point, Rect},
shape,
shape::Renderer,
},
};
@ -216,6 +218,32 @@ impl Component for Confirm<'_> {
self.buttons.paint();
}
fn render<'s>(&'s self, target: &mut impl Renderer<'s>) {
self.bg.render(target);
let mut display_top_left = |text: TString| {
text.map(|t| {
shape::Text::new(Point::zero(), t)
.with_font(Font::BOLD)
.with_fg(WHITE)
.render(target);
});
};
// We are either on the info screen or on the "main" screen
if self.showing_info_screen {
if let Some(title) = self.info_title {
display_top_left(title);
}
self.info_text.render(target);
} else {
display_top_left(self.title);
self.message.render(target);
self.alert.render(target);
}
self.buttons.render(target);
}
#[cfg(feature = "ui_bounds")]
fn bounds(&self, sink: &mut dyn FnMut(Rect)) {
self.buttons.bounds(sink);

@ -7,6 +7,8 @@ use crate::{
display::{self, Color, Font, Icon},
event::PhysicalButton,
geometry::{Alignment2D, Offset, Point, Rect},
shape,
shape::Renderer,
},
};
@ -265,6 +267,88 @@ impl Component for Button {
}
}
}
fn render<'s>(&'s self, target: &mut impl Renderer<'s>) {
let style = self.style();
let fg_color = style.text_color;
let bg_color = fg_color.negate();
let area = self.get_current_area();
let inversed_colors = bg_color != theme::BG;
// Filling the background (with 2-pixel rounding when applicable)
if inversed_colors {
shape::Bar::new(area)
.with_radius(3)
.with_bg(bg_color)
.render(target);
} else if style.with_outline {
shape::Bar::new(area)
.with_radius(3)
.with_fg(fg_color)
.render(target);
} else {
shape::Bar::new(area).with_bg(bg_color).render(target);
}
// Optionally display "arms" at both sides of content - always in FG and BG
// colors (they are not inverted).
if style.with_arms {
shape::ToifImage::new(area.left_center(), theme::ICON_ARM_LEFT.toif)
.with_align(Alignment2D::TOP_RIGHT)
.with_fg(theme::FG)
.render(target);
shape::ToifImage::new(area.right_center(), theme::ICON_ARM_RIGHT.toif)
.with_align(Alignment2D::TOP_LEFT)
.with_fg(theme::FG)
.render(target);
}
// Painting the content
match &self.content {
ButtonContent::Text(text) => text.map(|t| {
shape::Text::new(
self.get_text_baseline(style) - Offset::x(style.font.start_x_bearing(t)),
t,
)
.with_font(style.font)
.with_fg(fg_color)
.render(target);
}),
ButtonContent::Icon(icon) => {
// Allowing for possible offset of the area from current style
let icon_area = area.translate(style.offset);
if style.with_outline {
shape::ToifImage::new(icon_area.center(), icon.toif)
.with_align(Alignment2D::CENTER)
.with_fg(fg_color)
.render(target);
} else {
// Positioning the icon in the corresponding corner/center
match self.pos {
ButtonPos::Left => {
shape::ToifImage::new(icon_area.bottom_left(), icon.toif)
.with_align(Alignment2D::BOTTOM_LEFT)
.with_fg(fg_color)
.render(target)
}
ButtonPos::Right => {
shape::ToifImage::new(icon_area.bottom_right(), icon.toif)
.with_align(Alignment2D::BOTTOM_RIGHT)
.with_fg(fg_color)
.render(target)
}
ButtonPos::Middle => shape::ToifImage::new(icon_area.center(), icon.toif)
.with_align(Alignment2D::CENTER)
.with_fg(fg_color)
.render(target),
}
}
}
}
}
}
#[derive(PartialEq, Eq)]

@ -7,6 +7,7 @@ use crate::{
component::{base::Event, Component, EventCtx, Pad, TimerToken},
event::{ButtonEvent, PhysicalButton},
geometry::Rect,
shape::Renderer,
},
};
@ -93,6 +94,18 @@ impl ButtonType {
Self::Nothing => {}
}
}
pub fn render<'s>(&'s self, target: &mut impl Renderer<'s>) {
match self {
Self::Button(button) => {
button.render(target);
}
Self::HoldToConfirm(htc) => {
htc.render(target);
}
Self::Nothing => {}
}
}
}
/// Wrapping a button and its state, so that it can be easily
@ -154,6 +167,10 @@ impl ButtonContainer {
self.button_type.paint();
}
pub fn render<'s>(&'s self, target: &mut impl Renderer<'s>) {
self.button_type.render(target);
}
/// Setting the visual state of the button - released/pressed.
pub fn set_pressed(&mut self, ctx: &mut EventCtx, is_pressed: bool) {
if let ButtonType::Button(btn) = &mut self.button_type {
@ -575,6 +592,13 @@ impl Component for ButtonController {
self.right_btn.paint();
}
fn render<'s>(&'s self, target: &mut impl Renderer<'s>) {
self.pad.render(target);
self.left_btn.render(target);
self.middle_btn.render(target);
self.right_btn.render(target);
}
fn place(&mut self, bounds: Rect) -> Rect {
// Saving button area so that we can re-place the buttons
// when they get updated
@ -754,6 +778,8 @@ impl Component for AutomaticMover {
fn paint(&mut self) {}
fn render<'s>(&'s self, _target: &mut impl Renderer<'s>) {}
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
// Moving automatically only when we receive a TimerToken that we have
// requested before

@ -2,6 +2,8 @@ use crate::ui::{
component::{Component, Event, EventCtx, Never, Pad},
display::Font,
geometry::{Alignment, Point, Rect},
shape,
shape::Renderer,
util::long_line_content_with_ellipsis,
};
@ -109,16 +111,39 @@ where
common::display_left(baseline, &self.text, self.font);
}
fn render_left<'s>(&'s self, target: &mut impl Renderer<'s>) {
let baseline = Point::new(self.pad.area.x0, self.y_baseline());
shape::Text::new(baseline, self.text.as_ref())
.with_font(self.font)
.render(target);
}
fn paint_center(&self) {
let baseline = Point::new(self.pad.area.bottom_center().x, self.y_baseline());
common::display_center(baseline, &self.text, self.font);
}
fn render_center<'s>(&'s self, target: &mut impl Renderer<'s>) {
let baseline = Point::new(self.pad.area.bottom_center().x, self.y_baseline());
shape::Text::new(baseline, self.text.as_ref())
.with_align(Alignment::Center)
.with_font(self.font)
.render(target);
}
fn paint_right(&self) {
let baseline = Point::new(self.pad.area.x1, self.y_baseline());
common::display_right(baseline, &self.text, self.font);
}
fn render_right<'s>(&'s self, target: &mut impl Renderer<'s>) {
let baseline = Point::new(self.pad.area.x1, self.y_baseline());
shape::Text::new(baseline, self.text.as_ref())
.with_align(Alignment::End)
.with_font(self.font)
.render(target);
}
fn paint_long_content_with_ellipsis(&self) {
let text_to_display = long_line_content_with_ellipsis(
self.text.as_ref(),
@ -175,4 +200,20 @@ where
}
}
}
fn render<'s>(&'s self, target: &mut impl Renderer<'s>) {
self.pad.render(target);
if self.show_content {
// In the case text cannot fit, show ellipsis and its right part
if !self.text_fits_completely() {
self.paint_long_content_with_ellipsis();
} else {
match self.alignment {
Alignment::Start => self.render_left(target),
Alignment::Center => self.render_center(target),
Alignment::End => self.render_right(target),
}
}
}
}
}

@ -6,11 +6,16 @@ use crate::{
ui::{
component::{
base::Never,
text::util::{text_multiline, text_multiline_bottom},
text::util::{
text_multiline, text_multiline2, text_multiline_bottom, text_multiline_bottom2,
},
Component, Event, EventCtx,
},
display::{self, Font},
geometry::{Alignment, Insets, Rect},
geometry::{Alignment, Alignment2D, Insets, Offset, Rect},
model_tr::cshape,
shape,
shape::Renderer,
util::animation_disabled,
},
};
@ -124,6 +129,56 @@ impl Component for CoinJoinProgress {
);
}
}
fn render<'s>(&'s self, target: &mut impl Renderer<'s>) {
// TOP
let center = self.area.center() + Offset::y(self.loader_y_offset);
if self.indeterminate {
text_multiline2(
target,
self.area,
TR::coinjoin__title_progress.into(),
Font::BOLD,
theme::FG,
theme::BG,
Alignment::Center,
);
cshape::LoaderSmall::new(center, self.value)
.with_color(theme::FG)
.render(target);
} else {
cshape::LoaderCircular::new(center, self.value)
.with_color(theme::FG)
.render(target);
shape::ToifImage::new(center, theme::ICON_TICK_FAT.toif)
.with_align(Alignment2D::CENTER)
.with_fg(theme::FG)
.render(target);
}
// BOTTOM
let top_rest = text_multiline_bottom2(
target,
self.area,
TR::coinjoin__do_not_disconnect.into(),
Font::BOLD,
theme::FG,
theme::BG,
Alignment::Center,
);
if let Some(rest) = top_rest {
text_multiline_bottom2(
target,
rest.inset(Insets::bottom(FOOTER_TEXT_MARGIN)),
self.text,
Font::NORMAL,
theme::FG,
theme::BG,
Alignment::Center,
);
}
}
}
#[cfg(feature = "ui_debug")]

@ -5,6 +5,9 @@ use crate::{
constant::{screen, WIDTH},
display,
geometry::{Alignment2D, Offset, Point, Rect},
model_tr::cshape,
shape,
shape::Renderer,
},
};
@ -98,4 +101,29 @@ impl Component for ErrorScreen<'_> {
self.footer.paint();
}
fn render<'s>(&'s self, target: &mut impl Renderer<'s>) {
self.bg.render(target);
if self.show_icons {
shape::ToifImage::new(screen().top_left(), theme::ICON_WARN_TITLE.toif)
.with_align(Alignment2D::TOP_LEFT)
.with_fg(FG)
.render(target);
shape::ToifImage::new(screen().top_right(), theme::ICON_WARN_TITLE.toif)
.with_align(Alignment2D::TOP_RIGHT)
.with_fg(FG)
.render(target);
}
self.title.render(target);
self.message.render(target);
cshape::HorizontalLine::new(Point::new(0, DIVIDER_POSITION), WIDTH)
.with_step(3)
.with_color(FG)
.render(target);
self.footer.render(target);
}
}

@ -3,6 +3,7 @@ use crate::{
ui::{
component::{Child, Component, ComponentExt, Event, EventCtx, Pad, Paginate},
geometry::Rect,
shape::Renderer,
},
};
@ -305,6 +306,23 @@ where
// (and painting buttons last would cover the lower part).
self.current_page.paint();
}
fn render<'s>(&'s self, target: &mut impl Renderer<'s>) {
self.pad.render(target);
// Scrollbars are painted only with a title and when requested
if self.title.is_some() {
if self.show_scrollbar {
self.scrollbar.render(target);
}
self.title.render(target);
}
self.buttons.render(target);
// On purpose painting current page at the end, after buttons,
// because we sometimes (in the case of QR code) need to use the
// whole height of the display for showing the content
// (and painting buttons last would cover the lower part).
self.current_page.render(target);
}
}
// DEBUG-ONLY SECTION BELOW

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save