refactor(core/rust): geometry/display API improvements

[no changelog]
mmilata/tt-pin-keyboard
Martin Milata 2 years ago
parent f2b8822d76
commit b5da6dc911

@ -23,7 +23,7 @@ where
T: Deref<Target = [u8]>, T: Deref<Target = [u8]>,
{ {
pub fn new(origin: Point, align: Alignment, text: T, style: LabelStyle) -> Self { pub fn new(origin: Point, align: Alignment, text: T, style: LabelStyle) -> Self {
let width = display::text_width(&text, style.font); let width = style.font.text_width(&text);
let height = style.font.line_height(); let height = style.font.line_height();
let area = match align { let area = match align {
// `origin` is the top-left point. // `origin` is the top-left point.

@ -26,7 +26,7 @@ impl Pad {
if self.clear { if self.clear {
self.clear = false; self.clear = false;
display::rect(self.area, self.color); display::rect_fill(self.area, self.color);
} }
} }
} }

@ -397,7 +397,7 @@ impl Span {
// break. // break.
let mut line = Self { let mut line = Self {
length: 0, length: 0,
advance: Offset::new(0, text_font.line_height()), advance: Offset::y(text_font.line_height()),
insert_hyphen_before_line_break: false, insert_hyphen_before_line_break: false,
skip_next_chars: 0, skip_next_chars: 0,
}; };
@ -447,7 +447,7 @@ impl Span {
// The whole text is fitting. // The whole text is fitting.
Self { Self {
length: text.len(), length: text.len(),
advance: Offset::new(span_width, 0), advance: Offset::x(span_width),
insert_hyphen_before_line_break: false, insert_hyphen_before_line_break: false,
skip_next_chars: 0, skip_next_chars: 0,
} }

@ -189,7 +189,7 @@ where
match fit { match fit {
LayoutFit::Fitting { size, .. } => { LayoutFit::Fitting { size, .. } => {
// Text fits, update the bounding box. // Text fits, update the bounding box.
let (used, free) = area.hsplit(size.y); let (used, free) = area.split_top(size.y);
paragraph.set_area(used); paragraph.set_area(used);
// Continue with next paragraph in remaining space. // Continue with next paragraph in remaining space.
area = free; area = free;

@ -49,11 +49,19 @@ pub fn fade_backlight(target: i32) {
} }
} }
pub fn rect(r: Rect, fg_color: Color) { pub fn rect_fill(r: Rect, fg_color: Color) {
display::bar(r.x0, r.y0, r.width(), r.height(), fg_color.into()); display::bar(r.x0, r.y0, r.width(), r.height(), fg_color.into());
} }
pub fn rounded_rect(r: Rect, fg_color: Color, bg_color: Color, radius: u8) { pub fn rect_stroke(r: Rect, fg_color: Color) {
display::bar(r.x0, r.y0, r.width(), 1, fg_color.into());
display::bar(r.x0, r.y0 + r.height() - 1, r.width(), 1, fg_color.into());
display::bar(r.x0, r.y0, 1, r.height(), fg_color.into());
display::bar(r.x0 + r.width() - 1, r.y0, 1, r.height(), fg_color.into());
}
pub fn rect_fill_rounded(r: Rect, fg_color: Color, bg_color: Color, radius: u8) {
assert!([2, 4, 8, 16].iter().any(|allowed| radius == *allowed));
display::bar_radius( display::bar_radius(
r.x0, r.x0,
r.y0, r.y0,
@ -65,6 +73,22 @@ pub fn rounded_rect(r: Rect, fg_color: Color, bg_color: Color, radius: u8) {
); );
} }
/// NOTE: Cannot start at odd x-coordinate. In this case icon is shifted 1px
/// left.
pub fn icon_top_left(top_left: Point, data: &[u8], fg_color: Color, bg_color: Color) {
let toif_info = display::toif_info(data).unwrap();
assert!(toif_info.grayscale);
display::icon(
top_left.x,
top_left.y,
toif_info.width.into(),
toif_info.height.into(),
&data[12..], // Skip TOIF header.
fg_color.into(),
bg_color.into(),
);
}
pub fn icon(center: Point, data: &[u8], fg_color: Color, bg_color: Color) { pub fn icon(center: Point, data: &[u8], fg_color: Color, bg_color: Color) {
let toif_info = display::toif_info(data).unwrap(); let toif_info = display::toif_info(data).unwrap();
assert!(toif_info.grayscale); assert!(toif_info.grayscale);
@ -85,13 +109,13 @@ pub fn icon(center: Point, data: &[u8], fg_color: Color, bg_color: Color) {
} }
// Used on T1 only. // Used on T1 only.
pub fn rounded_rect1(r: Rect, fg_color: Color, bg_color: Color) { pub fn rect_fill_rounded1(r: Rect, fg_color: Color, bg_color: Color) {
display::bar(r.x0, r.y0, r.width(), r.height(), fg_color.into()); display::bar(r.x0, r.y0, r.width(), r.height(), fg_color.into());
let corners = [ let corners = [
r.top_left(), r.top_left(),
r.top_right() + Offset::new(-1, 0), r.top_right() - Offset::x(1),
r.bottom_right() + Offset::new(-1, -1), r.bottom_right() - Offset::uniform(1),
r.bottom_left() + Offset::new(0, -1), r.bottom_left() - Offset::y(1),
]; ];
for p in corners.iter() { for p in corners.iter() {
display::bar(p.x, p.y, 1, 1, bg_color.into()); display::bar(p.x, p.y, 1, 1, bg_color.into());
@ -156,7 +180,7 @@ pub fn text(baseline: Point, text: &[u8], font: Font, fg_color: Color, bg_color:
} }
pub fn text_center(baseline: Point, text: &[u8], font: Font, fg_color: Color, bg_color: Color) { pub fn text_center(baseline: Point, text: &[u8], font: Font, fg_color: Color, bg_color: Color) {
let w = text_width(text, font); let w = font.text_width(text);
display::text( display::text(
baseline.x - w / 2, baseline.x - w / 2,
baseline.y, baseline.y,
@ -167,10 +191,6 @@ pub fn text_center(baseline: Point, text: &[u8], font: Font, fg_color: Color, bg
); );
} }
pub fn text_width(text: &[u8], font: Font) -> i32 {
display::text_width(text, font.0)
}
#[derive(Copy, Clone, PartialEq, Eq)] #[derive(Copy, Clone, PartialEq, Eq)]
pub struct Font(i32); pub struct Font(i32);
@ -180,7 +200,7 @@ impl Font {
} }
pub fn text_width(self, text: &[u8]) -> i32 { pub fn text_width(self, text: &[u8]) -> i32 {
text_width(text, self) display::text_width(text, self.0)
} }
pub fn text_height(self) -> i32 { pub fn text_height(self) -> i32 {

@ -22,6 +22,14 @@ impl Offset {
Self::new(0, 0) Self::new(0, 0)
} }
pub const fn x(x: i32) -> Self {
Self::new(x, 0)
}
pub const fn y(y: i32) -> Self {
Self::new(0, y)
}
pub fn on_axis(axis: Axis, a: i32) -> Self { pub fn on_axis(axis: Axis, a: i32) -> Self {
match axis { match axis {
Axis::Horizontal => Self::new(a, 0), Axis::Horizontal => Self::new(a, 0),
@ -29,6 +37,13 @@ impl Offset {
} }
} }
pub fn axis(&self, axis: Axis) -> i32 {
match axis {
Axis::Horizontal => self.x,
Axis::Vertical => self.y,
}
}
pub fn abs(self) -> Self { pub fn abs(self) -> Self {
Self::new(self.x.abs(), self.y.abs()) Self::new(self.x.abs(), self.y.abs())
} }
@ -165,16 +180,20 @@ impl Rect {
self.top_left().center(self.bottom_right()) self.top_left().center(self.bottom_right())
} }
pub fn bottom_center(&self) -> Point {
self.bottom_left().center(self.bottom_right())
}
pub fn contains(&self, point: Point) -> bool { pub fn contains(&self, point: Point) -> bool {
point.x >= self.x0 && point.x < self.x1 && point.y >= self.y0 && point.y < self.y1 point.x >= self.x0 && point.x < self.x1 && point.y >= self.y0 && point.y < self.y1
} }
pub fn inset(&self, uniform: i32) -> Self { pub fn inset(&self, insets: Insets) -> Self {
Self { Self {
x0: self.x0 + uniform, x0: self.x0 + insets.left,
y0: self.y0 + uniform, y0: self.y0 + insets.top,
x1: self.x1 - uniform, x1: self.x1 - insets.right,
y1: self.y1 - uniform, y1: self.y1 - insets.bottom,
} }
} }
@ -196,53 +215,80 @@ impl Rect {
} }
} }
pub fn hsplit(self, height: i32) -> (Self, Self) { pub fn split_top(self, height: i32) -> (Self, Self) {
let height = if height.is_negative() { let height = height.clamp(0, self.height());
self.height() + height
} else {
height
};
let top = Self { let top = Self {
x0: self.x0,
y0: self.y0,
x1: self.x1,
y1: self.y0 + height, y1: self.y0 + height,
..self
}; };
let bottom = Self { let bottom = Self {
x0: self.x0,
y0: self.y0 + height, y0: self.y0 + height,
x1: self.x1, ..self
y1: self.y1,
}; };
(top, bottom) (top, bottom)
} }
pub fn vsplit(self, width: i32) -> (Self, Self) { pub fn split_bottom(self, height: i32) -> (Self, Self) {
let width = if width.is_negative() { self.split_top(self.height() - height)
self.width() + width }
} else {
width pub fn split_left(self, width: i32) -> (Self, Self) {
}; let width = width.clamp(0, self.width());
let left = Self { let left = Self {
x0: self.x0,
y0: self.y0,
x1: self.x0 + width, x1: self.x0 + width,
y1: self.y1, ..self
}; };
let right = Self { let right = Self {
x0: self.x0 + width, x0: self.x0 + width,
y0: self.y0, ..self
x1: self.x1,
y1: self.y1,
}; };
(left, right) (left, right)
} }
pub fn split_right(self, width: i32) -> (Self, Self) {
self.split_left(self.width() - width)
}
}
#[derive(Copy, Clone, PartialEq, Eq)]
pub struct Insets {
pub top: i32,
pub right: i32,
pub bottom: i32,
pub left: i32,
}
impl Insets {
pub const fn new(top: i32, right: i32, bottom: i32, left: i32) -> Self {
Self {
top,
right,
bottom,
left,
}
}
pub const fn uniform(d: i32) -> Self {
Self::new(d, d, d, d)
}
pub const fn top(d: i32) -> Self {
Self::new(d, 0, 0, 0)
}
pub const fn right(d: i32) -> Self {
Self::new(0, d, 0, 0)
}
pub const fn bottom(d: i32) -> Self {
Self::new(0, 0, d, 0)
}
pub const fn left(d: i32) -> Self {
Self::new(0, 0, 0, d)
}
} }
#[derive(Copy, Clone, PartialEq, Eq)] #[derive(Copy, Clone, PartialEq, Eq)]
@ -295,17 +341,50 @@ impl Grid {
} }
} }
pub fn with_spacing(mut self, spacing: i32) -> Self {
self.spacing = spacing;
self
}
pub fn row_col(&self, row: usize, col: usize) -> Rect { pub fn row_col(&self, row: usize, col: usize) -> Rect {
let cell_width = self.area.width() / self.cols as i32; let ncols = self.cols as i32;
let cell_height = self.area.height() / self.rows as i32; let nrows = self.rows as i32;
let x = col as i32 * cell_width; let col = (col as i32).min(ncols - 1);
let y = row as i32 * cell_height; let row = (row as i32).min(nrows - 1);
Rect {
x0: self.area.x0 + x, // Total number of horizontal pixels used for spacing.
y0: self.area.y0 + y, let spacing_width = self.spacing * (ncols - 1);
x1: self.area.x0 + x + (cell_width - self.spacing), let spacing_height = self.spacing * (nrows - 1);
y1: self.area.y0 + y + (cell_height - self.spacing),
// Divide what is left by number of cells to obtain width of each cell.
let cell_width = (self.area.width() - spacing_width) / ncols;
let cell_height = (self.area.height() - spacing_height) / nrows;
// Not every area can be fully covered by equal-sized cells and spaces, there
// might be serveral pixels left unused. We'll distribute them by 1px to
// the leftmost cells.
let leftover_width = (self.area.width() - spacing_width) % ncols;
let leftover_height = (self.area.height() - spacing_height) % nrows;
let mut top_left = self.area.top_left()
+ Offset::new(
col * (cell_width + self.spacing),
row * (cell_height + self.spacing),
);
// Some previous cells were 1px wider.
top_left.x += leftover_width.min(col);
top_left.y += leftover_height.min(row);
let mut size = Offset::new(cell_width, cell_height);
// This cell might be 1px wider.
if col < leftover_width {
size.x += 1
} }
if row < leftover_height {
size.y += 1
}
Rect::from_top_left_and_size(top_left, size)
} }
pub fn cell(&self, index: usize) -> Rect { pub fn cell(&self, index: usize) -> Rect {
@ -357,45 +436,67 @@ impl LinearLayout {
self self
} }
/// Arranges all `items` by parameters configured in `self` into `area`. fn compute_spacing(&self, area: Rect, count: usize, size_sum: i32) -> (i32, i32) {
/// Does not change the size of the items (only the position), but it needs let spacing_count = count.saturating_sub(1);
/// to iterate (and ask for the size) twice.
pub fn arrange(&self, area: Rect, items: &mut [impl Dimensions]) {
let item_sum: i32 = items
.iter_mut()
.map(|i| {
let size = i.get_size();
self.axis.main(size.x, size.y)
})
.sum();
let spacing_count = items.len().saturating_sub(1);
let spacing_sum = spacing_count as i32 * self.spacing; let spacing_sum = spacing_count as i32 * self.spacing;
let naive_size = item_sum + spacing_sum; let naive_size = size_sum + spacing_sum;
let available_space = match self.axis { let available_space = area.size().axis(self.axis);
Axis::Horizontal => area.width(),
Axis::Vertical => area.height(),
};
// scale down spacing to fit everything into area // scale down spacing to fit everything into area
let (total_size, spacing) = if naive_size > available_space { let (total_size, spacing) = if naive_size > available_space {
let scaled_space = (available_space - item_sum) / spacing_count as i32; let scaled_space = (available_space - size_sum) / spacing_count as i32;
// forbid negative spacing // forbid negative spacing
(available_space, scaled_space.max(0)) (available_space, scaled_space.max(0))
} else { } else {
(naive_size, self.spacing) (naive_size, self.spacing)
}; };
let mut cursor = match self.align { let init_cursor = match self.align {
Alignment::Start => 0, Alignment::Start => 0,
Alignment::Center => available_space / 2 - total_size / 2, Alignment::Center => available_space / 2 - total_size / 2,
Alignment::End => available_space - total_size, Alignment::End => available_space - total_size,
}; };
(init_cursor, spacing)
}
/// Arranges all `items` by parameters configured in `self` into `area`.
/// Does not change the size of the items (only the position), but it needs
/// to iterate (and ask for the size) twice.
pub fn arrange(&self, area: Rect, items: &mut [impl Dimensions]) {
let item_sum: i32 = items.iter_mut().map(|i| i.get_size().axis(self.axis)).sum();
let (mut cursor, spacing) = self.compute_spacing(area, items.len(), item_sum);
for item in items { for item in items {
let top_left = area.top_left() + Offset::on_axis(self.axis, cursor); let top_left = area.top_left() + Offset::on_axis(self.axis, cursor);
let size = item.get_size(); let size = item.get_size();
item.set_area(Rect::from_top_left_and_size(top_left, size)); item.set_area(Rect::from_top_left_and_size(top_left, size));
cursor += self.axis.main(size.x, size.y); cursor += size.axis(self.axis);
cursor += spacing;
}
}
/// Arranges number of items of the same size into `area`. The `sink`
/// closure is called `count` times with top left point of each item as
/// argument. Items are centered along the cross axis.
pub fn arrange_uniform(
&self,
area: Rect,
count: usize,
size: Offset,
sink: &mut dyn FnMut(Point),
) {
let item_size = size.axis(self.axis);
let (mut cursor, spacing) = self.compute_spacing(area, count, (count as i32) * item_size);
let cross_coord =
area.size().axis(self.axis.cross()) / 2 - size.axis(self.axis.cross()) / 2;
for _ in 0..count {
let top_left = area.top_left()
+ Offset::on_axis(self.axis, cursor)
+ Offset::on_axis(self.axis.cross(), cross_coord);
sink(top_left);
cursor += item_size;
cursor += spacing; cursor += spacing;
} }
} }

@ -94,13 +94,13 @@ impl<T: AsRef<[u8]>> Button<T> {
) -> (Rect, Point) { ) -> (Rect, Point) {
let border_width = if styles.normal.border_horiz { 2 } else { 0 }; let border_width = if styles.normal.border_horiz { 2 } else { 0 };
let content_width = match content { let content_width = match content {
ButtonContent::Text(text) => display::text_width(text.as_ref(), styles.normal.font) - 1, ButtonContent::Text(text) => styles.normal.font.text_width(text.as_ref()) - 1,
ButtonContent::Icon(_icon) => todo!(), ButtonContent::Icon(_icon) => todo!(),
}; };
let button_width = content_width + 2 * border_width; let button_width = content_width + 2 * border_width;
let area = match pos { let area = match pos {
ButtonPos::Left => area.vsplit(button_width).0, ButtonPos::Left => area.split_left(button_width).0,
ButtonPos::Right => area.vsplit(-button_width).1, ButtonPos::Right => area.split_right(button_width).1,
}; };
let start_of_baseline = area.bottom_left() + Offset::new(border_width, -2); let start_of_baseline = area.bottom_left() + Offset::new(border_width, -2);
@ -135,9 +135,9 @@ impl<T: AsRef<[u8]>> Component for Button<T> {
ButtonContent::Text(text) => { ButtonContent::Text(text) => {
let background_color = style.text_color.neg(); let background_color = style.text_color.neg();
if style.border_horiz { if style.border_horiz {
display::rounded_rect1(self.area, background_color, theme::BG); display::rect_fill_rounded1(self.area, background_color, theme::BG);
} else { } else {
display::rect(self.area, background_color) display::rect_fill(self.area, background_color)
} }
display::text( display::text(

@ -39,7 +39,7 @@ impl<T: Component, U: AsRef<[u8]>> Dialog<T, U> {
fn areas(area: Rect) -> (Rect, Rect) { fn areas(area: Rect) -> (Rect, Rect) {
let button_height = theme::FONT_BOLD.line_height() + 2; let button_height = theme::FONT_BOLD.line_height() + 2;
let (content_area, button_area) = area.hsplit(-button_height); let (content_area, button_area) = area.split_bottom(button_height);
(content_area, button_area) (content_area, button_area)
} }
} }

@ -25,8 +25,8 @@ impl<T: Component, U: AsRef<[u8]>> Frame<T, U> {
const HEADER_SPACE: i32 = 4; const HEADER_SPACE: i32 = 4;
let header_height = theme::FONT_BOLD.line_height(); let header_height = theme::FONT_BOLD.line_height();
let (header_area, content_area) = area.hsplit(header_height); let (header_area, content_area) = area.split_top(header_height);
let (_space, content_area) = content_area.hsplit(HEADER_SPACE); let (_space, content_area) = content_area.split_top(HEADER_SPACE);
(header_area, content_area) (header_area, content_area)
} }
@ -41,7 +41,7 @@ impl<T: Component, U: AsRef<[u8]>> Component for Frame<T, U> {
fn paint(&mut self) { fn paint(&mut self) {
display::text( display::text(
self.area.bottom_left() + Offset::new(0, -2), self.area.bottom_left() - Offset::y(2),
self.title.as_ref(), self.title.as_ref(),
theme::FONT_BOLD, theme::FONT_BOLD,
theme::FG, theme::FG,

@ -1,7 +1,7 @@
use crate::ui::{ use crate::ui::{
component::{Component, ComponentExt, Event, EventCtx, Never, Pad, PageMsg, Paginate}, component::{Component, ComponentExt, Event, EventCtx, Never, Pad, PageMsg, Paginate},
display::{self, Color}, display::{self, Color},
geometry::{Offset, Point, Rect}, geometry::{Insets, Offset, Point, Rect},
}; };
use super::{theme, Button, ButtonMsg, ButtonPos}; use super::{theme, Button, ButtonMsg, ButtonPos};
@ -63,9 +63,9 @@ where
fn areas(area: Rect) -> (Rect, Rect, Rect) { fn areas(area: Rect) -> (Rect, Rect, Rect) {
let button_height = theme::FONT_BOLD.line_height() + 2; let button_height = theme::FONT_BOLD.line_height() + 2;
let (content_area, button_area) = area.hsplit(-button_height); let (content_area, button_area) = area.split_bottom(button_height);
let (content_area, scrollbar_area) = content_area.vsplit(-ScrollBar::WIDTH); let (content_area, scrollbar_area) = content_area.split_right(ScrollBar::WIDTH);
let (content_area, _) = content_area.hsplit(-1); let (content_area, _) = content_area.split_bottom(1);
(content_area, scrollbar_area, button_area) (content_area, scrollbar_area, button_area)
} }
@ -182,8 +182,8 @@ impl ScrollBar {
fn paint_dot(&self, active: bool, top_left: Point) { fn paint_dot(&self, active: bool, top_left: Point) {
let sides = [ let sides = [
Rect::from_top_left_and_size(top_left + Offset::new(1, 0), Offset::new(2, 1)), Rect::from_top_left_and_size(top_left + Offset::x(1), Offset::new(2, 1)),
Rect::from_top_left_and_size(top_left + Offset::new(0, 1), Offset::new(1, 2)), Rect::from_top_left_and_size(top_left + Offset::y(1), Offset::new(1, 2)),
Rect::from_top_left_and_size( Rect::from_top_left_and_size(
top_left + Offset::new(1, Self::DOT_SIZE.y - 1), top_left + Offset::new(1, Self::DOT_SIZE.y - 1),
Offset::new(2, 1), Offset::new(2, 1),
@ -194,11 +194,11 @@ impl ScrollBar {
), ),
]; ];
for side in sides { for side in sides {
display::rect(side, theme::FG) display::rect_fill(side, theme::FG)
} }
if active { if active {
display::rect( display::rect_fill(
Rect::from_top_left_and_size(top_left, Self::DOT_SIZE).inset(1), Rect::from_top_left_and_size(top_left, Self::DOT_SIZE).inset(Insets::uniform(1)),
theme::FG, theme::FG,
) )
} }

@ -2,7 +2,7 @@ use super::{event::TouchEvent, theme};
use crate::ui::{ use crate::ui::{
component::{Component, Event, EventCtx, Map}, component::{Component, Event, EventCtx, Map},
display::{self, Color, Font}, display::{self, Color, Font},
geometry::{Grid, Offset, Rect}, geometry::{Grid, Insets, Offset, Rect},
}; };
pub enum ButtonMsg { pub enum ButtonMsg {
@ -160,14 +160,14 @@ where
if style.border_width > 0 { if style.border_width > 0 {
// Paint the border and a smaller background on top of it. // Paint the border and a smaller background on top of it.
display::rounded_rect( display::rect_fill_rounded(
self.area, self.area,
style.border_color, style.border_color,
style.background_color, style.background_color,
style.border_radius, style.border_radius,
); );
display::rounded_rect( display::rect_fill_rounded(
self.area.inset(style.border_width), self.area.inset(Insets::uniform(style.border_width)),
style.button_color, style.button_color,
style.border_color, style.border_color,
style.border_radius, style.border_radius,
@ -175,7 +175,7 @@ where
} else { } else {
// We do not need to draw an explicit border in this case, just a // We do not need to draw an explicit border in this case, just a
// bigger background. // bigger background.
display::rounded_rect( display::rect_fill_rounded(
self.area, self.area,
style.button_color, style.button_color,
style.background_color, style.background_color,

@ -29,9 +29,9 @@ where
const HEADER_SPACE: i32 = 14; const HEADER_SPACE: i32 = 14;
let header_height = theme::FONT_BOLD.line_height() - theme::CONTENT_BORDER; let header_height = theme::FONT_BOLD.line_height() - theme::CONTENT_BORDER;
let (header_area, content_area) = area.hsplit(header_height); let (header_area, content_area) = area.split_top(header_height);
let (_space, header_area) = header_area.vsplit(theme::CONTENT_BORDER); let (_space, header_area) = header_area.split_left(theme::CONTENT_BORDER);
let (_space, content_area) = content_area.hsplit(HEADER_SPACE); let (_space, content_area) = content_area.split_top(HEADER_SPACE);
(header_area, content_area) (header_area, content_area)
} }

@ -3,7 +3,7 @@ use crate::ui::{
base::ComponentExt, paginated::PageMsg, Component, Event, EventCtx, Never, Pad, Paginate, base::ComponentExt, paginated::PageMsg, Component, Event, EventCtx, Never, Pad, Paginate,
}, },
display::{self, Color}, display::{self, Color},
geometry::{Dimensions, Offset, Point, Rect}, geometry::{Dimensions, LinearLayout, Offset, Rect},
}; };
use super::{theme, Button, Swipe, SwipeDirection}; use super::{theme, Button, Swipe, SwipeDirection};
@ -82,7 +82,7 @@ where
fn paint_hint(&mut self) { fn paint_hint(&mut self) {
display::text_center( display::text_center(
Point::new(self.pad.area.center().x, self.pad.area.bottom_right().y - 3), self.pad.area.bottom_center() - Offset::y(3),
b"SWIPE TO CONTINUE", b"SWIPE TO CONTINUE",
theme::FONT_BOLD, // FIXME: Figma has this as 14px but bold is 16px theme::FONT_BOLD, // FIXME: Figma has this as 14px but bold is 16px
theme::GREY_LIGHT, theme::GREY_LIGHT,
@ -180,8 +180,11 @@ pub struct ScrollBar {
} }
impl ScrollBar { impl ScrollBar {
const DOT_INTERVAL: i32 = 12; const DOT_SIZE: i32 = 6;
const ARROW_SPACE: i32 = 23; /// Edge to edge.
const DOT_INTERVAL: i32 = 6;
/// Edge of last dot to center of arrow icon.
const ARROW_SPACE: i32 = 26;
const ICON_ACTIVE: &'static [u8] = include_res!("model_tt/res/scroll-active.toif"); const ICON_ACTIVE: &'static [u8] = include_res!("model_tt/res/scroll-active.toif");
const ICON_INACTIVE: &'static [u8] = include_res!("model_tt/res/scroll-inactive.toif"); const ICON_INACTIVE: &'static [u8] = include_res!("model_tt/res/scroll-inactive.toif");
@ -229,40 +232,42 @@ impl Component for ScrollBar {
} }
fn paint(&mut self) { fn paint(&mut self) {
let count = self.page_count as i32; let layout = LinearLayout::vertical()
let interval = { .align_at_center()
let available_height = self.area.height(); .with_spacing(Self::DOT_INTERVAL);
let naive_height = count * Self::DOT_INTERVAL;
if naive_height > available_height { let mut i = 0;
available_height / count let mut top = None;
let mut display_icon = |top_left| {
let icon = if i == self.active_page {
Self::ICON_ACTIVE
} else { } else {
Self::DOT_INTERVAL Self::ICON_INACTIVE
} };
display::icon_top_left(top_left, icon, theme::FG, theme::BG);
i += 1;
top.get_or_insert(top_left.x);
}; };
let mut dot = Point::new(
self.area.center().x, layout.arrange_uniform(
self.area.center().y - (count / 2) * interval, self.area,
self.page_count,
Offset::new(Self::DOT_SIZE, Self::DOT_SIZE),
&mut display_icon,
); );
let arrow_distance = self.area.center().x - top.unwrap_or(0) + Self::ARROW_SPACE;
if self.has_previous_page() { if self.has_previous_page() {
display::icon( display::icon(
dot - Offset::new(0, Self::ARROW_SPACE), self.area.center() - Offset::y(arrow_distance),
Self::ICON_UP, Self::ICON_UP,
theme::FG, theme::FG,
theme::BG, theme::BG,
); );
} }
for i in 0..self.page_count {
let icon = if i == self.active_page {
Self::ICON_ACTIVE
} else {
Self::ICON_INACTIVE
};
display::icon(dot, icon, theme::FG, theme::BG);
dot.y += interval;
}
if self.has_next_page() { if self.has_next_page() {
display::icon( display::icon(
dot + Offset::new(0, Self::ARROW_SPACE - interval), self.area.center() + Offset::y(arrow_distance),
Self::ICON_DOWN, Self::ICON_DOWN,
theme::FG, theme::FG,
theme::BG, theme::BG,
@ -284,13 +289,14 @@ impl PageLayout {
const SCROLLBAR_SPACE: i32 = 10; const SCROLLBAR_SPACE: i32 = 10;
pub fn new(area: Rect) -> Self { pub fn new(area: Rect) -> Self {
let (content, buttons) = area.hsplit(-Button::HEIGHT); let (content, buttons) = area.split_bottom(Button::<&str>::HEIGHT);
let (content, _space) = content.hsplit(-Self::BUTTON_SPACE); let (content, _space) = content.split_bottom(Self::BUTTON_SPACE);
let (buttons, _space) = buttons.vsplit(-theme::CONTENT_BORDER); let (buttons, _space) = buttons.split_right(theme::CONTENT_BORDER);
let (_space, content) = content.vsplit(theme::CONTENT_BORDER); let (_space, content) = content.split_left(theme::CONTENT_BORDER);
let (content_single_page, _space) = content.vsplit(-theme::CONTENT_BORDER); let (content_single_page, _space) = content.split_right(theme::CONTENT_BORDER);
let (content, scrollbar) = content.vsplit(-(Self::SCROLLBAR_SPACE + Self::SCROLLBAR_WIDTH)); let (content, scrollbar) =
let (_space, scrollbar) = scrollbar.vsplit(Self::SCROLLBAR_SPACE); content.split_right(Self::SCROLLBAR_SPACE + Self::SCROLLBAR_WIDTH);
let (_space, scrollbar) = scrollbar.split_left(Self::SCROLLBAR_SPACE);
Self { Self {
content_single_page, content_single_page,

@ -222,7 +222,7 @@ impl Component for PinDots {
fn paint(&mut self) { fn paint(&mut self) {
// Clear the area with the background color. // Clear the area with the background color.
display::rect(self.area, self.style.background_color); display::rect_fill(self.area, self.style.background_color);
// Draw a dot for each PIN digit. // Draw a dot for each PIN digit.
for i in 0..self.digit_count { for i in 0..self.digit_count {
@ -231,7 +231,7 @@ impl Component for PinDots {
y: self.area.center().y, y: self.area.center().y,
}; };
let size = Offset::new(Self::DOT, Self::DOT); let size = Offset::new(Self::DOT, Self::DOT);
display::rounded_rect( display::rect_fill_rounded(
Rect::from_top_left_and_size(pos, size), Rect::from_top_left_and_size(pos, size),
self.style.text_color, self.style.text_color,
self.style.background_color, self.style.background_color,

@ -1,7 +1,7 @@
use crate::ui::{ use crate::ui::{
component::{label::LabelStyle, text::layout::DefaultTextTheme}, component::{label::LabelStyle, text::layout::DefaultTextTheme},
display::{self, Color, Font}, display::{self, Color, Font},
geometry::Rect, geometry::{Insets, Rect},
}; };
use super::component::{ButtonStyle, ButtonStyleSheet, LoaderStyle, LoaderStyleSheet}; use super::component::{ButtonStyle, ButtonStyleSheet, LoaderStyle, LoaderStyleSheet};
@ -164,9 +164,5 @@ pub const CONTENT_BORDER: i32 = 5;
/// | 14 | /// | 14 |
/// +----------+ /// +----------+
pub fn borders() -> Rect { pub fn borders() -> Rect {
let (_left_border, area) = display::screen().vsplit(10); display::screen().inset(Insets::new(13, 5, 14, 10))
let (area, _right_area) = area.vsplit(-5);
let (_top_border, area) = area.hsplit(13);
let (area, _bottom_border) = area.hsplit(-14);
area
} }

Loading…
Cancel
Save