Compare commits
No commits in common. "main" and "demo" have entirely different histories.
@ -35,7 +35,5 @@ embedded-graphics = "0.8.1"
|
|||||||
ili9341 = "0.6.0"
|
ili9341 = "0.6.0"
|
||||||
display-interface-spi = "0.5.0"
|
display-interface-spi = "0.5.0"
|
||||||
|
|
||||||
gui = { path="../gui/" }
|
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
debug = 2
|
debug = 2
|
||||||
1
gui/.gitignore
vendored
1
gui/.gitignore
vendored
@ -1 +0,0 @@
|
|||||||
/target
|
|
||||||
@ -1,9 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "gui"
|
|
||||||
version = "0.1.0"
|
|
||||||
edition = "2021"
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
embedded-graphics = "0.8.1"
|
|
||||||
embedded-layout = "0.4.1"
|
|
||||||
profont = "0.7.0"
|
|
||||||
158
gui/src/lib.rs
158
gui/src/lib.rs
@ -1,158 +0,0 @@
|
|||||||
#![no_std]
|
|
||||||
|
|
||||||
use embedded_graphics::{pixelcolor::Rgb565, prelude::*};
|
|
||||||
|
|
||||||
mod widgets;
|
|
||||||
|
|
||||||
use core::cmp::PartialEq;
|
|
||||||
use core::result::Result::{self, Ok};
|
|
||||||
|
|
||||||
pub type Color = Rgb565;
|
|
||||||
|
|
||||||
/// Enum representing the valid actions that can be performed in the GUI.
|
|
||||||
/// Each variant corresponds to a user input action that the interface can respond to.
|
|
||||||
#[derive(PartialEq, Clone, Copy)]
|
|
||||||
pub enum GuiAction {
|
|
||||||
Up,
|
|
||||||
Down,
|
|
||||||
Left,
|
|
||||||
Right,
|
|
||||||
Select,
|
|
||||||
Back,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Enum representing all available views in the GUI.
|
|
||||||
/// Each variant corresponds to a different screen or state within the application.
|
|
||||||
mod views;
|
|
||||||
pub enum GuiView {
|
|
||||||
MainMenu(views::main_menu::State),
|
|
||||||
Scan,
|
|
||||||
Settings,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl GuiView {
|
|
||||||
/// Draws the current view to the provided display target.
|
|
||||||
/// This method maps each individual view's drawing function to a common interface,
|
|
||||||
/// allowing for a unified way to render different views.
|
|
||||||
///
|
|
||||||
/// # Parameters
|
|
||||||
/// - `display`: A mutable reference to the display target where the view will be drawn.
|
|
||||||
///
|
|
||||||
/// # Returns
|
|
||||||
/// - `Result<(), D::Error>`: Returns Ok if drawing was successful, or an error if it failed.
|
|
||||||
pub fn draw<D>(&self, display: &mut D) -> Result<(), D::Error>
|
|
||||||
where
|
|
||||||
D: DrawTarget<Color = Color>,
|
|
||||||
{
|
|
||||||
match self {
|
|
||||||
Self::MainMenu(state) => views::main_menu::draw(state, display)?,
|
|
||||||
Self::Scan => views::scan::draw(display)?,
|
|
||||||
Self::Settings => views::settings::draw(display)?,
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Handles user actions based on the current view.
|
|
||||||
/// This method maps individual action handling functions to a common interface,
|
|
||||||
/// allowing the GUI to respond to user inputs appropriately depending on the current view.
|
|
||||||
///
|
|
||||||
/// # Parameters
|
|
||||||
/// - `action`: The action that has occurred, represented by the `GuiAction` enum.
|
|
||||||
///
|
|
||||||
/// # Returns
|
|
||||||
/// - `Self`: Returns the updated view after processing the action.
|
|
||||||
/// The state may change based on the action taken, particularly in the MainMenu.
|
|
||||||
pub fn action(self, action: GuiAction) -> Self {
|
|
||||||
match self {
|
|
||||||
Self::MainMenu(state) => views::main_menu::action(state, action),
|
|
||||||
Self::Scan => Self::Scan,
|
|
||||||
Self::Settings => Self::Settings,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A struct representing the graphical user interface (GUI).
|
|
||||||
/// It holds the current display target and the active view of the GUI.
|
|
||||||
pub struct Gui<D>
|
|
||||||
where
|
|
||||||
// The display must implement the DrawTarget trait with a specified Color type
|
|
||||||
D: DrawTarget<Color = Color>,
|
|
||||||
{
|
|
||||||
/// The display target where the GUI will be rendered
|
|
||||||
display: D,
|
|
||||||
/// The current view of the GUI, represented by the GuiView enum
|
|
||||||
view: GuiView,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<D> Gui<D>
|
|
||||||
where
|
|
||||||
D: DrawTarget<Color = Color>,
|
|
||||||
{
|
|
||||||
/// Creates a new instance of the GUI with the specified display target.
|
|
||||||
/// Initializes the GUI to start in the Main Menu view with the first item selected.
|
|
||||||
///
|
|
||||||
/// # Parameters
|
|
||||||
/// - `display`: The display target to be used for rendering the GUI.
|
|
||||||
///
|
|
||||||
/// # Returns
|
|
||||||
/// - `Self`: A new instance of the Gui struct.
|
|
||||||
pub fn new(display: D) -> Self {
|
|
||||||
Self {
|
|
||||||
view: GuiView::MainMenu(views::main_menu::State { selected: 0 }), // Init on main menu
|
|
||||||
display,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Processes a user action and updates the GUI state accordingly.
|
|
||||||
/// This method delegates the action handling to the current view's action method.
|
|
||||||
///
|
|
||||||
/// # Parameters
|
|
||||||
/// - `gui_action`: The action that has occurred, represented by the GuiAction enum.
|
|
||||||
///
|
|
||||||
/// # Returns
|
|
||||||
/// - `Self`: A new instance of the Gui struct with the updated view.
|
|
||||||
pub fn action(self, gui_action: GuiAction) -> Self {
|
|
||||||
// Get the resulting view after the action has been performed
|
|
||||||
let new_view = self.view.action(gui_action);
|
|
||||||
|
|
||||||
// Create a new GUI with the new view, retaining the display
|
|
||||||
Self {
|
|
||||||
display: self.display, // Move display to new GUI struct
|
|
||||||
view: new_view, // Set the new view
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Fills the entire display with a solid black color.
|
|
||||||
/// This can be used to clear the screen before drawing the next frame.
|
|
||||||
///
|
|
||||||
/// # Returns
|
|
||||||
/// - `Result<(), D::Error>`: Returns Ok if the fill operation was successful, or an error if it failed.
|
|
||||||
pub fn fill_black(&mut self) -> Result<(), D::Error> {
|
|
||||||
let display: &mut D = &mut self.display;
|
|
||||||
display.fill_solid(&display.bounding_box(), Color::BLACK)?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Draws the current view of the GUI onto the display.
|
|
||||||
/// This method calls the draw method of the current view, rendering it to the display target.
|
|
||||||
///
|
|
||||||
/// # Returns
|
|
||||||
/// - `Result<(), D::Error>`: Returns Ok if the drawing operation was successful, or an error if it failed.
|
|
||||||
pub fn draw(&mut self) -> Result<(), D::Error> {
|
|
||||||
let display: &mut D = &mut self.display; // Get a mutable reference to the display
|
|
||||||
self.view.draw(display)?; // Draw the view
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns a reference to the display target.
|
|
||||||
/// This can be useful for accessing the display outside of the GUI struct.
|
|
||||||
///
|
|
||||||
/// # Returns
|
|
||||||
/// - `&D`: A reference to the display target.
|
|
||||||
pub fn display_ref(&self) -> &D {
|
|
||||||
&self.display // Return a reference to the display
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,72 +0,0 @@
|
|||||||
use crate::widgets::{button::Button, header::Header};
|
|
||||||
use crate::{Color, GuiAction, GuiView};
|
|
||||||
use core::result::Result::{self, Ok};
|
|
||||||
use embedded_graphics::prelude::*;
|
|
||||||
use embedded_layout::{
|
|
||||||
layout::linear::{FixedMargin, LinearLayout},
|
|
||||||
prelude::*,
|
|
||||||
};
|
|
||||||
|
|
||||||
fn create_button(button_index: u8, selected_index: u8, text: &str) -> Button {
|
|
||||||
let button_width = 240;
|
|
||||||
let button_height = 60;
|
|
||||||
|
|
||||||
if button_index == selected_index {
|
|
||||||
Button::new(Point::zero(), Size::new(button_width, button_height), text).toggle_highlight()
|
|
||||||
} else {
|
|
||||||
Button::new(Point::zero(), Size::new(button_width, button_height), text)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn draw<D>(state: &State, display: &mut D) -> Result<(), D::Error>
|
|
||||||
where
|
|
||||||
D: DrawTarget<Color = Color>,
|
|
||||||
{
|
|
||||||
// Create the header
|
|
||||||
let header = Header::new("Main Menu");
|
|
||||||
header.draw(display)?;
|
|
||||||
|
|
||||||
// Create the buttons
|
|
||||||
let scan_button = create_button(0, state.selected, "Scan");
|
|
||||||
let settings_button = create_button(1, state.selected, "Settings");
|
|
||||||
|
|
||||||
// Draw the buttons in a vertical layout
|
|
||||||
LinearLayout::vertical(Chain::new(scan_button).append(settings_button))
|
|
||||||
.with_spacing(FixedMargin(10))
|
|
||||||
.arrange()
|
|
||||||
.align_to(
|
|
||||||
&display.bounding_box(),
|
|
||||||
horizontal::Center,
|
|
||||||
vertical::Center,
|
|
||||||
)
|
|
||||||
.draw(display)?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct State {
|
|
||||||
/// Which button is currently highlighted
|
|
||||||
pub selected: u8,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn action(state: State, action: GuiAction) -> GuiView {
|
|
||||||
let num_buttons: u8 = 2;
|
|
||||||
|
|
||||||
let new_selected = match (action, state.selected) {
|
|
||||||
// Check if a button was "pressed"
|
|
||||||
(GuiAction::Select, 0) => return GuiView::Scan,
|
|
||||||
(GuiAction::Select, 1) => return GuiView::Settings,
|
|
||||||
|
|
||||||
// Scroll the selected and output the new selected
|
|
||||||
(GuiAction::Up, 0) => num_buttons - 1,
|
|
||||||
(GuiAction::Up, _) => state.selected - 1,
|
|
||||||
(GuiAction::Down, i) if i == num_buttons - 1 => 0,
|
|
||||||
(GuiAction::Down, _) => state.selected + 1,
|
|
||||||
_ => 0,
|
|
||||||
};
|
|
||||||
|
|
||||||
GuiView::MainMenu(State {
|
|
||||||
selected: new_selected,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@ -1,4 +0,0 @@
|
|||||||
pub mod main_menu;
|
|
||||||
pub mod scan;
|
|
||||||
pub mod settings;
|
|
||||||
pub mod void;
|
|
||||||
@ -1,12 +0,0 @@
|
|||||||
use crate::widgets::header::Header;
|
|
||||||
use core::result::Result::{self, Ok};
|
|
||||||
use embedded_graphics::prelude::*;
|
|
||||||
|
|
||||||
pub fn draw<D>(display: &mut D) -> Result<(), D::Error>
|
|
||||||
where
|
|
||||||
D: DrawTarget<Color = crate::Color>,
|
|
||||||
{
|
|
||||||
Header::new("Scan page").draw(display)?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
@ -1,12 +0,0 @@
|
|||||||
use crate::widgets::header::Header;
|
|
||||||
use core::result::Result::{self, Ok};
|
|
||||||
use embedded_graphics::prelude::*;
|
|
||||||
|
|
||||||
pub fn draw<D>(display: &mut D) -> Result<(), D::Error>
|
|
||||||
where
|
|
||||||
D: DrawTarget<Color = crate::Color>,
|
|
||||||
{
|
|
||||||
Header::new("Settings page").draw(display)?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
@ -1,27 +0,0 @@
|
|||||||
use crate::widgets::button::Button;
|
|
||||||
use core::result::Result::{self, Ok};
|
|
||||||
use embedded_graphics::prelude::*;
|
|
||||||
use embedded_layout::{
|
|
||||||
layout::linear::{FixedMargin, LinearLayout},
|
|
||||||
prelude::*,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub fn draw<D>(display: &mut D) -> Result<(), D::Error>
|
|
||||||
where
|
|
||||||
D: DrawTarget<Color = crate::Color>,
|
|
||||||
{
|
|
||||||
let button1 = Button::new(Point::zero(), Size::new(100, 50), "Void");
|
|
||||||
let button2 = Button::new(Point::zero(), Size::new(100, 50), "Void");
|
|
||||||
|
|
||||||
LinearLayout::vertical(Chain::new(button1).append(button2))
|
|
||||||
.with_spacing(FixedMargin(10))
|
|
||||||
.arrange()
|
|
||||||
.align_to(
|
|
||||||
&display.bounding_box(),
|
|
||||||
horizontal::Center,
|
|
||||||
vertical::Center,
|
|
||||||
)
|
|
||||||
.draw(display)?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
@ -1,90 +0,0 @@
|
|||||||
use core::result::Result::{self, Ok};
|
|
||||||
use embedded_graphics::{
|
|
||||||
mono_font::{ascii::FONT_10X20, MonoTextStyle},
|
|
||||||
pixelcolor::Rgb888,
|
|
||||||
prelude::*,
|
|
||||||
primitives::{PrimitiveStyle, Rectangle},
|
|
||||||
text::Text,
|
|
||||||
};
|
|
||||||
use embedded_layout::{
|
|
||||||
align::{horizontal, vertical, Align},
|
|
||||||
View,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub struct Button<'a> {
|
|
||||||
bounds: Rectangle,
|
|
||||||
pub highlighted: bool,
|
|
||||||
pub text: &'a str,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> Button<'a> {
|
|
||||||
pub fn new(position: Point, size: Size, text: &'a str) -> Self {
|
|
||||||
Self {
|
|
||||||
bounds: Rectangle::new(position, size),
|
|
||||||
highlighted: false,
|
|
||||||
text,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn toggle_highlight(self) -> Self {
|
|
||||||
Self {
|
|
||||||
bounds: self.bounds,
|
|
||||||
highlighted: !self.highlighted,
|
|
||||||
text: self.text,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl View for Button<'_> {
|
|
||||||
#[inline]
|
|
||||||
fn translate_impl(&mut self, by: Point) {
|
|
||||||
View::translate_mut(&mut self.bounds, by);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn bounds(&self) -> Rectangle {
|
|
||||||
self.bounds
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Drawable for Button<'_> {
|
|
||||||
type Color = crate::Color;
|
|
||||||
type Output = ();
|
|
||||||
|
|
||||||
fn draw<D>(&self, target: &mut D) -> Result<Self::Output, D::Error>
|
|
||||||
where
|
|
||||||
D: DrawTarget<Color = Self::Color>,
|
|
||||||
{
|
|
||||||
// Create styles
|
|
||||||
let border_style = PrimitiveStyle::with_stroke(Self::Color::WHITE, 5);
|
|
||||||
|
|
||||||
let normal_background_style = PrimitiveStyle::with_fill(Self::Color::CSS_GRAY);
|
|
||||||
let highlighted_background_style = PrimitiveStyle::with_fill(Self::Color::BLUE);
|
|
||||||
|
|
||||||
let background_style = if self.highlighted {
|
|
||||||
highlighted_background_style
|
|
||||||
} else {
|
|
||||||
normal_background_style
|
|
||||||
};
|
|
||||||
|
|
||||||
let character_style = MonoTextStyle::new(&FONT_10X20, Self::Color::WHITE);
|
|
||||||
|
|
||||||
// Create the border
|
|
||||||
let border = self.bounds.into_styled(border_style);
|
|
||||||
|
|
||||||
// Create the button fill
|
|
||||||
let fill = Rectangle::new(Point::zero(), self.bounds.size()).into_styled(background_style);
|
|
||||||
let fill = fill.align_to(&border, horizontal::Left, vertical::Center);
|
|
||||||
|
|
||||||
// Create the button text
|
|
||||||
let text = Text::new(self.text, Point::zero(), character_style);
|
|
||||||
let text = text.align_to(&border, horizontal::Center, vertical::Center);
|
|
||||||
|
|
||||||
// Draw the views
|
|
||||||
fill.draw(target)?;
|
|
||||||
border.draw(target)?;
|
|
||||||
text.draw(target)?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,68 +0,0 @@
|
|||||||
use core::result::Result::{self, Ok};
|
|
||||||
use embedded_graphics::{
|
|
||||||
mono_font::MonoTextStyle,
|
|
||||||
prelude::*,
|
|
||||||
primitives::{PrimitiveStyle, Rectangle},
|
|
||||||
text::Text,
|
|
||||||
};
|
|
||||||
use embedded_layout::{
|
|
||||||
align::{horizontal, vertical, Align},
|
|
||||||
View,
|
|
||||||
};
|
|
||||||
use profont::PROFONT_24_POINT;
|
|
||||||
|
|
||||||
use crate::Color;
|
|
||||||
|
|
||||||
pub struct Header<'a> {
|
|
||||||
bounds: Rectangle,
|
|
||||||
pub text: &'a str,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> Header<'a> {
|
|
||||||
pub fn new(text: &'a str) -> Self {
|
|
||||||
Self {
|
|
||||||
bounds: Rectangle::new(Point::zero(), Size::new(240, 30)),
|
|
||||||
text,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl View for Header<'_> {
|
|
||||||
#[inline]
|
|
||||||
fn translate_impl(&mut self, by: Point) {
|
|
||||||
View::translate_mut(&mut self.bounds, by);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn bounds(&self) -> Rectangle {
|
|
||||||
self.bounds
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Drawable for Header<'_> {
|
|
||||||
type Color = crate::Color;
|
|
||||||
type Output = ();
|
|
||||||
|
|
||||||
fn draw<D>(&self, target: &mut D) -> Result<Self::Output, D::Error>
|
|
||||||
where
|
|
||||||
D: DrawTarget<Color = crate::Color>,
|
|
||||||
{
|
|
||||||
// Create styles
|
|
||||||
let background_style = PrimitiveStyle::with_fill(Color::CSS_MINT_CREAM);
|
|
||||||
let character_style = MonoTextStyle::new(&PROFONT_24_POINT, Self::Color::BLACK);
|
|
||||||
|
|
||||||
// Create the background
|
|
||||||
let background =
|
|
||||||
Rectangle::new(Point::zero(), self.bounds.size()).into_styled(background_style);
|
|
||||||
|
|
||||||
// Create the text
|
|
||||||
let text = Text::new(self.text, Point::zero(), character_style);
|
|
||||||
let text = text.align_to(&background, horizontal::Center, vertical::Center);
|
|
||||||
|
|
||||||
// Draw the menu
|
|
||||||
background.draw(target)?;
|
|
||||||
text.draw(target)?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,3 +0,0 @@
|
|||||||
pub mod button;
|
|
||||||
pub mod header;
|
|
||||||
pub mod progress;
|
|
||||||
@ -1,95 +0,0 @@
|
|||||||
use core::result::Result::{self, Ok};
|
|
||||||
use embedded_graphics::{
|
|
||||||
geometry::{Point, Size},
|
|
||||||
pixelcolor::Rgb888,
|
|
||||||
prelude::*,
|
|
||||||
primitives::{PrimitiveStyle, Rectangle},
|
|
||||||
};
|
|
||||||
use embedded_layout::{
|
|
||||||
align::{horizontal, vertical, Align},
|
|
||||||
View,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub struct ProgressBar {
|
|
||||||
progress: u32,
|
|
||||||
bounds: Rectangle,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ProgressBar {
|
|
||||||
/// The progress bar needs a configurable position and size
|
|
||||||
pub fn new(position: Point, size: Size) -> Self {
|
|
||||||
Self {
|
|
||||||
bounds: Rectangle::new(position, size),
|
|
||||||
progress: 0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The progress bar needs a configurable position and size
|
|
||||||
pub fn with_progress(self, progress: u32) -> Self {
|
|
||||||
Self {
|
|
||||||
bounds: self.bounds,
|
|
||||||
progress,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn increment(self) -> Self {
|
|
||||||
Self {
|
|
||||||
bounds: self.bounds,
|
|
||||||
progress: self.progress + 1,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Impleenting `View` is required by the layout and alignment operations
|
|
||||||
/// `View` teaches `embedded-layout` where our object is, how big it is, and how to move it.
|
|
||||||
impl View for ProgressBar {
|
|
||||||
#[inline]
|
|
||||||
fn translate_impl(&mut self, by: Point) {
|
|
||||||
// NB: Do not use translate (non-mut)
|
|
||||||
View::translate_mut(&mut self.bounds, by);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn bounds(&self) -> Rectangle {
|
|
||||||
self.bounds
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// We need to implement `Drawable` for a reference of our view
|
|
||||||
impl Drawable for ProgressBar {
|
|
||||||
type Color = Rgb888;
|
|
||||||
type Output = ();
|
|
||||||
|
|
||||||
fn draw<D>(&self, target: &mut D) -> Result<Self::Output, D::Error>
|
|
||||||
where
|
|
||||||
D: DrawTarget<Color = Self::Color>,
|
|
||||||
{
|
|
||||||
// Create styles
|
|
||||||
let border_style = PrimitiveStyle::with_stroke(Self::Color::WHITE, 5);
|
|
||||||
let progress_style = PrimitiveStyle::with_fill(Self::Color::RED);
|
|
||||||
|
|
||||||
// Create a border
|
|
||||||
let border = self.bounds.into_styled(border_style);
|
|
||||||
|
|
||||||
// Create a rectangle to indicate progress
|
|
||||||
let progress = Rectangle::new(
|
|
||||||
Point::zero(),
|
|
||||||
Size::new(
|
|
||||||
(self.bounds.size().width - 4) * self.progress / 100,
|
|
||||||
self.bounds.size().height,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.into_styled(progress_style);
|
|
||||||
|
|
||||||
// Align the progress bar with the border
|
|
||||||
let progress = progress
|
|
||||||
.align_to(&border, horizontal::Left, vertical::Center)
|
|
||||||
.translate(Point::new(6, 0));
|
|
||||||
|
|
||||||
// Draw the views
|
|
||||||
progress.draw(target)?;
|
|
||||||
border.draw(target)?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,12 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "gui-test"
|
|
||||||
version = "0.1.0"
|
|
||||||
edition = "2021"
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
embedded-graphics = "0.8.1"
|
|
||||||
embedded-graphics-simulator = "0.7.0"
|
|
||||||
embedded-layout = "0.4.1"
|
|
||||||
heapless = "0.8.0"
|
|
||||||
profont = "0.7.0"
|
|
||||||
gui = { path = "../gui/" }
|
|
||||||
@ -1,43 +0,0 @@
|
|||||||
use embedded_graphics::{geometry::Size, pixelcolor::Rgb565};
|
|
||||||
use embedded_graphics_simulator::{
|
|
||||||
sdl2::Keycode, OutputSettingsBuilder, SimulatorDisplay, SimulatorEvent, Window,
|
|
||||||
};
|
|
||||||
|
|
||||||
use gui::*;
|
|
||||||
|
|
||||||
fn main() -> Result<(), core::convert::Infallible> {
|
|
||||||
let display = SimulatorDisplay::<Rgb565>::new(Size::new(240, 320));
|
|
||||||
|
|
||||||
let output_settings = OutputSettingsBuilder::new().scale(1).build();
|
|
||||||
let mut window = Window::new("Hello World", &output_settings);
|
|
||||||
|
|
||||||
let mut gui = Gui::new(display);
|
|
||||||
|
|
||||||
'running: loop {
|
|
||||||
gui.draw()?;
|
|
||||||
window.update(gui.display_ref());
|
|
||||||
|
|
||||||
for event in window.events() {
|
|
||||||
match event {
|
|
||||||
SimulatorEvent::Quit => break 'running,
|
|
||||||
SimulatorEvent::KeyDown { keycode, .. } => {
|
|
||||||
let action: GuiAction = match keycode {
|
|
||||||
Keycode::Left => GuiAction::Left,
|
|
||||||
Keycode::Right => GuiAction::Right,
|
|
||||||
Keycode::Up => GuiAction::Up,
|
|
||||||
Keycode::Down => GuiAction::Down,
|
|
||||||
Keycode::Return => GuiAction::Select,
|
|
||||||
Keycode::Backspace => GuiAction::Back,
|
|
||||||
_ => continue,
|
|
||||||
};
|
|
||||||
|
|
||||||
gui = gui.action(action);
|
|
||||||
gui.fill_black()?;
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
@ -2,17 +2,15 @@
|
|||||||
#![no_main]
|
#![no_main]
|
||||||
|
|
||||||
use core::cell::RefCell;
|
use core::cell::RefCell;
|
||||||
use defmt::{error, info};
|
|
||||||
use display_interface_spi::SPIInterface;
|
use display_interface_spi::SPIInterface;
|
||||||
use embassy_embedded_hal::shared_bus::blocking::spi::SpiDevice;
|
use embassy_embedded_hal::shared_bus::blocking::spi::SpiDevice;
|
||||||
use embassy_executor::Spawner;
|
use embassy_executor::Spawner;
|
||||||
use embassy_nrf::{
|
use embassy_nrf::{
|
||||||
bind_interrupts,
|
bind_interrupts,
|
||||||
gpio::{AnyPin, Input, Level, Output, OutputDrive, Pin},
|
gpio::{AnyPin, Level, Output, OutputDrive, Pin},
|
||||||
peripherals, spim,
|
peripherals, spim,
|
||||||
};
|
};
|
||||||
use embassy_sync::blocking_mutex::{raw::ThreadModeRawMutex, Mutex};
|
use embassy_sync::blocking_mutex::Mutex;
|
||||||
use embassy_sync::channel::{Channel, Sender};
|
|
||||||
use embassy_time::{Duration, Timer};
|
use embassy_time::{Duration, Timer};
|
||||||
use ili9341::Ili9341;
|
use ili9341::Ili9341;
|
||||||
use {defmt_rtt as _, panic_probe as _};
|
use {defmt_rtt as _, panic_probe as _};
|
||||||
@ -26,7 +24,6 @@ use embedded_graphics::{
|
|||||||
},
|
},
|
||||||
text::{Alignment, Text},
|
text::{Alignment, Text},
|
||||||
};
|
};
|
||||||
use gui::*;
|
|
||||||
|
|
||||||
// Setup interrupts
|
// Setup interrupts
|
||||||
bind_interrupts!(struct Irqs {
|
bind_interrupts!(struct Irqs {
|
||||||
@ -52,100 +49,77 @@ async fn blink(pin: AnyPin, blink_delay: Duration) -> ! {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static GUI_CHANNEL: Channel<ThreadModeRawMutex, gui::GuiAction, 8> = Channel::new();
|
/// Create a function to run the demo
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// This function takes in a variable of a generic type `D`.
|
||||||
|
/// With the restriction that the generic type `D` implements
|
||||||
|
/// `DrawTarget`, aka that it is a display.
|
||||||
|
///
|
||||||
|
/// I've also said it needs to use a certain colormode, because
|
||||||
|
/// that is the color mode I use in the demo.
|
||||||
|
///
|
||||||
|
/// While this may seem a little clunky this function now accepts _any_
|
||||||
|
/// display as long as it uses the standard 565 colormode.
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// The function returns a `Result` containing either an empty tuple
|
||||||
|
/// or the error type associated with the generic type `D`.
|
||||||
|
fn display_demo<D>(display: &mut D) -> Result<(), D::Error>
|
||||||
|
where
|
||||||
|
D: DrawTarget<Color = Rgb565>,
|
||||||
|
{
|
||||||
|
display.clear(Rgb565::CSS_DARK_OLIVE_GREEN)?;
|
||||||
|
|
||||||
#[embassy_executor::task]
|
// Create styles used by the drawing operations.
|
||||||
async fn gui_task(
|
let thin_stroke = PrimitiveStyle::with_stroke(Rgb565::WHITE, 1);
|
||||||
peripheral: embassy_nrf::peripherals::SERIAL3,
|
let thick_stroke = PrimitiveStyle::with_stroke(Rgb565::WHITE, 3);
|
||||||
sck: AnyPin,
|
let border_stroke = PrimitiveStyleBuilder::new()
|
||||||
miso: AnyPin,
|
.stroke_color(Rgb565::WHITE)
|
||||||
mosi: AnyPin,
|
.stroke_width(3)
|
||||||
display_reset_pin: AnyPin,
|
.stroke_alignment(StrokeAlignment::Inside)
|
||||||
cs_pin: AnyPin,
|
.build();
|
||||||
dc_pin: AnyPin,
|
let fill = PrimitiveStyle::with_fill(Rgb565::WHITE);
|
||||||
) -> ! {
|
let character_style = MonoTextStyle::new(&FONT_10X20, Rgb565::WHITE);
|
||||||
///////////////
|
|
||||||
// Setup SPI //
|
|
||||||
///////////////
|
|
||||||
|
|
||||||
// Create an SPI config with the SPI frequency at 8MHz
|
let yoffset = 10;
|
||||||
let mut spim_config = spim::Config::default();
|
|
||||||
spim_config.frequency = spim::Frequency::M8;
|
|
||||||
|
|
||||||
// Grab the SPI Master peripheral form the nRF60
|
// Draw a 3px wide outline around the display.
|
||||||
let spim = embassy_nrf::spim::Spim::new(peripheral, Irqs, sck, miso, mosi, spim_config);
|
display
|
||||||
// Use the SPI peripheral to create a shared SPI bus
|
.bounding_box()
|
||||||
let spi_bus: Mutex<embassy_sync::blocking_mutex::raw::NoopRawMutex, _> =
|
.into_styled(border_stroke)
|
||||||
Mutex::new(RefCell::new(spim));
|
.draw(display)?;
|
||||||
|
|
||||||
////////////////
|
// Draw a triangle.
|
||||||
// Setup pins //
|
Triangle::new(
|
||||||
////////////////
|
Point::new(16, 16 + yoffset),
|
||||||
|
Point::new(16 + 16, 16 + yoffset),
|
||||||
// Initialize the CS, DC, and display reset pins as outputs
|
Point::new(16 + 8, yoffset),
|
||||||
let chip_select = Output::new(cs_pin, Level::High, OutputDrive::Standard);
|
|
||||||
let direction_control = Output::new(dc_pin, Level::High, OutputDrive::Standard);
|
|
||||||
let display_reset = Output::new(display_reset_pin, Level::High, OutputDrive::Standard);
|
|
||||||
|
|
||||||
////////////////////////
|
|
||||||
// Create the display //
|
|
||||||
////////////////////////
|
|
||||||
|
|
||||||
// Use the shared bus to create a SPI Device to use for the display
|
|
||||||
let spi_display = SpiDevice::new(&spi_bus, chip_select);
|
|
||||||
// Create a new SPI display interface
|
|
||||||
let iface = SPIInterface::new(spi_display, direction_control);
|
|
||||||
|
|
||||||
// Some options for the display
|
|
||||||
let display_orientation = ili9341::Orientation::PortraitFlipped;
|
|
||||||
let display_size = ili9341::DisplaySize240x320;
|
|
||||||
|
|
||||||
// Create the display
|
|
||||||
let display = Ili9341::new(
|
|
||||||
iface,
|
|
||||||
display_reset,
|
|
||||||
&mut embassy_time::Delay,
|
|
||||||
display_orientation,
|
|
||||||
display_size,
|
|
||||||
)
|
)
|
||||||
.unwrap();
|
.into_styled(thin_stroke)
|
||||||
|
.draw(display)?;
|
||||||
|
|
||||||
let mut gui = Gui::new(display);
|
// Draw a filled square
|
||||||
gui.draw().expect("Failed to draw GUI");
|
Rectangle::new(Point::new(52, yoffset), Size::new(16, 16))
|
||||||
|
.into_styled(fill)
|
||||||
|
.draw(display)?;
|
||||||
|
|
||||||
loop {
|
// Draw a circle with a 3px wide stroke.
|
||||||
info!("Waiting for GUI event");
|
Circle::new(Point::new(88, yoffset), 17)
|
||||||
// Wait to receive a GUI event
|
.into_styled(thick_stroke)
|
||||||
let gui_action = GUI_CHANNEL.receive().await;
|
.draw(display)?;
|
||||||
// Re-render the GUI
|
|
||||||
info!("Received event, calculating new screen");
|
|
||||||
gui = gui.action(gui_action);
|
|
||||||
|
|
||||||
// Clear the screen
|
// Draw centered text.
|
||||||
info!("Clearing screen");
|
let text = "embedded-graphics";
|
||||||
gui.fill_black().expect("Failed to clear screen");
|
Text::with_alignment(
|
||||||
// Draw the GUI
|
text,
|
||||||
info!("Drawing screen");
|
display.bounding_box().center() + Point::new(0, 15),
|
||||||
gui.draw().expect("Failed to draw GUI");
|
character_style,
|
||||||
}
|
Alignment::Center,
|
||||||
}
|
)
|
||||||
|
.draw(display)?;
|
||||||
|
|
||||||
#[embassy_executor::task(pool_size = 2)]
|
Ok(())
|
||||||
async fn input_controller(
|
|
||||||
control: Sender<'static, ThreadModeRawMutex, gui::GuiAction, 8>,
|
|
||||||
pin: AnyPin,
|
|
||||||
action: gui::GuiAction,
|
|
||||||
) -> ! {
|
|
||||||
let mut button = Input::new(pin, embassy_nrf::gpio::Pull::Up);
|
|
||||||
|
|
||||||
loop {
|
|
||||||
// Wait for the button to be pressed
|
|
||||||
button.wait_for_falling_edge().await;
|
|
||||||
// Send the event
|
|
||||||
if let Err(_e) = control.try_send(action) {
|
|
||||||
error!("Failed to send input action with error");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[embassy_executor::main]
|
#[embassy_executor::main]
|
||||||
@ -163,6 +137,10 @@ async fn main(spawner: Spawner) {
|
|||||||
)
|
)
|
||||||
.expect("Failed to start blink task");
|
.expect("Failed to start blink task");
|
||||||
|
|
||||||
|
////////////////
|
||||||
|
// Setup pins //
|
||||||
|
////////////////
|
||||||
|
|
||||||
let mosi = p.P0_13;
|
let mosi = p.P0_13;
|
||||||
let miso = p.P0_12;
|
let miso = p.P0_12;
|
||||||
let sck = p.P0_30;
|
let sck = p.P0_30;
|
||||||
@ -170,38 +148,51 @@ async fn main(spawner: Spawner) {
|
|||||||
let dc_pin = p.P0_20; // Data / Command pin for the display
|
let dc_pin = p.P0_20; // Data / Command pin for the display
|
||||||
let disp_rst_pin = p.P0_11;
|
let disp_rst_pin = p.P0_11;
|
||||||
|
|
||||||
// Create the GUI
|
// Initialize the CS, DC, and display reset pins as outputs
|
||||||
spawner
|
let chip_select = Output::new(cs_pin, Level::High, OutputDrive::Standard);
|
||||||
.spawn(gui_task(
|
let direction_control = Output::new(dc_pin, Level::High, OutputDrive::Standard);
|
||||||
p.SERIAL3,
|
let display_reset = Output::new(disp_rst_pin, Level::High, OutputDrive::Standard);
|
||||||
sck.degrade(),
|
|
||||||
miso.degrade(),
|
|
||||||
mosi.degrade(),
|
|
||||||
disp_rst_pin.degrade(),
|
|
||||||
cs_pin.degrade(),
|
|
||||||
dc_pin.degrade(),
|
|
||||||
))
|
|
||||||
.expect("Failed to spawn GUI task");
|
|
||||||
|
|
||||||
// Create the GUI controllers
|
///////////////
|
||||||
spawner
|
// Setup SPI //
|
||||||
.spawn(input_controller(
|
///////////////
|
||||||
GUI_CHANNEL.sender(),
|
|
||||||
p.P0_06.degrade(),
|
// Create an SPI config with the SPI frequency at 8MHz
|
||||||
gui::GuiAction::Down,
|
let mut spim_config = spim::Config::default();
|
||||||
))
|
spim_config.frequency = spim::Frequency::M8;
|
||||||
.expect("Failed tp spawn DOWN input controller");
|
|
||||||
spawner
|
// Grab the SPI Master peripheral form the nRF60
|
||||||
.spawn(input_controller(
|
let spim = embassy_nrf::spim::Spim::new(p.SERIAL3, Irqs, sck, miso, mosi, spim_config);
|
||||||
GUI_CHANNEL.sender(),
|
// Use the SPI peripheral to create a shared SPI bus
|
||||||
p.P0_07.degrade(),
|
let spi_bus: Mutex<embassy_sync::blocking_mutex::raw::NoopRawMutex, _> =
|
||||||
gui::GuiAction::Select,
|
Mutex::new(RefCell::new(spim));
|
||||||
))
|
|
||||||
.expect("Failed tp spawn Select input controller");
|
// Use the shared bus to create a SPI Device to use for the display
|
||||||
|
let spi_display = SpiDevice::new(&spi_bus, chip_select);
|
||||||
|
// Create a new SPI display interface
|
||||||
|
let iface = SPIInterface::new(spi_display, direction_control);
|
||||||
|
|
||||||
|
////////////////////////
|
||||||
|
// Create the display //
|
||||||
|
////////////////////////
|
||||||
|
|
||||||
|
// Some options for the display
|
||||||
|
let display_orientation = ili9341::Orientation::Landscape;
|
||||||
|
let display_size = ili9341::DisplaySize240x320;
|
||||||
|
|
||||||
|
// Create the display
|
||||||
|
let mut display = Ili9341::new(
|
||||||
|
iface,
|
||||||
|
display_reset,
|
||||||
|
&mut embassy_time::Delay,
|
||||||
|
display_orientation,
|
||||||
|
display_size,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
// Now loop, re-running the demo every second
|
// Now loop, re-running the demo every second
|
||||||
loop {
|
loop {
|
||||||
info!("Hello, world!");
|
display_demo(&mut display).expect("Failed to run display demo");
|
||||||
Timer::after_secs(1).await;
|
Timer::after_secs(1).await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Loading…
Reference in New Issue
Block a user