use embedded_graphics::{
mono_font::{
MonoTextStyleBuilder,
ascii::{FONT_7X14, FONT_9X18_BOLD},
},
pixelcolor::Rgb565,
prelude::*,
primitives::{PrimitiveStyle, PrimitiveStyleBuilder, Rectangle, RoundedRectangle},
text::{Alignment, Baseline, Text, TextStyleBuilder},
};
use heapless::Vec;
use crate::{
AlertAction, AlertButtonRole, AlertHostError, AlertKind, AlertSpec, Button, ButtonKind,
ButtonSpec, ButtonTouchResponse, ButtonTouchState, FsTheme, I18n, Localized, TouchEvent,
UiCanvas,
alert_layout::{alert_layout, alert_layout_in_panel, message_top},
};
#[derive(Clone, Debug, PartialEq, Eq)]
pub(super) struct PresentedAlert<'a, Modal, const MAX_ACTIONS: usize> {
key: Modal,
kind: AlertKind,
title: Localized<'a>,
message: Localized<'a>,
actions: Vec<AlertAction<'a>, MAX_ACTIONS>,
touch_state: ButtonTouchState<u8>,
}
impl<'a, Modal, const MAX_ACTIONS: usize> PresentedAlert<'a, Modal, MAX_ACTIONS>
where
Modal: Copy + Eq,
{
pub(super) fn new<const N: usize>(
key: Modal,
spec: AlertSpec<'a, N>,
) -> Result<Self, AlertHostError> {
let mut actions = Vec::new();
for action in spec.actions {
actions
.push(action)
.map_err(|_| AlertHostError::TooManyActions)?;
}
Ok(Self {
key,
kind: spec.kind,
title: spec.title,
message: spec.message,
actions,
touch_state: ButtonTouchState::new(),
})
}
pub(super) fn key(&self) -> Modal {
self.key
}
pub(super) fn title(&self) -> Localized<'a> {
self.title
}
pub(super) fn clear_touch_state(&mut self) {
self.touch_state.clear();
}
pub(super) fn panel(&self, bounds: Rectangle, theme: &FsTheme, i18n: &I18n<'a>) -> Rectangle {
alert_layout::<1>(bounds, theme, i18n.text(self.message)).panel
}
pub(super) fn handle_touch(
&mut self,
touch: TouchEvent,
panel: Rectangle,
) -> ButtonTouchResponse<u8> {
let buttons = self.buttons(panel);
self.touch_state.handle_touch(touch, buttons.as_slice())
}
pub(super) fn draw<D>(
&self,
display: &mut D,
panel: Rectangle,
theme: &FsTheme,
i18n: &I18n<'a>,
) where
D: UiCanvas,
{
let layout = alert_layout_in_panel::<1>(panel, i18n.text(self.message));
RoundedRectangle::with_equal_corners(
Rectangle::new(panel.top_left + Point::new(6, 8), panel.size),
Size::new(22, 22),
)
.into_styled(PrimitiveStyle::with_fill(theme.dim))
.draw(display)
.ok();
RoundedRectangle::with_equal_corners(panel, Size::new(22, 22))
.into_styled(
PrimitiveStyleBuilder::new()
.fill_color(theme.surface)
.stroke_color(theme.surface_alt)
.stroke_width(2)
.build(),
)
.draw(display)
.ok();
let title_style = MonoTextStyleBuilder::new()
.font(&FONT_9X18_BOLD)
.text_color(kind_color(self.kind, theme))
.build();
let message_style = MonoTextStyleBuilder::new()
.font(&FONT_7X14)
.text_color(theme.text_secondary)
.build();
let text_style = TextStyleBuilder::new()
.alignment(Alignment::Center)
.baseline(Baseline::Middle)
.build();
Text::with_text_style(
i18n.text(self.title),
panel.center() + Point::new(0, -40),
title_style,
text_style,
)
.draw(display)
.ok();
for (index, line) in layout.message_lines.iter().enumerate() {
Text::with_text_style(
line,
Point::new(panel.center().x, message_top(panel) + (index as i32 * 18)),
message_style,
text_style,
)
.draw(display)
.ok();
}
for button in self.buttons(panel) {
button.draw_state(
display,
theme,
i18n,
self.touch_state.is_highlighted(&button),
);
}
}
fn buttons(&self, panel: Rectangle) -> Vec<Button<'a, u8>, MAX_ACTIONS> {
let count = self.actions.len().max(1);
let mut buttons = Vec::new();
for (index, action) in self.actions.iter().enumerate() {
let _ = buttons.push(Button::new(
button_frame(panel, count, index),
ButtonSpec {
key: action.id,
icon: None,
label: action.label,
kind: button_kind(action.role),
},
));
}
buttons
}
}
fn button_frame(panel: Rectangle, count: usize, index: usize) -> Rectangle {
let width = panel.size.width.saturating_sub(36);
let button_width = width / count as u32;
let x = panel.top_left.x + 18 + (index as i32 * button_width as i32);
let width = if index + 1 == count {
panel
.size
.width
.saturating_sub(36 + button_width * index as u32)
} else {
button_width.saturating_sub(8)
};
Rectangle::new(
Point::new(x, panel.top_left.y + panel.size.height as i32 - 62),
Size::new(width, 40),
)
}
fn kind_color(kind: AlertKind, theme: &FsTheme) -> Rgb565 {
match kind {
AlertKind::Error => theme.danger,
AlertKind::Warning => theme.warning,
AlertKind::Confirm => theme.accent,
}
}
fn button_kind(role: AlertButtonRole) -> ButtonKind {
match role {
AlertButtonRole::Primary => ButtonKind::Primary,
AlertButtonRole::Secondary => ButtonKind::Secondary,
AlertButtonRole::Destructive => ButtonKind::Destructive,
}
}