feat: add display UI with splash screen, dashboard, and RSSI sparkline
Implement full SSD1306 OLED display support: - Splash screen on boot (1.5s) with project name, version, board - Dashboard showing radio state, config, counters, RSSI/SNR - RSSI history sparkline (64-sample ring buffer, 2px-wide bars) - DisplayOn/DisplayOff host commands routed via dedicated channel - Pure render functions against generic DrawTarget (no hardware coupling) - Display on/off control bypasses radio task entirely Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
17624 files changed
+4564928
-38
No files matched your search
Vendored
+78
@@ -0,0 +1,78 @@
|
||||
use crate::error::{Error, Result};
|
||||
use crate::iter::Iter;
|
||||
use proc_macro::{Delimiter, Group, Ident, Literal, Span, TokenTree};
|
||||
|
||||
pub fn parse_punct(iter: Iter, ch: char) -> Result<()> {
|
||||
match iter.next() {
|
||||
Some(TokenTree::Punct(ref punct)) if punct.as_char() == ch => Ok(()),
|
||||
unexpected => {
|
||||
let span = unexpected
|
||||
.as_ref()
|
||||
.map_or_else(Span::call_site, TokenTree::span);
|
||||
Err(Error::new(span, format!("expected `{}`", ch)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_optional_punct(iter: Iter, ch: char) -> Option<()> {
|
||||
match iter.peek() {
|
||||
Some(TokenTree::Punct(punct)) if punct.as_char() == ch => iter.next().map(drop),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_optional_keyword(iter: Iter, keyword: &str) -> Option<Span> {
|
||||
match iter.peek() {
|
||||
Some(TokenTree::Ident(ident)) if ident.to_string() == keyword => {
|
||||
Some(iter.next().unwrap().span())
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_literal(iter: Iter) -> Result<Literal> {
|
||||
match iter.next() {
|
||||
Some(TokenTree::Literal(literal)) => Ok(literal),
|
||||
unexpected => {
|
||||
let span = unexpected
|
||||
.as_ref()
|
||||
.map_or_else(Span::call_site, TokenTree::span);
|
||||
Err(Error::new(span, "expected literal"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_paren(introducer: &Ident, iter: Iter) -> Result<Group> {
|
||||
match iter.peek() {
|
||||
Some(TokenTree::Group(group)) if group.delimiter() == Delimiter::Parenthesis => {
|
||||
match iter.next() {
|
||||
Some(TokenTree::Group(group)) => Ok(group),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
Some(unexpected) => Err(Error::new(unexpected.span(), "expected `(`")),
|
||||
None => Err(Error::new(
|
||||
introducer.span(),
|
||||
format!("expected `(` after `{}`", introducer),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_optional_paren(iter: Iter) -> Option<Group> {
|
||||
match iter.peek() {
|
||||
Some(TokenTree::Group(group)) if group.delimiter() == Delimiter::Parenthesis => {
|
||||
match iter.next() {
|
||||
Some(TokenTree::Group(group)) => Some(group),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_end(iter: Iter) -> Result<()> {
|
||||
match iter.next() {
|
||||
None => Ok(()),
|
||||
Some(unexpected) => Err(Error::new(unexpected.span(), "unexpected token")),
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user