Initial commit

This commit is contained in:
2025-11-28 12:53:28 +01:00
commit 9a880c23ac
17 changed files with 1891 additions and 0 deletions

39
src/images.rs Normal file
View File

@ -0,0 +1,39 @@
use std::{cell::UnsafeCell, mem::MaybeUninit};
use embedded_graphics::pixelcolor::Rgb565;
use tinybmp::Bmp;
thread_local! {
pub static HUMIDITY_ICON: UnsafeCell<MaybeUninit<Bmp<'static, Rgb565>>> = const { UnsafeCell::new(MaybeUninit::zeroed()) };
pub static TEMPERATURE_ICON: UnsafeCell<MaybeUninit<Bmp<'static, Rgb565>>> = const { UnsafeCell::new(MaybeUninit::zeroed()) };
pub static VOC_ICON: UnsafeCell<MaybeUninit<Bmp<'static, Rgb565>>> = const { UnsafeCell::new(MaybeUninit::zeroed()) };
pub static CO2_ICON: UnsafeCell<MaybeUninit<Bmp<'static, Rgb565>>> = const { UnsafeCell::new(MaybeUninit::zeroed()) };
}
macro_rules! load_image {
($stat:expr, $path:expr) => {
$stat
.with(|x| *x.get() = MaybeUninit::new(Bmp::from_slice(include_bytes!($path)).unwrap()));
};
}
#[macro_export]
macro_rules! get_image {
($image:expr) => {
unsafe {
&*std::mem::transmute::<*mut MaybeUninit<Bmp<Rgb565>>, *mut Bmp<Rgb565>>(
$image.with(|g| g.get()),
)
}
};
}
pub fn prepare_images()
{
unsafe {
load_image!(HUMIDITY_ICON, "../assets/humidity-icon.bmp");
load_image!(TEMPERATURE_ICON, "../assets/temperature-icon.bmp");
load_image!(VOC_ICON, "../assets/voc-icon.bmp");
load_image!(CO2_ICON, "../assets/co2-icon.bmp");
}
}

167
src/main.rs Normal file
View File

@ -0,0 +1,167 @@
#![feature(unsafe_cell_access)]
mod images;
mod triangle;
use core::fmt::Write;
use std::{cell::UnsafeCell, mem::MaybeUninit};
use buoyant::{primitives::Point, render::StrokedShape, view::prelude::*};
use embedded_graphics::{
pixelcolor::{Rgb565, Rgb888},
prelude::*,
};
use embedded_graphics_simulator::{OutputSettings, SimulatorDisplay, Window};
use embedded_sprites::{include_image, sprite::Sprite};
use heapless::format;
use profont::{PROFONT_12_POINT, PROFONT_14_POINT, PROFONT_18_POINT, PROFONT_24_POINT};
use tinybmp::Bmp;
use crate::triangle::Triangle;
const BACKGROUND_COLOR: Rgb565 = Rgb565::BLACK;
const DEFAULT_COLOR: Rgb565 = Rgb565::WHITE;
fn main()
{
images::prepare_images();
let mut window = Window::new("Hello World", &OutputSettings::default());
let mut display: SimulatorDisplay<Rgb565> = SimulatorDisplay::new(Size::new(320, 240));
display.clear(BACKGROUND_COLOR);
hello_view()
.as_drawable(display.size(), DEFAULT_COLOR)
.draw(&mut display)
.unwrap();
window.show_static(&display);
}
fn hello_view() -> impl View<Rgb565>
{
VStack::new((
HStack::new((
main_menu_indicator(MenuIndicatorType::Temperature(31.5)),
main_menu_indicator(MenuIndicatorType::Humidity(36.2)),
))
.with_spacing(5),
HStack::new((
main_menu_indicator(MenuIndicatorType::Co2(1329)),
main_menu_indicator(MenuIndicatorType::Voc(29)),
))
.with_spacing(5),
))
.with_spacing(5)
}
const FRAME_STROKE: u32 = 2;
const FRAME_COLOR: Rgb565 = Rgb565::new(5, 9, 5);
pub enum MenuIndicatorType
{
Temperature(f32),
Humidity(f32),
Co2(u32),
Voc(u32),
}
impl MenuIndicatorType
{
pub fn get_corresponding_icon(
&self,
) -> &'static std::thread::LocalKey<UnsafeCell<MaybeUninit<Bmp<'static, Rgb565>>>>
{
match self
{
MenuIndicatorType::Temperature(_) => &images::TEMPERATURE_ICON,
MenuIndicatorType::Humidity(_) => &images::HUMIDITY_ICON,
MenuIndicatorType::Co2(_) => &images::CO2_ICON,
MenuIndicatorType::Voc(_) => &images::VOC_ICON,
}
}
pub fn get_corresponding_unit_string(&self) -> &'static str
{
match self
{
MenuIndicatorType::Temperature(_) => "C",
MenuIndicatorType::Humidity(_) => "%",
MenuIndicatorType::Co2(_) => "ppm",
MenuIndicatorType::Voc(_) => "ppb",
}
}
pub fn get_value_str(&self) -> heapless::String<16>
{
match self
{
MenuIndicatorType::Temperature(temp) => format!(16; "{:.1}", temp).unwrap(),
MenuIndicatorType::Humidity(hum) => format!(16; "{:.1}", hum).unwrap(),
MenuIndicatorType::Co2(co2) => format!(16; "{}", co2).unwrap(),
MenuIndicatorType::Voc(voc) => format!(16; "{}", voc).unwrap(),
}
}
}
fn main_menu_indicator(indicator_type: MenuIndicatorType) -> impl View<Rgb565>
{
Rectangle
.corner_radius(10)
.stroked(FRAME_STROKE)
.foreground_color(FRAME_COLOR)
.background(Alignment::Center, || {
ZStack::new((
Rectangle
.corner_radius(15)
.foreground_color(Rgb565::new(1, 2, 1)),
VStack::new((
HStack::new((
Spacer::default(),
buoyant::view::Image::new(get_image!(
indicator_type.get_corresponding_icon()
)),
Spacer::default(),
)),
HStack::new((
Spacer::default(),
tendency_indicator(Tendency::Rising),
Text::new(indicator_type.get_value_str(), &PROFONT_24_POINT),
Text::new(
indicator_type.get_corresponding_unit_string(),
&PROFONT_18_POINT,
)
.foreground_color(Rgb565::CSS_DARK_GRAY)
.flex_frame()
.with_infinite_max_height()
.with_vertical_alignment(VerticalAlignment::Bottom)
.with_max_height(25),
Spacer::default(),
)),
))
.with_alignment(HorizontalAlignment::Center)
.flex_frame(),
))
})
}
pub enum Tendency
{
Rising,
Steady,
Falling,
}
fn tendency_indicator(tendency: Tendency) -> impl View<Rgb565>
{
VStack::new((
StrokedShape::new(
Triangle::new(Point::new(0, 5), Point::new(10, 0), Point::new(0, 10)),
10,
),
StrokedShape::new(
Triangle::new(Point::new(0, 5), Point::new(10, 0), Point::new(0, 10)),
10,
),
))
}

0
src/triangle.rs Normal file
View File