From 6ef213e858ffbb6f8b2818f9856a26a19bc0240b Mon Sep 17 00:00:00 2001 From: obrusvit Date: Mon, 25 Mar 2024 10:45:04 +0100 Subject: [PATCH] feat(core): T3T1 corner highlight shape Adds a new shape which serves to highlight information within a rectangle. --- core/embed/rust/src/ui/geometry.rs | 2 +- .../rust/src/ui/shape/corner_highlight.rs | 218 ++++++++++++++++++ core/embed/rust/src/ui/shape/mod.rs | 3 + 3 files changed, 222 insertions(+), 1 deletion(-) create mode 100644 core/embed/rust/src/ui/shape/corner_highlight.rs diff --git a/core/embed/rust/src/ui/geometry.rs b/core/embed/rust/src/ui/geometry.rs index 5e0c2393e..251a7c1c2 100644 --- a/core/embed/rust/src/ui/geometry.rs +++ b/core/embed/rust/src/ui/geometry.rs @@ -557,7 +557,7 @@ pub enum Alignment { End, } -#[derive(Copy, Clone)] +#[derive(Copy, Clone, PartialEq, Eq)] pub struct Alignment2D(pub Alignment, pub Alignment); impl Alignment2D { diff --git a/core/embed/rust/src/ui/shape/corner_highlight.rs b/core/embed/rust/src/ui/shape/corner_highlight.rs new file mode 100644 index 000000000..fe1ed55a8 --- /dev/null +++ b/core/embed/rust/src/ui/shape/corner_highlight.rs @@ -0,0 +1,218 @@ +use crate::ui::{ + canvas::Canvas, + display::Color, + geometry::{Alignment2D, Offset, Point, Rect}, + shape::{DrawingCache, Renderer, Shape, ShapeClone}, +}; +use without_alloc::alloc::LocalAllocLeakExt; + +#[derive(Clone, Copy)] +pub enum CornerPosition { + TopLeft, + TopRight, + BotLeft, + BotRight, +} + +/// The shape placed in the corners highlihting important information within. +pub struct CornerHighlight { + /// Base point, horizontal and vertical lines crossing `pos` create tangents + /// to the shape + pos: Point, + /// Where is shape located, determines orientation of the shape + corner: CornerPosition, + /// Color of the shape - uniform and filled + color: Color, + /// Background color + bg_color: Color, + /// Outer radius + r_outer: i16, + /// Tail-end radius + r_tail: i16, + /// Inner radius + r_inner: i16, + /// Total lengths of the shape from the base point to the end of any tail + length: i16, + /// Thickness (default 3) + thickness: i16, + /// Alpha (default 255) + alpha: u8, + /// Helper point for working with rectangles, offset of `pos` + pos_rect: Point, +} + +impl CornerHighlight { + pub fn new(pos: Point, corner: CornerPosition, color: Color, bg_color: Color) -> Self { + Self { + pos, + corner, + color, + bg_color, + r_outer: 3, + r_tail: 1, + r_inner: 1, + length: 8, + thickness: 3, + alpha: 255, + pos_rect: pos + Offset::rect_offset(corner), + } + } + + pub fn from_rect(rect: Rect, color: Color, bg_color: Color) -> (Self, Self, Self, Self) { + let [top_left_corner, top_right_corner, bot_right_corner, bot_left_corner] = + rect.corner_points(); + let top_left = + CornerHighlight::new(top_left_corner, CornerPosition::TopLeft, color, bg_color); + let top_right = + CornerHighlight::new(top_right_corner, CornerPosition::TopRight, color, bg_color); + let bot_left = + CornerHighlight::new(bot_left_corner, CornerPosition::BotLeft, color, bg_color); + let bot_right = + CornerHighlight::new(bot_right_corner, CornerPosition::BotRight, color, bg_color); + (top_left, top_right, bot_left, bot_right) + } + + pub fn render<'s>(self, renderer: &mut impl Renderer<'s>) { + renderer.render_shape(self); + } + + fn rect_visible_part_base(&self, p: Point) -> (Point, Point) { + let (horizontal, vertical) = match self.corner { + CornerPosition::TopLeft => (p + Offset::x(self.r_outer), p + Offset::y(self.r_outer)), + CornerPosition::TopRight => (p - Offset::x(self.r_outer), p + Offset::y(self.r_outer)), + CornerPosition::BotLeft => (p + Offset::x(self.r_outer), p - Offset::y(self.r_outer)), + CornerPosition::BotRight => (p - Offset::x(self.r_outer), p - Offset::y(self.r_outer)), + }; + (horizontal, vertical) + } +} + +impl Shape<'_> for CornerHighlight { + fn bounds(&self, _cache: &DrawingCache) -> Rect { + Rect::snap( + self.pos_rect, + Offset::uniform(self.length), + self.corner.into(), + ) + } + + fn draw(&mut self, canvas: &mut dyn Canvas, cache: &DrawingCache<'_>) { + let align: Alignment2D = self.corner.into(); + + // base circle + let circle_center = self.pos + Offset::uniform(self.r_outer).rotate(self.corner); + let circle_visible_part = Rect::snap(self.pos_rect, Offset::uniform(self.r_outer), align); + in_clip(canvas, circle_visible_part, &|can| { + can.fill_circle(circle_center, self.r_outer, self.color); + }); + + // rectangles (rounded) tailing from a corner + let (rect_horz_base, rect_vert_base) = self.rect_visible_part_base(self.pos_rect); + + // vertical tail + let rect_tail_vert = Rect::snap( + self.pos_rect, + Offset::new(self.thickness, self.length), + align, + ); + let rect_vert_visible_part = Rect::snap( + rect_vert_base, + Offset::new(self.thickness, self.length - self.r_outer), + align, + ); + in_clip(canvas, rect_vert_visible_part, &|can| { + can.fill_round_rect(rect_tail_vert, self.r_tail, self.color, self.alpha) + }); + + // horizontal tail + let rect_tail_horz = Rect::snap( + self.pos_rect, + Offset::new(self.length, self.thickness), + align, + ); + let rect_horz_visible_part = Rect::snap( + rect_horz_base, + Offset::new(self.length - self.r_outer, self.thickness), + align, + ); + in_clip(canvas, rect_horz_visible_part, &|can| { + can.fill_round_rect(rect_tail_horz, self.r_tail, self.color, self.alpha) + }); + + // inner radius by a rectangle (shape color) and a circle (background color) + let rect_outer_base = self.pos_rect + Offset::uniform(self.thickness).rotate(self.corner); + let rect_outer_fill = Rect::snap(rect_outer_base, Offset::uniform(self.r_inner), align); + let circle_cover_center = + self.pos + Offset::uniform(self.thickness + self.r_inner).rotate(self.corner); + in_clip(canvas, rect_outer_fill, &|can| { + can.fill_rect(rect_outer_fill, self.color, self.alpha); + can.fill_circle(circle_cover_center, self.r_inner, self.bg_color); + }); + } + + fn cleanup(&mut self, cache: &super::DrawingCache<'_>) {} +} + +impl<'s> ShapeClone<'s> for CornerHighlight { + fn clone_at_bump<'alloc, T>(self, bump: &'alloc T) -> Option<&'alloc mut dyn Shape<'s>> + where + T: LocalAllocLeakExt<'alloc>, + { + let clone = bump.alloc_t::()?; + Some(clone.uninit.init(CornerHighlight { ..self })) + } +} + +// Helper functions + +fn in_clip(canvas: &mut dyn Canvas, r: Rect, inner: &dyn Fn(&mut dyn Canvas)) { + // TODO: like Renderer::in_clip, replace by Canvas::in_clip when done + let original = canvas.set_clip(r); + inner(canvas); + canvas.set_viewport(original); +} + +impl From for Alignment2D { + fn from(corner: CornerPosition) -> Self { + match corner { + CornerPosition::TopLeft => Alignment2D::TOP_LEFT, + CornerPosition::TopRight => Alignment2D::TOP_RIGHT, + CornerPosition::BotLeft => Alignment2D::BOTTOM_LEFT, + CornerPosition::BotRight => Alignment2D::BOTTOM_RIGHT, + } + } +} + +impl Offset { + fn rect_offset(corner: CornerPosition) -> Offset { + match corner { + CornerPosition::TopLeft => Offset::zero(), + CornerPosition::TopRight => Offset::x(1), + CornerPosition::BotLeft => Offset::y(1), + CornerPosition::BotRight => Offset::uniform(1), + } + } + + /// If Offset `self` is calculated for TopLeft (x,y positive) then this + /// function rotates the offset for other corners. + fn rotate(&self, corner: CornerPosition) -> Offset { + match corner { + CornerPosition::TopLeft => Offset { + x: self.x, + y: self.y, + }, + CornerPosition::TopRight => Offset { + x: -self.x, + y: self.y, + }, + CornerPosition::BotLeft => Offset { + x: self.x, + y: -self.y, + }, + CornerPosition::BotRight => Offset { + x: -self.x, + y: -self.y, + }, + } + } +} diff --git a/core/embed/rust/src/ui/shape/mod.rs b/core/embed/rust/src/ui/shape/mod.rs index 63f5fe786..474a8fbb8 100644 --- a/core/embed/rust/src/ui/shape/mod.rs +++ b/core/embed/rust/src/ui/shape/mod.rs @@ -9,6 +9,7 @@ mod jpeg; mod model; mod qrcode; mod render; +mod corner_highlight; mod text; mod toif; @@ -23,5 +24,7 @@ pub use jpeg::JpegImage; pub use model::render_on_display; pub use qrcode::QrImage; pub use render::{DirectRenderer, ProgressiveRenderer, Renderer}; +pub use corner_highlight::CornerHighlight; pub use text::Text; pub use toif::ToifImage; +