mirror of
https://github.com/LegitCamper/picocalc-os-rs.git
synced 2025-12-27 15:55:25 +00:00
better ui
This commit is contained in:
@@ -28,18 +28,17 @@ use crate::{heap::HEAP, heap::init_qmi_psram_heap, psram::init_psram_qmi};
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
abi::{KEY_CACHE, MS_SINCE_LAUNCH},
|
abi::{KEY_CACHE, MS_SINCE_LAUNCH},
|
||||||
display::{FRAMEBUFFER, display_handler, init_display},
|
display::{display_handler, init_display},
|
||||||
|
elf::load_binary,
|
||||||
peripherals::{
|
peripherals::{
|
||||||
conf_peripherals,
|
conf_peripherals,
|
||||||
keyboard::{KeyState, read_keyboard_fifo},
|
keyboard::{KeyState, read_keyboard_fifo},
|
||||||
},
|
},
|
||||||
psram::init_psram,
|
psram::init_psram,
|
||||||
storage::{SDCARD, SdCard},
|
storage::{SDCARD, SdCard},
|
||||||
ui::{SELECTIONS, clear_selection, ui_handler},
|
ui::{clear_screen, ui_handler},
|
||||||
usb::usb_handler,
|
usb::usb_handler,
|
||||||
};
|
};
|
||||||
use abi_sys::EntryFn;
|
|
||||||
use bumpalo::Bump;
|
|
||||||
use core::sync::atomic::{AtomicBool, Ordering};
|
use core::sync::atomic::{AtomicBool, Ordering};
|
||||||
use embassy_executor::{Executor, Spawner};
|
use embassy_executor::{Executor, Spawner};
|
||||||
use embassy_futures::select::select;
|
use embassy_futures::select::select;
|
||||||
@@ -62,12 +61,8 @@ use embassy_sync::{
|
|||||||
blocking_mutex::raw::CriticalSectionRawMutex, channel::Channel, signal::Signal,
|
blocking_mutex::raw::CriticalSectionRawMutex, channel::Channel, signal::Signal,
|
||||||
};
|
};
|
||||||
use embassy_time::{Delay, Duration, Instant, Ticker, Timer};
|
use embassy_time::{Delay, Duration, Instant, Ticker, Timer};
|
||||||
use embedded_graphics::{
|
|
||||||
pixelcolor::Rgb565,
|
|
||||||
prelude::{DrawTarget, RgbColor},
|
|
||||||
};
|
|
||||||
use embedded_hal_bus::spi::ExclusiveDevice;
|
use embedded_hal_bus::spi::ExclusiveDevice;
|
||||||
use embedded_sdmmc::SdCard as SdmmcSdCard;
|
use embedded_sdmmc::{SdCard as SdmmcSdCard, ShortFileName};
|
||||||
use static_cell::StaticCell;
|
use static_cell::StaticCell;
|
||||||
use talc::*;
|
use talc::*;
|
||||||
use {defmt_rtt as _, panic_probe as _};
|
use {defmt_rtt as _, panic_probe as _};
|
||||||
@@ -172,21 +167,21 @@ async fn main(_spawner: Spawner) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// One-slot channel to pass EntryFn from core1
|
// One-slot channel to pass EntryFn from core1
|
||||||
static BINARY_CH: Channel<CriticalSectionRawMutex, (EntryFn, Bump), 1> = Channel::new();
|
static BINARY_CH: Channel<CriticalSectionRawMutex, ShortFileName, 1> = Channel::new();
|
||||||
|
|
||||||
// runs dynamically loaded elf files
|
// runs dynamically loaded elf files
|
||||||
#[embassy_executor::task]
|
#[embassy_executor::task]
|
||||||
async fn userland_task() {
|
async fn userland_task() {
|
||||||
let recv = BINARY_CH.receiver();
|
let recv = BINARY_CH.receiver();
|
||||||
loop {
|
loop {
|
||||||
let (entry, _bump) = recv.receive().await;
|
let name = recv.receive().await;
|
||||||
|
let (entry, _bump) = unsafe { load_binary(&name).await.expect("unable to load binary") };
|
||||||
|
|
||||||
// disable kernel ui
|
// disable kernel ui
|
||||||
{
|
{
|
||||||
ENABLE_UI.store(false, Ordering::Release);
|
ENABLE_UI.store(false, Ordering::Release);
|
||||||
UI_CHANGE.signal(());
|
UI_CHANGE.signal(());
|
||||||
|
clear_screen().await;
|
||||||
clear_selection().await;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe { MS_SINCE_LAUNCH = Some(Instant::now()) };
|
unsafe { MS_SINCE_LAUNCH = Some(Instant::now()) };
|
||||||
@@ -198,10 +193,7 @@ async fn userland_task() {
|
|||||||
{
|
{
|
||||||
ENABLE_UI.store(true, Ordering::Release);
|
ENABLE_UI.store(true, Ordering::Release);
|
||||||
UI_CHANGE.signal(());
|
UI_CHANGE.signal(());
|
||||||
unsafe { FRAMEBUFFER.as_mut().unwrap().clear(Rgb565::BLACK).unwrap() };
|
clear_screen().await;
|
||||||
|
|
||||||
let mut selections = SELECTIONS.lock().await;
|
|
||||||
selections.set_changed(true);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
348
kernel/src/ui.rs
348
kernel/src/ui.rs
@@ -10,11 +10,15 @@ use crate::{
|
|||||||
use abi_sys::keyboard::{KeyCode, KeyState};
|
use abi_sys::keyboard::{KeyCode, KeyState};
|
||||||
use alloc::{str::FromStr, string::String, vec::Vec};
|
use alloc::{str::FromStr, string::String, vec::Vec};
|
||||||
use core::sync::atomic::Ordering;
|
use core::sync::atomic::Ordering;
|
||||||
|
use embassy_futures::yield_now;
|
||||||
use embassy_sync::{blocking_mutex::raw::CriticalSectionRawMutex, mutex::Mutex};
|
use embassy_sync::{blocking_mutex::raw::CriticalSectionRawMutex, mutex::Mutex};
|
||||||
use embassy_time::Timer;
|
use embassy_time::Timer;
|
||||||
use embedded_graphics::{
|
use embedded_graphics::{
|
||||||
Drawable,
|
Drawable,
|
||||||
mono_font::{MonoTextStyle, ascii::FONT_10X20},
|
mono_font::{
|
||||||
|
MonoTextStyle,
|
||||||
|
ascii::{FONT_4X6, FONT_10X20},
|
||||||
|
},
|
||||||
pixelcolor::Rgb565,
|
pixelcolor::Rgb565,
|
||||||
prelude::{Dimensions, Point, Primitive, RgbColor, Size},
|
prelude::{Dimensions, Point, Primitive, RgbColor, Size},
|
||||||
primitives::{PrimitiveStyle, Rectangle},
|
primitives::{PrimitiveStyle, Rectangle},
|
||||||
@@ -27,166 +31,155 @@ use embedded_layout::{
|
|||||||
};
|
};
|
||||||
use embedded_text::TextBox;
|
use embedded_text::TextBox;
|
||||||
|
|
||||||
pub static SELECTIONS: Mutex<CriticalSectionRawMutex, SelectionList> =
|
|
||||||
Mutex::new(SelectionList::new());
|
|
||||||
|
|
||||||
pub async fn ui_handler() {
|
pub async fn ui_handler() {
|
||||||
|
let mut ui = UiState { page: UiPage::Menu };
|
||||||
|
let mut menu = MenuPage {
|
||||||
|
last_bounds: None,
|
||||||
|
selection: 0,
|
||||||
|
changed: true,
|
||||||
|
};
|
||||||
|
let mut scsi = ScsiPage { last_bounds: None };
|
||||||
|
let overlay = Overlay::new();
|
||||||
update_selections().await;
|
update_selections().await;
|
||||||
let mut scsi = Scsi { last_bounds: None };
|
|
||||||
let mut scsi_button = ScsiButton { last_bounds: None };
|
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
input_handler().await;
|
input_handler(&mut ui, &mut menu, &mut scsi).await;
|
||||||
|
match ui.page {
|
||||||
if USB_ACTIVE.load(Ordering::Acquire) {
|
UiPage::Menu => menu.draw().await,
|
||||||
scsi.draw(&mut scsi_button).await;
|
UiPage::Scsi => scsi.draw().await,
|
||||||
Timer::after_millis(100).await;
|
|
||||||
} else {
|
|
||||||
let changed = SELECTIONS.lock().await.changed;
|
|
||||||
if changed {
|
|
||||||
clear_selection().await;
|
|
||||||
draw_selection(&mut scsi_button).await;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
overlay.draw().await;
|
||||||
|
Timer::after_millis(5).await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn input_handler() {
|
async fn input_handler(ui: &mut UiState, menu: &mut MenuPage, scsi: &mut ScsiPage) {
|
||||||
if let Some(event) = keyboard::read_keyboard_fifo().await {
|
if let Some(event) = keyboard::read_keyboard_fifo().await {
|
||||||
if let KeyState::Pressed = event.state {
|
if event.state == KeyState::Pressed {
|
||||||
match event.key {
|
match (&mut ui.page, event.key) {
|
||||||
KeyCode::Up => {
|
(UiPage::Menu, KeyCode::F1) => {
|
||||||
if !USB_ACTIVE.load(Ordering::Acquire) {
|
start_usb();
|
||||||
let mut selections = SELECTIONS.lock().await;
|
menu.clear().await;
|
||||||
selections.up();
|
ui.page = UiPage::Scsi;
|
||||||
}
|
}
|
||||||
}
|
(UiPage::Scsi, KeyCode::F1) => {
|
||||||
KeyCode::Down => {
|
|
||||||
if !USB_ACTIVE.load(Ordering::Acquire) {
|
|
||||||
let mut selections = SELECTIONS.lock().await;
|
|
||||||
selections.down();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
KeyCode::F1 => {
|
|
||||||
match USB_ACTIVE.load(Ordering::Acquire) {
|
|
||||||
true => {
|
|
||||||
stop_usb();
|
stop_usb();
|
||||||
// wait for sd card to be put back and then reload selections
|
scsi.clear().await;
|
||||||
while USB_ACTIVE.load(Ordering::Acquire) {
|
ui.page = UiPage::Menu;
|
||||||
Timer::after_millis(100).await
|
update_selections().await;
|
||||||
}
|
}
|
||||||
update_selections().await
|
(UiPage::Menu, _) => menu.handle_input(event.key).await,
|
||||||
}
|
(UiPage::Scsi, _) => scsi.handle_input(event.key).await,
|
||||||
false => start_usb(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
KeyCode::Enter | KeyCode::Right => {
|
|
||||||
if !USB_ACTIVE.load(Ordering::Acquire) {
|
|
||||||
let selections = SELECTIONS.lock().await;
|
|
||||||
let selection =
|
|
||||||
selections.selections[selections.current_selection as usize].clone();
|
|
||||||
|
|
||||||
let entry = unsafe {
|
|
||||||
load_binary(&selection.short_name)
|
|
||||||
.await
|
|
||||||
.expect("unable to load binary")
|
|
||||||
};
|
|
||||||
BINARY_CH.send(entry).await;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => (),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn clear_rect(rect: Rectangle) {
|
struct Overlay {
|
||||||
Rectangle::new(rect.top_left, rect.size)
|
f1_label: &'static str,
|
||||||
.into_styled(PrimitiveStyle::with_fill(Rgb565::BLACK))
|
}
|
||||||
.draw(unsafe { &mut *FRAMEBUFFER.as_mut().unwrap() })
|
|
||||||
|
impl Overlay {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
f1_label: "Press F1 to enable/disable mass storage",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn draw(&self) {
|
||||||
|
let text_style = MonoTextStyle::new(&FONT_4X6, Rgb565::WHITE);
|
||||||
|
let fb = unsafe { &mut *FRAMEBUFFER.as_mut().unwrap() };
|
||||||
|
let bounds = fb.bounding_box();
|
||||||
|
|
||||||
|
Text::with_alignment(
|
||||||
|
self.f1_label,
|
||||||
|
Point::new(10, bounds.size.height as i32 - 24), // bottom-left corner
|
||||||
|
text_style,
|
||||||
|
Alignment::Left,
|
||||||
|
)
|
||||||
|
.draw(fb)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum UiPage {
|
||||||
|
Menu,
|
||||||
|
Scsi,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct UiState {
|
||||||
|
page: UiPage,
|
||||||
|
}
|
||||||
|
|
||||||
|
trait Page {
|
||||||
|
async fn draw(&mut self);
|
||||||
|
async fn handle_input(&mut self, key: KeyCode);
|
||||||
|
async fn clear(&mut self);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ScsiPage {
|
||||||
|
last_bounds: Option<Rectangle>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Page for ScsiPage {
|
||||||
|
async fn draw(&mut self) {
|
||||||
|
let text_style = MonoTextStyle::new(&FONT_10X20, Rgb565::WHITE);
|
||||||
|
let fb = unsafe { &mut *FRAMEBUFFER.as_mut().unwrap() };
|
||||||
|
let bounds = fb.bounding_box();
|
||||||
|
|
||||||
|
Text::with_alignment(
|
||||||
|
"Mass storage over usb enabled",
|
||||||
|
bounds.center(),
|
||||||
|
text_style,
|
||||||
|
Alignment::Center,
|
||||||
|
)
|
||||||
|
.draw(fb)
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn handle_input(&mut self, _key: KeyCode) {
|
||||||
|
()
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn clear(&mut self) {
|
||||||
|
if let Some(rect) = self.last_bounds {
|
||||||
|
clear_rect(rect).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static SELECTIONS: Mutex<CriticalSectionRawMutex, Vec<FileName>> = Mutex::new(Vec::new());
|
||||||
|
static mut SELECTIONS_CHANGED: bool = true;
|
||||||
|
|
||||||
async fn update_selections() {
|
async fn update_selections() {
|
||||||
|
while USB_ACTIVE.load(Ordering::Acquire) {
|
||||||
|
Timer::after_millis(50).await;
|
||||||
|
}
|
||||||
let mut guard = SDCARD.get().lock().await;
|
let mut guard = SDCARD.get().lock().await;
|
||||||
let sd = guard.as_mut().unwrap();
|
let sd = guard.as_mut().unwrap();
|
||||||
|
|
||||||
let files = sd.list_files_by_extension(".bin").unwrap();
|
let files = sd.list_files_by_extension(".bin").unwrap();
|
||||||
let mut select = SELECTIONS.lock().await;
|
let mut selections = SELECTIONS.lock().await;
|
||||||
|
|
||||||
if *select.selections() != files {
|
if *selections != files {
|
||||||
select.update_selections(files);
|
*selections = files;
|
||||||
select.reset();
|
unsafe { SELECTIONS_CHANGED = true }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
struct MenuPage {
|
||||||
struct Scsi {
|
|
||||||
last_bounds: Option<Rectangle>,
|
last_bounds: Option<Rectangle>,
|
||||||
|
selection: usize,
|
||||||
|
changed: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Scsi {
|
impl Page for MenuPage {
|
||||||
async fn draw(&mut self, scsi_button: &mut ScsiButton) {
|
|
||||||
if let Some(rect) = self.last_bounds {
|
|
||||||
clear_rect(rect).await
|
|
||||||
}
|
|
||||||
|
|
||||||
let text_style = MonoTextStyle::new(&FONT_10X20, Rgb565::WHITE);
|
|
||||||
let text = Text::with_alignment(
|
|
||||||
"Usb Mass Storage active",
|
|
||||||
unsafe { FRAMEBUFFER.as_ref().unwrap() }
|
|
||||||
.bounding_box()
|
|
||||||
.center(),
|
|
||||||
text_style,
|
|
||||||
Alignment::Center,
|
|
||||||
);
|
|
||||||
|
|
||||||
self.last_bounds = Some(text.bounds());
|
|
||||||
|
|
||||||
text.draw(unsafe { &mut *FRAMEBUFFER.as_mut().unwrap() })
|
|
||||||
.unwrap();
|
|
||||||
scsi_button.draw().await;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct ScsiButton {
|
|
||||||
last_bounds: Option<Rectangle>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ScsiButton {
|
|
||||||
async fn draw(&mut self) {
|
async fn draw(&mut self) {
|
||||||
if let Some(rect) = self.last_bounds {
|
if self.changed {
|
||||||
clear_rect(rect).await
|
self.clear().await;
|
||||||
}
|
}
|
||||||
|
|
||||||
let text_style = MonoTextStyle::new(&FONT_10X20, Rgb565::WHITE);
|
let guard = SELECTIONS.lock().await;
|
||||||
let text = Text::with_alignment(
|
let file_names = &guard.clone();
|
||||||
"Press F1 to enable/disable mass storage",
|
|
||||||
unsafe { FRAMEBUFFER.as_ref().unwrap() }
|
|
||||||
.bounding_box()
|
|
||||||
.bottom_right()
|
|
||||||
.unwrap(),
|
|
||||||
text_style,
|
|
||||||
Alignment::Left,
|
|
||||||
);
|
|
||||||
|
|
||||||
self.last_bounds = Some(text.bounds());
|
|
||||||
|
|
||||||
text.draw(unsafe { &mut *FRAMEBUFFER.as_mut().unwrap() })
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn clear_selection() {
|
|
||||||
let sel = SELECTIONS.lock().await;
|
|
||||||
|
|
||||||
if let Some(rect) = sel.last_bounds {
|
|
||||||
clear_rect(rect).await
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn draw_selection(scsi_button: &mut ScsiButton) {
|
|
||||||
let mut guard = SELECTIONS.lock().await;
|
|
||||||
let file_names = &guard.selections.clone();
|
|
||||||
|
|
||||||
let text_style = MonoTextStyle::new(&FONT_10X20, Rgb565::WHITE);
|
let text_style = MonoTextStyle::new(&FONT_10X20, Rgb565::WHITE);
|
||||||
let display_area = unsafe { FRAMEBUFFER.as_mut().unwrap().bounding_box() };
|
let display_area = unsafe { FRAMEBUFFER.as_mut().unwrap().bounding_box() };
|
||||||
@@ -225,7 +218,7 @@ async fn draw_selection(scsi_button: &mut ScsiButton) {
|
|||||||
// draw selected box
|
// draw selected box
|
||||||
let selected_bounds = layout
|
let selected_bounds = layout
|
||||||
.inner()
|
.inner()
|
||||||
.get(guard.current_selection as usize)
|
.get(self.selection)
|
||||||
.expect("Selected binary missing")
|
.expect("Selected binary missing")
|
||||||
.bounding_box();
|
.bounding_box();
|
||||||
Rectangle::new(selected_bounds.top_left, selected_bounds.size)
|
Rectangle::new(selected_bounds.top_left, selected_bounds.size)
|
||||||
@@ -233,67 +226,70 @@ async fn draw_selection(scsi_button: &mut ScsiButton) {
|
|||||||
.draw(unsafe { &mut *FRAMEBUFFER.as_mut().unwrap() })
|
.draw(unsafe { &mut *FRAMEBUFFER.as_mut().unwrap() })
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
guard.last_bounds = Some(layout.bounds());
|
self.last_bounds = Some(layout.bounds());
|
||||||
|
|
||||||
layout
|
layout
|
||||||
.draw(unsafe { &mut *FRAMEBUFFER.as_mut().unwrap() })
|
.draw(unsafe { &mut *FRAMEBUFFER.as_mut().unwrap() })
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
guard.changed = false;
|
self.changed = false;
|
||||||
scsi_button.draw().await;
|
|
||||||
FB_PAUSED.store(false, Ordering::Release); // ensure all elements show up at once
|
FB_PAUSED.store(false, Ordering::Release); // ensure all elements show up at once
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct SelectionList {
|
|
||||||
// allows easy clearing of selection ui,
|
|
||||||
// based on previous bounds
|
|
||||||
last_bounds: Option<Rectangle>,
|
|
||||||
current_selection: u16,
|
|
||||||
selections: Vec<FileName>,
|
|
||||||
changed: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SelectionList {
|
|
||||||
pub const fn new() -> Self {
|
|
||||||
Self {
|
|
||||||
last_bounds: None,
|
|
||||||
selections: Vec::new(),
|
|
||||||
current_selection: 0,
|
|
||||||
changed: false,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_changed(&mut self, changed: bool) {
|
async fn handle_input(&mut self, key: KeyCode) {
|
||||||
self.changed = changed
|
match key {
|
||||||
}
|
KeyCode::Enter | KeyCode::Right => {
|
||||||
|
let selections = SELECTIONS.lock().await;
|
||||||
|
let selection = selections[self.selection].clone();
|
||||||
|
|
||||||
pub fn update_selections(&mut self, selections: Vec<FileName>) {
|
BINARY_CH.send(selection.short_name).await;
|
||||||
self.selections = selections;
|
self.clear().await;
|
||||||
|
loop {
|
||||||
|
yield_now().await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
KeyCode::Up => {
|
||||||
|
if self.selection > 0 {
|
||||||
|
self.selection -= 1;
|
||||||
|
} else {
|
||||||
|
let len = SELECTIONS.lock().await.len();
|
||||||
|
if len > 0 {
|
||||||
|
self.selection = len - 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
KeyCode::Down => {
|
||||||
|
if self.selection + 1 < SELECTIONS.lock().await.len() {
|
||||||
|
self.selection += 1;
|
||||||
|
} else {
|
||||||
|
self.selection = 0; // wrap to top
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
self.changed = true;
|
self.changed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn selections(&self) -> &Vec<FileName> {
|
async fn clear(&mut self) {
|
||||||
&self.selections
|
if let Some(rect) = self.last_bounds {
|
||||||
}
|
clear_rect(rect).await;
|
||||||
|
|
||||||
pub fn reset(&mut self) {
|
|
||||||
self.current_selection = 0;
|
|
||||||
self.changed = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn up(&mut self) {
|
|
||||||
if self.current_selection > 0 {
|
|
||||||
self.current_selection -= 1;
|
|
||||||
self.changed = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn down(&mut self) {
|
|
||||||
if self.current_selection + 1 < self.selections.len() as u16 {
|
|
||||||
self.current_selection += 1;
|
|
||||||
self.changed = true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn clear_screen() {
|
||||||
|
clear_rect(Rectangle {
|
||||||
|
top_left: Point::zero(),
|
||||||
|
size: Size::new(320, 320),
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn clear_rect(rect: Rectangle) {
|
||||||
|
let fb = unsafe { &mut *FRAMEBUFFER.as_mut().unwrap() };
|
||||||
|
Rectangle::new(rect.top_left, rect.size)
|
||||||
|
.into_styled(PrimitiveStyle::with_fill(Rgb565::BLACK))
|
||||||
|
.draw(fb)
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user