Browse Source

Initial commit

master
Ilya 4 years ago
commit
1b5c4c1795
  1. 2
      .gitignore
  2. 13
      Cargo.toml
  3. 106
      examples/test.rs
  4. 1
      rustfmt.toml
  5. 27
      src/bullet_counter.rs
  6. 386
      src/calendar.rs
  7. 77
      src/gauge.rs
  8. 9
      src/lib.rs
  9. 78
      src/list_item.rs
  10. 8
      src/style.rs

2
.gitignore vendored

@ -0,0 +1,2 @@
/target
Cargo.lock

13
Cargo.toml

@ -0,0 +1,13 @@
[package]
name = "embedded-components"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
embedded-graphics = "*"
embedded-graphics-simulator = "*"
chrono = "*"
embedded-layout = "*"
num-traits = "*"

106
examples/test.rs

@ -0,0 +1,106 @@
use embedded_components::{Calendar, ComponentStyle, Gauge, ScrollingCalendar};
use embedded_graphics::{
pixelcolor::{raw::RawU2, BinaryColor, Rgb888},
prelude::*,
};
use embedded_graphics_simulator::{
BinaryColorTheme, OutputSettingsBuilder, SimulatorDisplay, Window,
};
#[derive(Copy, Clone, PartialEq, Eq)]
enum TriColor {
White,
Black,
Red,
}
impl PixelColor for TriColor {
type Raw = RawU2;
}
impl From<BinaryColor> for TriColor {
fn from(value: BinaryColor) -> TriColor {
match value {
BinaryColor::On => TriColor::Black,
BinaryColor::Off => TriColor::White,
}
}
}
impl Into<Rgb888> for TriColor {
fn into(self) -> Rgb888 {
match self {
TriColor::Black => Rgb888::new(0, 0, 0),
TriColor::White => Rgb888::new(255, 255, 255),
TriColor::Red => Rgb888::new(180, 0, 0),
}
}
}
impl From<Rgb888> for TriColor {
fn from(value: Rgb888) -> TriColor {
match value {
_ => TriColor::White,
}
}
}
fn main() -> Result<(), core::convert::Infallible> {
//let mut display = SimulatorDisplay::<BinaryColor>::new(Size::new(212, 104));
//let mut display = SimulatorDisplay::<BinaryColor>::new(Size::new(800, 480));
let mut display = SimulatorDisplay::<TriColor>::new(Size::new(800, 480));
let fg_color = TriColor::Black;
let bg_color = TriColor::White;
let hi_color = Some(TriColor::Red);
let gauge = Gauge {
top_left: Point::new(300, 300),
bezel: 1,
border: 1,
size: Size::new(200, 20),
ratio: 0.5,
fg_color,
bg_color,
text: "test test test test test",
};
gauge.draw(&mut display)?;
let calendar = Calendar {
top_left: Point::new(10, 10),
size: Size::new(196, 196),
border: 1,
fg_color,
bg_color,
hi_color,
};
calendar.draw(&mut display)?;
let calendar_style = ComponentStyle {
border: 1,
bezel: 1,
fg_color,
bg_color,
hi_color: TriColor::Red,
};
let calendar_week = ScrollingCalendar {
top_left: Point::new(400, 10),
size: Size::new(396, 396),
style: calendar_style,
day: chrono::Local::now()
.naive_local()
.date()
.checked_add_signed(chrono::Duration::days(28 * 4 * 0 - 14))
.unwrap(),
weeks: 5,
};
calendar_week.draw(&mut display)?;
let output_settings = OutputSettingsBuilder::new()
.theme(BinaryColorTheme::Default)
.build();
Window::new("Hello World", &output_settings).show_static(&display);
Ok(())
}

1
rustfmt.toml

@ -0,0 +1 @@
tab_spaces = 2

27
src/bullet_counter.rs

@ -0,0 +1,27 @@
use embedded_graphics::{
pixelcolor::{BinaryColor, PixelColor},
prelude::*,
primitives::{Circle, Rectangle},
};
use embedded_layout::prelude::*;
pub struct BulletCounter<C> {
pub top_left: Point,
pub size: Size, // Available space
pub color: C,
}
impl<C> Drawable for BulletCounter<C>
where
C: PixelColor + From<BinaryColor>,
{
type Color = C;
type Output = ();
fn draw<D>(&self, target: &mut D) -> Result<Self::Output, D::Error>
where
D: DrawTarget<Color = C>,
{
Ok(())
}
}

386
src/calendar.rs

@ -0,0 +1,386 @@
use crate::ComponentStyle;
use chrono::{Datelike, Month};
use embedded_graphics::{
mono_font::{
ascii::{FONT_10X20, FONT_4X6, FONT_6X9, FONT_8X13},
MonoFont, MonoTextStyle,
},
pixelcolor::{BinaryColor, PixelColor},
prelude::*,
primitives::{Line, PrimitiveStyle, Rectangle},
text::Text,
};
use embedded_layout::prelude::*;
use num_traits::cast::FromPrimitive;
fn font(height: u32) -> MonoFont<'static> {
if height < 8 {
return FONT_4X6;
} else if height < 10 {
return FONT_6X9;
} else if height < 14 {
return FONT_8X13;
} else {
return FONT_10X20;
}
}
pub struct CalendarDay<C> {
pub top_left: Point,
pub size: Size,
pub fg_color: C,
pub bg_color: C,
pub border: u32,
pub day_of_month: u32,
pub has_event: bool,
}
impl<C> Drawable for CalendarDay<C>
where
C: PixelColor + From<BinaryColor>,
{
type Color = C;
type Output = ();
fn draw<D>(&self, target: &mut D) -> Result<Self::Output, D::Error>
where
D: DrawTarget<Color = C>,
{
// Adding based on border so that the borders overlap
let date_rect = Rectangle::new(
self.top_left,
self.size + Size::new(self.border / 2 + 1, self.border / 2 + 1),
);
date_rect
.into_styled(PrimitiveStyle::with_fill(self.bg_color))
.draw(target)?;
date_rect
.into_styled(PrimitiveStyle::with_stroke(self.fg_color, self.border))
.draw(target)?;
let text = format!("{}", self.day_of_month);
let date_font = font(self.size.height / 2);
let text_style = MonoTextStyle::new(&date_font, self.fg_color);
Text::new(&text, self.top_left, text_style)
.align_to(&date_rect, horizontal::Center, vertical::Center)
.draw(target)?;
if self.has_event {
let event_padding: i32 = 2 + self.size.height as i32 / 10;
Line::new(
self.top_left + Point::new(event_padding, self.size.height as i32 - event_padding),
self.top_left
+ Point::new(
self.size.width as i32 - event_padding,
self.size.height as i32 - event_padding,
),
)
.into_styled(PrimitiveStyle::with_stroke(self.fg_color, 2))
.draw(target)?;
}
Ok(())
}
}
pub struct CalendarMonth<C> {
pub top_left: Point,
pub size: Size,
pub border: u32,
pub fg_color: C,
pub bg_color: C,
pub month: chrono::Month,
}
impl<C> Drawable for CalendarMonth<C>
where
C: PixelColor + From<BinaryColor>,
{
type Color = C;
type Output = ();
fn draw<D>(&self, target: &mut D) -> Result<Self::Output, D::Error>
where
D: DrawTarget<Color = C>,
{
let rect = Rectangle::new(
self.top_left,
self.size + Size::new(self.border / 2 + 1, self.border / 2 + 1),
);
rect
.into_styled(PrimitiveStyle::with_stroke(self.fg_color, self.border))
.draw(target)?;
let month_font = font((self.size.height as f32 * 0.8 - 2.) as u32);
let text_style = MonoTextStyle::new(&month_font, self.fg_color);
Text::new(self.month.name(), self.top_left, text_style)
.align_to(&rect, horizontal::Center, vertical::Center)
.draw(target)?;
Ok(())
}
}
pub struct Calendar<C> {
pub top_left: Point,
pub size: Size,
pub border: u32,
pub fg_color: C,
pub bg_color: C,
// If hi_color is None, the fg and bg colors are inverted instead.
pub hi_color: Option<C>,
}
impl<C> Drawable for Calendar<C>
where
C: PixelColor + From<BinaryColor>,
{
type Color = C;
type Output = ();
fn draw<D>(&self, target: &mut D) -> Result<Self::Output, D::Error>
where
D: DrawTarget<Color = C>,
{
let border = self.border;
let date_now = chrono::offset::Local::now().date();
let first_day = date_now.with_day(1).unwrap();
let last_day = date_now
.with_day(1)
.unwrap()
.with_month(date_now.month() % 11 + 1)
.unwrap()
.pred();
let empty_squares = first_day.weekday().num_days_from_monday();
let squares_needed = empty_squares + last_day.day();
let month_needs_separate_row = empty_squares < 4;
let mut rows_needed = squares_needed / 7 + 1;
if month_needs_separate_row {
rows_needed += 1;
}
let width = self.size.width / 7;
let height = self.size.height / rows_needed;
let month_width = if month_needs_separate_row {
width * 7
} else {
width * empty_squares
};
let calendar_month = CalendarMonth {
border,
top_left: self.top_left,
size: Size::new(month_width, height),
fg_color: self.fg_color,
bg_color: self.bg_color,
month: Month::from_u32(date_now.month()).unwrap(),
};
calendar_month.draw(target)?;
for i in empty_squares..squares_needed {
let x = i % 7;
let mut y = i / 7;
if month_needs_separate_row {
y = y + 1;
}
let day_of_month = i - empty_squares + 1;
let is_weekend = date_now
.with_day(day_of_month)
.unwrap()
.weekday()
.num_days_from_monday()
>= 5;
let is_today = day_of_month == date_now.day();
let (mut fg_color, mut bg_color, mut border) = match is_weekend ^ is_today {
false => (self.fg_color, self.bg_color, border),
true => (self.bg_color, self.fg_color, 0),
};
if is_today && self.hi_color.is_some() {
(fg_color, bg_color, border) = (self.bg_color, self.hi_color.unwrap(), 0);
}
let calendar_day = CalendarDay {
border,
top_left: self.top_left + Point::new((width * x) as i32, (height * y) as i32),
size: Size::new(width, height),
fg_color,
bg_color,
day_of_month: i - empty_squares + 1,
has_event: i - empty_squares + 1 == date_now.day(),
};
calendar_day.draw(target)?;
}
Ok(())
}
}
pub struct ScrollingWeek {
day: chrono::NaiveDate,
}
impl ScrollingWeek {
pub fn succ(&self) -> ScrollingWeek {
let start_of_next_month = self
.day
.with_month(self.day.month() % 11 + 1)
.unwrap()
.with_day(1)
.unwrap();
let days_until_next_month =
start_of_next_month.num_days_from_ce() - self.day.num_days_from_ce();
let day_of_week = self.day.weekday().number_from_monday() as i32;
let days_to_skip = if days_until_next_month < 7 {
days_until_next_month
} else {
8 - day_of_week
};
ScrollingWeek {
day: self
.day
.checked_add_signed(chrono::Duration::days(days_to_skip as i64))
.unwrap(),
}
}
pub fn month(&self) -> chrono::Month {
chrono::Month::from_u32(self.day.month()).unwrap()
}
pub fn week(&self) -> u32 {
self.day.iso_week().week()
}
pub fn year(&self) -> i32 {
self.day.year()
}
}
pub struct ScrollingCalendar<C> {
pub top_left: Point,
pub size: Size,
pub style: ComponentStyle<C>,
pub day: chrono::NaiveDate,
pub weeks: u32,
}
impl<C> Drawable for ScrollingCalendar<C>
where
C: PixelColor + From<BinaryColor>,
{
type Color = C;
type Output = ();
fn draw<D>(&self, target: &mut D) -> Result<Self::Output, D::Error>
where
D: DrawTarget<Color = C>,
{
let height = self.size.height / (1 + self.weeks * 2);
let mut week = ScrollingWeek {
day: chrono::NaiveDate::from_isoywd(
self.day.year(),
self.day.iso_week().week(),
chrono::Weekday::Mon,
)
.checked_add_signed(chrono::Duration::days(-7 * self.weeks as i64))
.unwrap(),
};
for i in 0..self.weeks * 2 {
week = week.succ();
let calendar_week = CalendarWeek {
top_left: Point::new(self.top_left.x, self.top_left.y + (height * i) as i32),
size: Size::new(self.size.width, height),
style: self.style.clone(),
year: week.year(),
month: week.month(),
week: week.week(),
};
calendar_week.draw(target)?;
}
Ok(())
}
}
pub struct CalendarWeek<C> {
pub top_left: Point,
pub size: Size,
pub style: ComponentStyle<C>,
pub year: i32,
pub month: chrono::Month,
pub week: u32,
}
impl<C> Drawable for CalendarWeek<C>
where
C: PixelColor + From<BinaryColor>,
{
type Color = C;
type Output = ();
fn draw<D>(&self, target: &mut D) -> Result<Self::Output, D::Error>
where
D: DrawTarget<Color = C>,
{
let today = chrono::Utc::now().date();
let monday = chrono::NaiveDate::from_isoywd(self.year, self.week, chrono::Weekday::Mon);
let sunday = chrono::NaiveDate::from_isoywd(self.year, self.week, chrono::Weekday::Sun);
let week_starts_in_prev_month = monday.month() != self.month.number_from_month();
let week_ends_in_next_month = sunday.month() != self.month.number_from_month();
let first_day = match week_starts_in_prev_month {
true => sunday.with_day(1).unwrap(),
false => monday,
};
let last_day = match week_ends_in_next_month {
true => sunday.with_day(1).unwrap().pred(),
false => sunday,
};
let width = self.size.width / 7;
let mut weekday = first_day.weekday();
for i in first_day.weekday().number_from_monday()..(last_day.weekday().number_from_monday() + 1)
{
let day = chrono::NaiveDate::from_isoywd(self.year, self.week, weekday);
let (fg_color, bg_color) = match (
weekday.num_days_from_monday() < 5,
day.num_days_from_ce() == today.num_days_from_ce(),
) {
(_, true) => (self.style.bg_color, self.style.hi_color),
(true, _) => (self.style.fg_color, self.style.bg_color),
(false, _) => (self.style.bg_color, self.style.fg_color),
};
let style = ComponentStyle {
fg_color,
bg_color,
..self.style
};
let calendar_day = CalendarDay {
top_left: self.top_left + Point::new((width * weekday.num_days_from_monday()) as i32, 0),
size: Size::new(width, self.size.height),
border: self.style.border,
fg_color,
bg_color,
day_of_month: day.day(),
has_event: day.num_days_from_ce() == today.num_days_from_ce(),
};
calendar_day.draw(target)?;
weekday = weekday.succ();
}
Ok(())
}
}

77
src/gauge.rs

@ -0,0 +1,77 @@
use embedded_graphics::{
mono_font::{ascii::FONT_6X9, MonoTextStyle},
pixelcolor::{BinaryColor, PixelColor},
prelude::*,
primitives::{PrimitiveStyle, Rectangle},
text::{Baseline, Text},
};
pub struct Gauge<'a, C> {
pub top_left: Point,
pub bezel: u32,
pub border: u32,
pub size: Size,
pub ratio: f32,
pub fg_color: C,
pub bg_color: C,
pub text: &'a str,
}
impl<C> Drawable for Gauge<'_, C>
where
C: PixelColor + From<BinaryColor>,
{
type Color = C;
type Output = ();
fn draw<D>(&self, target: &mut D) -> Result<Self::Output, D::Error>
where
D: DrawTarget<Color = C>,
{
let margin = self.border + self.bezel;
Rectangle::new(self.top_left, self.size)
.into_styled(PrimitiveStyle::with_stroke(self.fg_color, self.border))
.draw(target)?;
let inner_size = self.size - Size::new(margin * 2, margin * 2);
let inner_filled = Size::new(
(inner_size.width as f32 * self.ratio).ceil() as u32,
inner_size.height,
);
let inner_unfilled = Size::new(inner_size.width - inner_filled.width, inner_size.height);
let filled_rect = Rectangle::new(
self.top_left + Point::new(margin as i32, margin as i32),
inner_filled,
);
let unfilled_rect = Rectangle::new(
self.top_left + Point::new(margin as i32 + inner_filled.width as i32, margin as i32),
inner_unfilled,
);
// Added stroke as well because sometimes it showed a 1px bezel otherwise
// This is probably because the outer border stroke extends border/2 out/inwards
filled_rect
.into_styled(PrimitiveStyle::with_stroke(self.fg_color, self.border))
.draw(target)?;
filled_rect
.into_styled(PrimitiveStyle::with_fill(self.fg_color))
.draw(target)?;
let filled_style = MonoTextStyle::new(&FONT_6X9, self.bg_color);
let unfilled_style = MonoTextStyle::new(&FONT_6X9, self.fg_color);
let mut text =
Text::with_baseline(self.text, self.top_left, filled_style, Baseline::Middle);
let text_box = text.bounding_box();
text.position = self.top_left + Point::new(margin as i32, margin as i32) + inner_size / 2
- Point::new((text_box.size.width as f32 / 2.) as i32, 0);
text.draw(&mut target.clipped(&filled_rect))?;
let text = Text::with_baseline(self.text, text.position, unfilled_style, Baseline::Middle);
text.draw(&mut target.clipped(&unfilled_rect))?;
Ok(())
}
}

9
src/lib.rs

@ -0,0 +1,9 @@
mod bullet_counter;
mod calendar;
mod gauge;
mod style;
pub use bullet_counter::BulletCounter;
pub use calendar::{Calendar, ScrollingCalendar};
pub use gauge::Gauge;
pub use style::ComponentStyle;

78
src/list_item.rs

@ -0,0 +1,78 @@
use embedded_graphics::{
pixelcolor::{BinaryColor, PixelColor},
prelude::*,
primitives::{Line, PrimitiveStyle, Rectangle},
text::Text,
};
use embedded_layout::prelude::*;
pub struct ListItemData {
pub text: &'a str,
pub icon: &'a str,
}
pub struct List<'a, C> {
pub top_left: Point,
pub border: u32,
pub size: Size,
pub fg_color: C,
pub bg_color: C,
pub list_items: Vec<ListItemData>,
}
impl<C> Drawable for ListItem<'_, C>
where
C: PixelColor + From<BinaryColor>,
{
type Color = C;
type Output = ();
fn draw<D>(&self, target: &mut D) -> Result<Self::Output, D::Error>
where
D: DrawTarget<Color = C>,
{
let rect = Rectangle::new(self.top_left, self.size);
rect
.into_styled(PrimitiveStyle::with_stroke(self.fg_color, self.border))
.draw(target)?;
rect
.into_styled(PrimitiveStyle::with_fill(self.fg_color))
.draw(target)?;
// TODO: draw list items
Ok(())
}
}
pub struct ListItem<'a, C> {
pub top_left: Point,
pub border: u32,
pub size: Size,
pub fg_color: C,
pub bg_color: C,
pub text: &'a str,
}
impl<C> Drawable for ListItem<'_, C>
where
C: PixelColor + From<BinaryColor>,
{
type Color = C;
type Output = ();
fn draw<D>(&self, target: &mut D) -> Result<Self::Output, D::Error>
where
D: DrawTarget<Color = C>,
{
let rect = Rectangle::new(self.top_left, self.size);
rect
.into_styled(PrimitiveStyle::with_stroke(self.fg_color, self.border))
.draw(target)?;
rect
.into_styled(PrimitiveStyle::with_fill(self.fg_color))
.draw(target)?;
Ok(())
}
}

8
src/style.rs

@ -0,0 +1,8 @@
#[derive(Clone)]
pub struct ComponentStyle<C> {
pub fg_color: C,
pub bg_color: C,
pub hi_color: C,
pub border: u32,
pub bezel: u32,
}
Loading…
Cancel
Save