use crate::application::states::prelude::*;
use crate::system::input::InputEvent;
use crate::system::{Display, System};
use embedded_graphics::mono_font::ascii::FONT_6X12;
use embedded_graphics::mono_font::MonoTextStyle;
use embedded_graphics::pixelcolor::raw::RawU16;
use embedded_graphics::prelude::*;
use embedded_graphics::text::{Text, Alignment, Baseline};
use crate::system::notification::Notification;
const DISPLAY_HEIGHT: i32 = 128; const CHAR_WIDTH: i32 = 6;
const CHAR_HEIGHT: i32 = 12;
const LINE_WIDTH: i32 = DISPLAY_HEIGHT / CHAR_WIDTH;
#[derive(Debug, Copy, Clone, PartialEq)]
enum InternalState {
Menu,
Body,
}
pub struct NotificationState {
is_running: bool,
state: InternalState,
menu: Menu,
body: Body,
}
impl State for NotificationState {
fn render(&mut self, system: &mut impl System, display: &mut impl Display) -> Option<Signal> {
self.menu.update_count(system.nm().idx() as i8);
match self.state {
InternalState::Menu => {
let size = display.bounding_box().size;
let style = MonoTextStyle::new(&FONT_6X12, RawU16::from(0x02D4).into());
if system.nm().idx() > 0 {
Text::with_baseline(
">",
Point::new(0, self.menu.selected() as i32 * CHAR_HEIGHT),
style,
Baseline::Top,
)
.draw(display).ok();
for item in 0..system.nm().idx() {
system.nm().peek_notification(item, |notification| {
Text::with_baseline(
notification.title(),
Point::new(size.width as i32 / 2, item as i32 * CHAR_HEIGHT),
style,
Baseline::Top,
)
.draw(display).ok();
});
}
} else {
Text::with_alignment(
"Nothing to display!",
Point::new(size.width as i32 / 2, size.height as i32 / 2),
style,
Alignment::Center,
)
.draw(display).ok();
}
}
InternalState::Body => {
system
.nm()
.peek_notification(self.menu.selected() as usize, |notification| {
self.body.render(display, ¬ification);
});
}
}
None
}
fn input(&mut self, system: &mut impl System, input: InputEvent) -> Option<Signal> {
if input == InputEvent::Multi {
self.stop(system);
return Some(Signal::Home); }
self.menu.update_count(system.nm().idx() as i8);
match self.state {
InternalState::Menu => {
if system.nm().idx() > 0 {
match input {
InputEvent::Left => {
self.menu.prev();
}
InputEvent::Right => {
self.menu.next();
}
InputEvent::Middle => {
self.state = InternalState::Body;
system.nm().peek_notification(
self.menu.selected() as usize,
|notification| {
let line_count = notification.body().len() as i32 / LINE_WIDTH;
self.body = Body::new(line_count - line_count / 2);
},
);
}
_ => {}
}
} else {
self.stop(system);
}
}
InternalState::Body => match input {
InputEvent::Middle => {
self.state = InternalState::Menu;
}
InputEvent::Left => {
self.body.down();
}
InputEvent::Right => {
self.body.up();
}
_ => {}
},
}
None
}
}
impl Default for NotificationState {
fn default() -> Self {
Self {
is_running: false,
state: InternalState::Menu,
menu: Menu::new(),
body: Body::new(0),
}
}
}
impl ScopedState for NotificationState {
fn preview(&mut self, _system: &mut impl System, display: &mut impl Display) -> Option<Signal> {
let size = display.bounding_box().size;
let style = MonoTextStyle::new(&FONT_6X12, RawU16::from(0x02D4).into());
Text::with_alignment(
"Notifications",
Point::new(size.width as i32 / 2, size.height as i32 / 2),
style,
Alignment::Center
)
.draw(display).ok();
None
}
fn is_running(&self, _system: &mut impl System) -> bool {
self.is_running
}
fn start(&mut self, _system: &mut impl System) {
self.is_running = true;
}
fn stop(&mut self, _system: &mut impl System) {
self.is_running = false;
}
}
#[derive(Debug, Copy, Clone, PartialEq)]
struct Body {
scroll_y: i32,
max_scroll_y: i32,
}
impl Body {
pub fn new(max_scroll: i32) -> Self {
let max_scroll = if max_scroll < (DISPLAY_HEIGHT / CHAR_HEIGHT) / 2 {
0
} else {
max_scroll
};
info!("Creating body with max scroll of {}", -max_scroll);
Body {
scroll_y: 0,
max_scroll_y: -max_scroll,
}
}
pub fn render(&mut self, display: &mut impl Display, notification: &Notification) {
let body = notification.body().as_bytes();
for (idx, line) in body[0..body.len()].chunks(LINE_WIDTH as usize).enumerate() {
let style = MonoTextStyle::new(&FONT_6X12, RawU16::from(0x02D4).into());
Text::with_baseline(
unsafe { core::str::from_utf8_unchecked(line) },
Point::new(0, ((idx as i32) + self.scroll_y) * CHAR_HEIGHT),
style,
Baseline::Top
)
.draw(display).ok();
}
}
fn up(&mut self) {
self.scroll_y -= 1;
if self.scroll_y < self.max_scroll_y {
self.scroll_y = self.max_scroll_y;
}
}
fn down(&mut self) {
self.scroll_y += 1;
if self.scroll_y > 0 {
self.scroll_y = 0;
}
}
}
#[derive(Debug, Copy, Clone, PartialEq)]
struct Menu {
state_idx: i8,
item_count: i8,
}
impl Menu {
pub const fn new() -> Self {
Menu {
state_idx: 0,
item_count: 0,
}
}
fn prev(&mut self) {
self.state_idx -= 1;
if self.state_idx < 0 {
self.state_idx = self.item_count - 1;
}
}
fn next(&mut self) {
self.state_idx += 1;
if self.state_idx > self.item_count - 1 {
self.state_idx = 0;
}
}
fn selected(&self) -> i8 {
self.state_idx
}
fn update_count(&mut self, item_count: i8) {
self.item_count = item_count;
}
}