use embedded_graphics::{
mono_font::{
MonoTextStyleBuilder,
ascii::{FONT_7X14, FONT_9X18_BOLD},
},
pixelcolor::Rgb565,
prelude::*,
primitives::{Line, PrimitiveStyle, PrimitiveStyleBuilder, Rectangle},
text::{Alignment, Baseline, Text, TextStyleBuilder},
};
use crate::{
Button, ButtonTouchResponse, ButtonTouchState, FsTheme, I18n, Layer, Localized, StackLayers,
StackNav, TouchEvent,
};
use super::stack_header::{
HeaderTitle, back_button, back_title_center, draw_title, header_titles, show_back_button,
};
const HEADER_HEIGHT: u32 = 64;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum NavHeaderAction {
Back,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct NavView<'a, K> {
pub frame: Rectangle,
pub header: Rectangle,
pub body: Rectangle,
pub layers: StackLayers<K>,
back_button: Option<Button<'a, NavHeaderAction>>,
back_title: Option<Localized<'a>>,
active_title: HeaderTitle<'a>,
secondary_title: Option<HeaderTitle<'a>>,
}
impl<K: Copy, const N: usize> StackNav<K, N> {
pub fn nav_view<'a, F>(&self, frame: Rectangle, title_for: F) -> NavView<'a, K>
where
F: Fn(K) -> Localized<'a>,
{
let header_height = HEADER_HEIGHT.min(frame.size.height.saturating_sub(1));
let header = Rectangle::new(frame.top_left, Size::new(frame.size.width, header_height));
let body = Rectangle::new(
frame.top_left + Point::new(0, header_height as i32),
Size::new(
frame.size.width,
frame.size.height.saturating_sub(header_height),
),
);
let back_button = show_back_button(self).then_some(back_button(header));
let (back_title, active_title, secondary_title) =
header_titles(self, header, &title_for, back_button.as_ref());
NavView {
frame,
header,
body,
layers: self.layers(body),
back_button,
back_title,
active_title,
secondary_title,
}
}
}
impl<'a> NavView<'a, ()> {
pub fn root(frame: Rectangle, title: Localized<'a>) -> Self {
let header_height = HEADER_HEIGHT.min(frame.size.height.saturating_sub(1));
let header = Rectangle::new(frame.top_left, Size::new(frame.size.width, header_height));
let body = Rectangle::new(
frame.top_left + Point::new(0, header_height as i32),
Size::new(
frame.size.width,
frame.size.height.saturating_sub(header_height),
),
);
Self {
frame,
header,
body,
layers: StackLayers {
base: Layer::new((), body, Point::zero()),
overlay: None,
},
back_button: None,
back_title: None,
active_title: HeaderTitle {
title,
center: header.center(),
},
secondary_title: None,
}
}
}
impl<'a, K> NavView<'a, K> {
pub fn draw_static_chrome<D>(&self, display: &mut D, theme: &FsTheme, i18n: &I18n<'a>)
where
D: DrawTarget<Color = Rgb565>,
{
let touch = ButtonTouchState::new();
self.draw_chrome(display, theme, i18n, &touch);
}
pub fn draw_chrome<D>(
&self,
display: &mut D,
theme: &FsTheme,
i18n: &I18n<'a>,
touch: &ButtonTouchState<NavHeaderAction>,
) where
D: DrawTarget<Color = Rgb565>,
{
let shell = PrimitiveStyleBuilder::new()
.fill_color(theme.surface_alt)
.stroke_color(theme.surface)
.stroke_width(2)
.build();
self.frame.into_styled(shell).draw(display).ok();
self.header
.into_styled(PrimitiveStyle::with_fill(theme.surface))
.draw(display)
.ok();
Line::new(
self.header.top_left + Point::new(0, self.header.size.height as i32),
self.header.bottom_right().unwrap_or(self.header.top_left),
)
.into_styled(PrimitiveStyle::with_stroke(theme.surface_alt, 2))
.draw(display)
.ok();
let title_style = MonoTextStyleBuilder::new()
.font(&FONT_9X18_BOLD)
.text_color(theme.text_primary)
.build();
let back_style = MonoTextStyleBuilder::new()
.font(&FONT_7X14)
.text_color(theme.text_primary)
.build();
let text_style = TextStyleBuilder::new()
.alignment(Alignment::Center)
.baseline(Baseline::Middle)
.build();
let settled_back_button = self.back_title.is_some() && self.secondary_title.is_none();
if settled_back_button {
let button = self
.back_button
.as_ref()
.expect("settled stacked headers include a back button");
button.draw_state(display, theme, i18n, touch.is_highlighted(button));
let arrow = MonoTextStyleBuilder::new()
.font(&FONT_7X14)
.text_color(theme.text_primary)
.build();
Text::with_text_style(
"<",
button.frame.top_left + Point::new(18, (button.frame.size.height / 2) as i32),
arrow,
TextStyleBuilder::new()
.alignment(Alignment::Center)
.baseline(Baseline::Middle)
.build(),
)
.draw(display)
.ok();
}
let mut header = display.clipped(&self.header);
if settled_back_button {
let title = self
.back_title
.expect("settled stacked headers include a back title");
Text::with_text_style(
i18n.text(title),
back_title_center(self.back_button.as_ref()),
back_style,
text_style,
)
.draw(&mut header)
.ok();
}
draw_title(
&mut header,
i18n.text(self.active_title.title),
self.active_title.center,
title_style,
text_style,
);
if let Some(title) = self.secondary_title {
draw_title(
&mut header,
i18n.text(title.title),
title.center,
title_style,
text_style,
);
}
}
pub fn handle_touch(
&self,
touch_state: &mut ButtonTouchState<NavHeaderAction>,
touch: TouchEvent,
) -> ButtonTouchResponse<NavHeaderAction> {
if let Some(button) = self.back_button.as_ref() {
touch_state.handle_touch(touch, core::slice::from_ref(button))
} else {
touch_state.clear();
ButtonTouchResponse {
action: None,
captured: false,
redraw: false,
}
}
}
}