mirror of
https://github.com/trezor/trezor-firmware.git
synced 2025-06-02 22:28:45 +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]>,
|
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;
|
||||||
} else {
|
let mut display_icon = |top_left| {
|
||||||
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 icon = if i == self.active_page {
|
let icon = if i == self.active_page {
|
||||||
Self::ICON_ACTIVE
|
Self::ICON_ACTIVE
|
||||||
} else {
|
} else {
|
||||||
Self::ICON_INACTIVE
|
Self::ICON_INACTIVE
|
||||||
};
|
};
|
||||||
display::icon(dot, icon, theme::FG, theme::BG);
|
display::icon_top_left(top_left, icon, theme::FG, theme::BG);
|
||||||
dot.y += interval;
|
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() {
|
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…
Reference in New Issue
Block a user