mirror of
https://github.com/trezor/trezor-firmware.git
synced 2025-01-10 15:30:55 +00:00
refactor(core/rust): geometry/display API improvements
[no changelog]
This commit is contained in:
parent
f2b8822d76
commit
b5da6dc911
@ -23,7 +23,7 @@ where
|
||||
T: Deref<Target = [u8]>,
|
||||
{
|
||||
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 area = match align {
|
||||
// `origin` is the top-left point.
|
||||
|
@ -26,7 +26,7 @@ impl Pad {
|
||||
if self.clear {
|
||||
self.clear = false;
|
||||
|
||||
display::rect(self.area, self.color);
|
||||
display::rect_fill(self.area, self.color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -397,7 +397,7 @@ impl Span {
|
||||
// break.
|
||||
let mut line = Self {
|
||||
length: 0,
|
||||
advance: Offset::new(0, text_font.line_height()),
|
||||
advance: Offset::y(text_font.line_height()),
|
||||
insert_hyphen_before_line_break: false,
|
||||
skip_next_chars: 0,
|
||||
};
|
||||
@ -447,7 +447,7 @@ impl Span {
|
||||
// The whole text is fitting.
|
||||
Self {
|
||||
length: text.len(),
|
||||
advance: Offset::new(span_width, 0),
|
||||
advance: Offset::x(span_width),
|
||||
insert_hyphen_before_line_break: false,
|
||||
skip_next_chars: 0,
|
||||
}
|
||||
|
@ -189,7 +189,7 @@ where
|
||||
match fit {
|
||||
LayoutFit::Fitting { size, .. } => {
|
||||
// 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);
|
||||
// Continue with next paragraph in remaining space.
|
||||
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());
|
||||
}
|
||||
|
||||
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(
|
||||
r.x0,
|
||||
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) {
|
||||
let toif_info = display::toif_info(data).unwrap();
|
||||
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.
|
||||
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());
|
||||
let corners = [
|
||||
r.top_left(),
|
||||
r.top_right() + Offset::new(-1, 0),
|
||||
r.bottom_right() + Offset::new(-1, -1),
|
||||
r.bottom_left() + Offset::new(0, -1),
|
||||
r.top_right() - Offset::x(1),
|
||||
r.bottom_right() - Offset::uniform(1),
|
||||
r.bottom_left() - Offset::y(1),
|
||||
];
|
||||
for p in corners.iter() {
|
||||
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) {
|
||||
let w = text_width(text, font);
|
||||
let w = font.text_width(text);
|
||||
display::text(
|
||||
baseline.x - w / 2,
|
||||
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)]
|
||||
pub struct Font(i32);
|
||||
|
||||
@ -180,7 +200,7 @@ impl Font {
|
||||
}
|
||||
|
||||
pub fn text_width(self, text: &[u8]) -> i32 {
|
||||
text_width(text, self)
|
||||
display::text_width(text, self.0)
|
||||
}
|
||||
|
||||
pub fn text_height(self) -> i32 {
|
||||
|
@ -22,6 +22,14 @@ impl Offset {
|
||||
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 {
|
||||
match axis {
|
||||
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 {
|
||||
Self::new(self.x.abs(), self.y.abs())
|
||||
}
|
||||
@ -165,16 +180,20 @@ impl Rect {
|
||||
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 {
|
||||
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 {
|
||||
x0: self.x0 + uniform,
|
||||
y0: self.y0 + uniform,
|
||||
x1: self.x1 - uniform,
|
||||
y1: self.y1 - uniform,
|
||||
x0: self.x0 + insets.left,
|
||||
y0: self.y0 + insets.top,
|
||||
x1: self.x1 - insets.right,
|
||||
y1: self.y1 - insets.bottom,
|
||||
}
|
||||
}
|
||||
|
||||
@ -196,53 +215,80 @@ impl Rect {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn hsplit(self, height: i32) -> (Self, Self) {
|
||||
let height = if height.is_negative() {
|
||||
self.height() + height
|
||||
} else {
|
||||
height
|
||||
};
|
||||
pub fn split_top(self, height: i32) -> (Self, Self) {
|
||||
let height = height.clamp(0, self.height());
|
||||
|
||||
let top = Self {
|
||||
x0: self.x0,
|
||||
y0: self.y0,
|
||||
x1: self.x1,
|
||||
y1: self.y0 + height,
|
||||
..self
|
||||
};
|
||||
|
||||
let bottom = Self {
|
||||
x0: self.x0,
|
||||
y0: self.y0 + height,
|
||||
x1: self.x1,
|
||||
y1: self.y1,
|
||||
..self
|
||||
};
|
||||
|
||||
(top, bottom)
|
||||
}
|
||||
|
||||
pub fn vsplit(self, width: i32) -> (Self, Self) {
|
||||
let width = if width.is_negative() {
|
||||
self.width() + width
|
||||
} else {
|
||||
width
|
||||
};
|
||||
pub fn split_bottom(self, height: i32) -> (Self, Self) {
|
||||
self.split_top(self.height() - height)
|
||||
}
|
||||
|
||||
pub fn split_left(self, width: i32) -> (Self, Self) {
|
||||
let width = width.clamp(0, self.width());
|
||||
|
||||
let left = Self {
|
||||
x0: self.x0,
|
||||
y0: self.y0,
|
||||
x1: self.x0 + width,
|
||||
y1: self.y1,
|
||||
..self
|
||||
};
|
||||
|
||||
let right = Self {
|
||||
x0: self.x0 + width,
|
||||
y0: self.y0,
|
||||
x1: self.x1,
|
||||
y1: self.y1,
|
||||
..self
|
||||
};
|
||||
|
||||
(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)]
|
||||
@ -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 {
|
||||
let cell_width = self.area.width() / self.cols as i32;
|
||||
let cell_height = self.area.height() / self.rows as i32;
|
||||
let x = col as i32 * cell_width;
|
||||
let y = row as i32 * cell_height;
|
||||
Rect {
|
||||
x0: self.area.x0 + x,
|
||||
y0: self.area.y0 + y,
|
||||
x1: self.area.x0 + x + (cell_width - self.spacing),
|
||||
y1: self.area.y0 + y + (cell_height - self.spacing),
|
||||
let ncols = self.cols as i32;
|
||||
let nrows = self.rows as i32;
|
||||
let col = (col as i32).min(ncols - 1);
|
||||
let row = (row as i32).min(nrows - 1);
|
||||
|
||||
// Total number of horizontal pixels used for spacing.
|
||||
let spacing_width = self.spacing * (ncols - 1);
|
||||
let spacing_height = self.spacing * (nrows - 1);
|
||||
|
||||
// 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 {
|
||||
@ -357,45 +436,67 @@ impl LinearLayout {
|
||||
self
|
||||
}
|
||||
|
||||
/// 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| {
|
||||
let size = i.get_size();
|
||||
self.axis.main(size.x, size.y)
|
||||
})
|
||||
.sum();
|
||||
let spacing_count = items.len().saturating_sub(1);
|
||||
fn compute_spacing(&self, area: Rect, count: usize, size_sum: i32) -> (i32, i32) {
|
||||
let spacing_count = count.saturating_sub(1);
|
||||
let spacing_sum = spacing_count as i32 * self.spacing;
|
||||
let naive_size = item_sum + spacing_sum;
|
||||
let available_space = match self.axis {
|
||||
Axis::Horizontal => area.width(),
|
||||
Axis::Vertical => area.height(),
|
||||
};
|
||||
let naive_size = size_sum + spacing_sum;
|
||||
let available_space = area.size().axis(self.axis);
|
||||
|
||||
// scale down spacing to fit everything into area
|
||||
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
|
||||
(available_space, scaled_space.max(0))
|
||||
} else {
|
||||
(naive_size, self.spacing)
|
||||
};
|
||||
|
||||
let mut cursor = match self.align {
|
||||
let init_cursor = match self.align {
|
||||
Alignment::Start => 0,
|
||||
Alignment::Center => available_space / 2 - total_size / 2,
|
||||
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 {
|
||||
let top_left = area.top_left() + Offset::on_axis(self.axis, cursor);
|
||||
let size = item.get_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;
|
||||
}
|
||||
}
|
||||
|
@ -94,13 +94,13 @@ impl<T: AsRef<[u8]>> Button<T> {
|
||||
) -> (Rect, Point) {
|
||||
let border_width = if styles.normal.border_horiz { 2 } else { 0 };
|
||||
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!(),
|
||||
};
|
||||
let button_width = content_width + 2 * border_width;
|
||||
let area = match pos {
|
||||
ButtonPos::Left => area.vsplit(button_width).0,
|
||||
ButtonPos::Right => area.vsplit(-button_width).1,
|
||||
ButtonPos::Left => area.split_left(button_width).0,
|
||||
ButtonPos::Right => area.split_right(button_width).1,
|
||||
};
|
||||
|
||||
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) => {
|
||||
let background_color = style.text_color.neg();
|
||||
if style.border_horiz {
|
||||
display::rounded_rect1(self.area, background_color, theme::BG);
|
||||
display::rect_fill_rounded1(self.area, background_color, theme::BG);
|
||||
} else {
|
||||
display::rect(self.area, background_color)
|
||||
display::rect_fill(self.area, background_color)
|
||||
}
|
||||
|
||||
display::text(
|
||||
|
@ -39,7 +39,7 @@ impl<T: Component, U: AsRef<[u8]>> Dialog<T, U> {
|
||||
|
||||
fn areas(area: Rect) -> (Rect, Rect) {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
@ -25,8 +25,8 @@ impl<T: Component, U: AsRef<[u8]>> Frame<T, U> {
|
||||
const HEADER_SPACE: i32 = 4;
|
||||
let header_height = theme::FONT_BOLD.line_height();
|
||||
|
||||
let (header_area, content_area) = area.hsplit(header_height);
|
||||
let (_space, content_area) = content_area.hsplit(HEADER_SPACE);
|
||||
let (header_area, content_area) = area.split_top(header_height);
|
||||
let (_space, content_area) = content_area.split_top(HEADER_SPACE);
|
||||
|
||||
(header_area, content_area)
|
||||
}
|
||||
@ -41,7 +41,7 @@ impl<T: Component, U: AsRef<[u8]>> Component for Frame<T, U> {
|
||||
|
||||
fn paint(&mut self) {
|
||||
display::text(
|
||||
self.area.bottom_left() + Offset::new(0, -2),
|
||||
self.area.bottom_left() - Offset::y(2),
|
||||
self.title.as_ref(),
|
||||
theme::FONT_BOLD,
|
||||
theme::FG,
|
||||
|
@ -1,7 +1,7 @@
|
||||
use crate::ui::{
|
||||
component::{Component, ComponentExt, Event, EventCtx, Never, Pad, PageMsg, Paginate},
|
||||
display::{self, Color},
|
||||
geometry::{Offset, Point, Rect},
|
||||
geometry::{Insets, Offset, Point, Rect},
|
||||
};
|
||||
|
||||
use super::{theme, Button, ButtonMsg, ButtonPos};
|
||||
@ -63,9 +63,9 @@ where
|
||||
|
||||
fn areas(area: Rect) -> (Rect, Rect, Rect) {
|
||||
let button_height = theme::FONT_BOLD.line_height() + 2;
|
||||
let (content_area, button_area) = area.hsplit(-button_height);
|
||||
let (content_area, scrollbar_area) = content_area.vsplit(-ScrollBar::WIDTH);
|
||||
let (content_area, _) = content_area.hsplit(-1);
|
||||
let (content_area, button_area) = area.split_bottom(button_height);
|
||||
let (content_area, scrollbar_area) = content_area.split_right(ScrollBar::WIDTH);
|
||||
let (content_area, _) = content_area.split_bottom(1);
|
||||
(content_area, scrollbar_area, button_area)
|
||||
}
|
||||
|
||||
@ -182,8 +182,8 @@ impl ScrollBar {
|
||||
|
||||
fn paint_dot(&self, active: bool, top_left: Point) {
|
||||
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::new(0, 1), Offset::new(1, 2)),
|
||||
Rect::from_top_left_and_size(top_left + Offset::x(1), Offset::new(2, 1)),
|
||||
Rect::from_top_left_and_size(top_left + Offset::y(1), Offset::new(1, 2)),
|
||||
Rect::from_top_left_and_size(
|
||||
top_left + Offset::new(1, Self::DOT_SIZE.y - 1),
|
||||
Offset::new(2, 1),
|
||||
@ -194,11 +194,11 @@ impl ScrollBar {
|
||||
),
|
||||
];
|
||||
for side in sides {
|
||||
display::rect(side, theme::FG)
|
||||
display::rect_fill(side, theme::FG)
|
||||
}
|
||||
if active {
|
||||
display::rect(
|
||||
Rect::from_top_left_and_size(top_left, Self::DOT_SIZE).inset(1),
|
||||
display::rect_fill(
|
||||
Rect::from_top_left_and_size(top_left, Self::DOT_SIZE).inset(Insets::uniform(1)),
|
||||
theme::FG,
|
||||
)
|
||||
}
|
||||
|
@ -2,7 +2,7 @@ use super::{event::TouchEvent, theme};
|
||||
use crate::ui::{
|
||||
component::{Component, Event, EventCtx, Map},
|
||||
display::{self, Color, Font},
|
||||
geometry::{Grid, Offset, Rect},
|
||||
geometry::{Grid, Insets, Offset, Rect},
|
||||
};
|
||||
|
||||
pub enum ButtonMsg {
|
||||
@ -160,14 +160,14 @@ where
|
||||
|
||||
if style.border_width > 0 {
|
||||
// Paint the border and a smaller background on top of it.
|
||||
display::rounded_rect(
|
||||
display::rect_fill_rounded(
|
||||
self.area,
|
||||
style.border_color,
|
||||
style.background_color,
|
||||
style.border_radius,
|
||||
);
|
||||
display::rounded_rect(
|
||||
self.area.inset(style.border_width),
|
||||
display::rect_fill_rounded(
|
||||
self.area.inset(Insets::uniform(style.border_width)),
|
||||
style.button_color,
|
||||
style.border_color,
|
||||
style.border_radius,
|
||||
@ -175,7 +175,7 @@ where
|
||||
} else {
|
||||
// We do not need to draw an explicit border in this case, just a
|
||||
// bigger background.
|
||||
display::rounded_rect(
|
||||
display::rect_fill_rounded(
|
||||
self.area,
|
||||
style.button_color,
|
||||
style.background_color,
|
||||
|
@ -29,9 +29,9 @@ where
|
||||
const HEADER_SPACE: i32 = 14;
|
||||
let header_height = theme::FONT_BOLD.line_height() - theme::CONTENT_BORDER;
|
||||
|
||||
let (header_area, content_area) = area.hsplit(header_height);
|
||||
let (_space, header_area) = header_area.vsplit(theme::CONTENT_BORDER);
|
||||
let (_space, content_area) = content_area.hsplit(HEADER_SPACE);
|
||||
let (header_area, content_area) = area.split_top(header_height);
|
||||
let (_space, header_area) = header_area.split_left(theme::CONTENT_BORDER);
|
||||
let (_space, content_area) = content_area.split_top(HEADER_SPACE);
|
||||
|
||||
(header_area, content_area)
|
||||
}
|
||||
|
@ -3,7 +3,7 @@ use crate::ui::{
|
||||
base::ComponentExt, paginated::PageMsg, Component, Event, EventCtx, Never, Pad, Paginate,
|
||||
},
|
||||
display::{self, Color},
|
||||
geometry::{Dimensions, Offset, Point, Rect},
|
||||
geometry::{Dimensions, LinearLayout, Offset, Rect},
|
||||
};
|
||||
|
||||
use super::{theme, Button, Swipe, SwipeDirection};
|
||||
@ -82,7 +82,7 @@ where
|
||||
|
||||
fn paint_hint(&mut self) {
|
||||
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",
|
||||
theme::FONT_BOLD, // FIXME: Figma has this as 14px but bold is 16px
|
||||
theme::GREY_LIGHT,
|
||||
@ -180,8 +180,11 @@ pub struct ScrollBar {
|
||||
}
|
||||
|
||||
impl ScrollBar {
|
||||
const DOT_INTERVAL: i32 = 12;
|
||||
const ARROW_SPACE: i32 = 23;
|
||||
const DOT_SIZE: i32 = 6;
|
||||
/// 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_INACTIVE: &'static [u8] = include_res!("model_tt/res/scroll-inactive.toif");
|
||||
@ -229,40 +232,42 @@ impl Component for ScrollBar {
|
||||
}
|
||||
|
||||
fn paint(&mut self) {
|
||||
let count = self.page_count as i32;
|
||||
let interval = {
|
||||
let available_height = self.area.height();
|
||||
let naive_height = count * Self::DOT_INTERVAL;
|
||||
if naive_height > available_height {
|
||||
available_height / count
|
||||
} else {
|
||||
Self::DOT_INTERVAL
|
||||
}
|
||||
};
|
||||
let mut dot = Point::new(
|
||||
self.area.center().x,
|
||||
self.area.center().y - (count / 2) * interval,
|
||||
);
|
||||
if self.has_previous_page() {
|
||||
display::icon(
|
||||
dot - Offset::new(0, Self::ARROW_SPACE),
|
||||
Self::ICON_UP,
|
||||
theme::FG,
|
||||
theme::BG,
|
||||
);
|
||||
}
|
||||
for i in 0..self.page_count {
|
||||
let layout = LinearLayout::vertical()
|
||||
.align_at_center()
|
||||
.with_spacing(Self::DOT_INTERVAL);
|
||||
|
||||
let mut i = 0;
|
||||
let mut top = None;
|
||||
let mut display_icon = |top_left| {
|
||||
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;
|
||||
display::icon_top_left(top_left, icon, theme::FG, theme::BG);
|
||||
i += 1;
|
||||
top.get_or_insert(top_left.x);
|
||||
};
|
||||
|
||||
layout.arrange_uniform(
|
||||
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() {
|
||||
display::icon(
|
||||
self.area.center() - Offset::y(arrow_distance),
|
||||
Self::ICON_UP,
|
||||
theme::FG,
|
||||
theme::BG,
|
||||
);
|
||||
}
|
||||
if self.has_next_page() {
|
||||
display::icon(
|
||||
dot + Offset::new(0, Self::ARROW_SPACE - interval),
|
||||
self.area.center() + Offset::y(arrow_distance),
|
||||
Self::ICON_DOWN,
|
||||
theme::FG,
|
||||
theme::BG,
|
||||
@ -284,13 +289,14 @@ impl PageLayout {
|
||||
const SCROLLBAR_SPACE: i32 = 10;
|
||||
|
||||
pub fn new(area: Rect) -> Self {
|
||||
let (content, buttons) = area.hsplit(-Button::HEIGHT);
|
||||
let (content, _space) = content.hsplit(-Self::BUTTON_SPACE);
|
||||
let (buttons, _space) = buttons.vsplit(-theme::CONTENT_BORDER);
|
||||
let (_space, content) = content.vsplit(theme::CONTENT_BORDER);
|
||||
let (content_single_page, _space) = content.vsplit(-theme::CONTENT_BORDER);
|
||||
let (content, scrollbar) = content.vsplit(-(Self::SCROLLBAR_SPACE + Self::SCROLLBAR_WIDTH));
|
||||
let (_space, scrollbar) = scrollbar.vsplit(Self::SCROLLBAR_SPACE);
|
||||
let (content, buttons) = area.split_bottom(Button::<&str>::HEIGHT);
|
||||
let (content, _space) = content.split_bottom(Self::BUTTON_SPACE);
|
||||
let (buttons, _space) = buttons.split_right(theme::CONTENT_BORDER);
|
||||
let (_space, content) = content.split_left(theme::CONTENT_BORDER);
|
||||
let (content_single_page, _space) = content.split_right(theme::CONTENT_BORDER);
|
||||
let (content, scrollbar) =
|
||||
content.split_right(Self::SCROLLBAR_SPACE + Self::SCROLLBAR_WIDTH);
|
||||
let (_space, scrollbar) = scrollbar.split_left(Self::SCROLLBAR_SPACE);
|
||||
|
||||
Self {
|
||||
content_single_page,
|
||||
|
@ -222,7 +222,7 @@ impl Component for PinDots {
|
||||
|
||||
fn paint(&mut self) {
|
||||
// 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.
|
||||
for i in 0..self.digit_count {
|
||||
@ -231,7 +231,7 @@ impl Component for PinDots {
|
||||
y: self.area.center().y,
|
||||
};
|
||||
let size = Offset::new(Self::DOT, Self::DOT);
|
||||
display::rounded_rect(
|
||||
display::rect_fill_rounded(
|
||||
Rect::from_top_left_and_size(pos, size),
|
||||
self.style.text_color,
|
||||
self.style.background_color,
|
||||
|
@ -1,7 +1,7 @@
|
||||
use crate::ui::{
|
||||
component::{label::LabelStyle, text::layout::DefaultTextTheme},
|
||||
display::{self, Color, Font},
|
||||
geometry::Rect,
|
||||
geometry::{Insets, Rect},
|
||||
};
|
||||
|
||||
use super::component::{ButtonStyle, ButtonStyleSheet, LoaderStyle, LoaderStyleSheet};
|
||||
@ -164,9 +164,5 @@ pub const CONTENT_BORDER: i32 = 5;
|
||||
/// | 14 |
|
||||
/// +----------+
|
||||
pub fn borders() -> Rect {
|
||||
let (_left_border, area) = display::screen().vsplit(10);
|
||||
let (area, _right_area) = area.vsplit(-5);
|
||||
let (_top_border, area) = area.hsplit(13);
|
||||
let (area, _bottom_border) = area.hsplit(-14);
|
||||
area
|
||||
display::screen().inset(Insets::new(13, 5, 14, 10))
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user