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
+398
@@ -0,0 +1,398 @@
|
||||
// Copyright 2015-2018 Benjamin Fry <benjaminfry@me.com>
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
|
||||
// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
|
||||
// http://opensource.org/licenses/MIT>, at your option. This file may not be
|
||||
// copied, modified, or distributed except according to those terms.
|
||||
|
||||
//! # enum-as-inner
|
||||
//!
|
||||
//! A deriving proc-macro for generating functions to automatically give access to the inner members of enum.
|
||||
//!
|
||||
//! ## Basic unnamed field case
|
||||
//!
|
||||
//! The basic case is meant for single item enums, like:
|
||||
//!
|
||||
//! ```rust
|
||||
//! use enum_as_inner::EnumAsInner;
|
||||
//!
|
||||
//! #[derive(Debug, EnumAsInner)]
|
||||
//! enum OneEnum {
|
||||
//! One(u32),
|
||||
//! }
|
||||
//!
|
||||
//! let one = OneEnum::One(1);
|
||||
//!
|
||||
//! assert_eq!(*one.as_one().unwrap(), 1);
|
||||
//! assert_eq!(one.into_one().unwrap(), 1);
|
||||
//! ```
|
||||
//!
|
||||
//! where the result is either a reference for inner items or a tuple containing the inner items.
|
||||
//!
|
||||
//! ## Unit case
|
||||
//!
|
||||
//! This will return true if enum's variant matches the expected type
|
||||
//!
|
||||
//! ```rust
|
||||
//! use enum_as_inner::EnumAsInner;
|
||||
//!
|
||||
//! #[derive(EnumAsInner)]
|
||||
//! enum UnitVariants {
|
||||
//! Zero,
|
||||
//! One,
|
||||
//! Two,
|
||||
//! }
|
||||
//!
|
||||
//! let unit = UnitVariants::Two;
|
||||
//!
|
||||
//! assert!(unit.is_two());
|
||||
//! ```
|
||||
//!
|
||||
//! ## Mutliple, unnamed field case
|
||||
//!
|
||||
//! This will return a tuple of the inner types:
|
||||
//!
|
||||
//! ```rust
|
||||
//! use enum_as_inner::EnumAsInner;
|
||||
//!
|
||||
//! #[derive(Debug, EnumAsInner)]
|
||||
//! enum ManyVariants {
|
||||
//! One(u32),
|
||||
//! Two(u32, i32),
|
||||
//! Three(bool, u32, i64),
|
||||
//! }
|
||||
//!
|
||||
//! let many = ManyVariants::Three(true, 1, 2);
|
||||
//!
|
||||
//! assert!(many.is_three());
|
||||
//! assert_eq!(many.as_three().unwrap(), (&true, &1_u32, &2_i64));
|
||||
//! assert_eq!(many.into_three().unwrap(), (true, 1_u32, 2_i64));
|
||||
//! ```
|
||||
//!
|
||||
//! ## Multiple, named field case
|
||||
//!
|
||||
//! This will return a tuple of the inner types, like the unnamed option:
|
||||
//!
|
||||
//! ```rust
|
||||
//! use enum_as_inner::EnumAsInner;
|
||||
//!
|
||||
//! #[derive(Debug, EnumAsInner)]
|
||||
//! enum ManyVariants {
|
||||
//! One { one: u32 },
|
||||
//! Two { one: u32, two: i32 },
|
||||
//! Three { one: bool, two: u32, three: i64 },
|
||||
//! }
|
||||
//!
|
||||
//! let many = ManyVariants::Three { one: true, two: 1, three: 2 };
|
||||
//!
|
||||
//! assert!(many.is_three());
|
||||
//! assert_eq!(many.as_three().unwrap(), (&true, &1_u32, &2_i64));
|
||||
//! assert_eq!(many.into_three().unwrap(), (true, 1_u32, 2_i64));
|
||||
//! ```
|
||||
|
||||
#![warn(
|
||||
clippy::default_trait_access,
|
||||
clippy::dbg_macro,
|
||||
clippy::print_stdout,
|
||||
clippy::unimplemented,
|
||||
clippy::use_self,
|
||||
missing_copy_implementations,
|
||||
missing_docs,
|
||||
non_snake_case,
|
||||
non_upper_case_globals,
|
||||
rust_2018_idioms,
|
||||
unreachable_pub
|
||||
)]
|
||||
|
||||
use heck::ToSnakeCase;
|
||||
use proc_macro2::{Ident, Span, TokenStream};
|
||||
use quote::quote;
|
||||
use syn::{parse_macro_input, DeriveInput};
|
||||
|
||||
/// returns first the types to return, the match names, and then tokens to the field accesses
|
||||
fn unit_fields_return(variant_name: &syn::Ident, function_name: &Ident, doc: &str) -> TokenStream {
|
||||
quote!(
|
||||
#[doc = #doc]
|
||||
#[inline]
|
||||
pub fn #function_name(&self) -> bool {
|
||||
matches!(self, Self::#variant_name)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
/// returns first the types to return, the match names, and then tokens to the field accesses
|
||||
fn unnamed_fields_return(
|
||||
variant_name: &syn::Ident,
|
||||
(function_name_is, doc_is): (&Ident, &str),
|
||||
(function_name_mut_ref, doc_mut_ref): (&Ident, &str),
|
||||
(function_name_ref, doc_ref): (&Ident, &str),
|
||||
(function_name_val, doc_val): (&Ident, &str),
|
||||
fields: &syn::FieldsUnnamed,
|
||||
) -> TokenStream {
|
||||
let (returns_mut_ref, returns_ref, returns_val, matches) = match fields.unnamed.len() {
|
||||
1 => {
|
||||
let field = fields.unnamed.first().expect("no fields on type");
|
||||
|
||||
let returns = &field.ty;
|
||||
let returns_mut_ref = quote!(&mut #returns);
|
||||
let returns_ref = quote!(&#returns);
|
||||
let returns_val = quote!(#returns);
|
||||
let matches = quote!(inner);
|
||||
|
||||
(returns_mut_ref, returns_ref, returns_val, matches)
|
||||
}
|
||||
0 => (quote!(()), quote!(()), quote!(()), quote!()),
|
||||
_ => {
|
||||
let mut returns_mut_ref = TokenStream::new();
|
||||
let mut returns_ref = TokenStream::new();
|
||||
let mut returns_val = TokenStream::new();
|
||||
let mut matches = TokenStream::new();
|
||||
|
||||
for (i, field) in fields.unnamed.iter().enumerate() {
|
||||
let rt = &field.ty;
|
||||
let match_name = Ident::new(&format!("match_{}", i), Span::call_site());
|
||||
returns_mut_ref.extend(quote!(&mut #rt,));
|
||||
returns_ref.extend(quote!(&#rt,));
|
||||
returns_val.extend(quote!(#rt,));
|
||||
matches.extend(quote!(#match_name,));
|
||||
}
|
||||
|
||||
(
|
||||
quote!((#returns_mut_ref)),
|
||||
quote!((#returns_ref)),
|
||||
quote!((#returns_val)),
|
||||
quote!(#matches),
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
quote!(
|
||||
#[doc = #doc_is ]
|
||||
#[inline]
|
||||
#[allow(unused_variables)]
|
||||
pub fn #function_name_is(&self) -> bool {
|
||||
matches!(self, Self::#variant_name(#matches))
|
||||
}
|
||||
|
||||
#[doc = #doc_mut_ref ]
|
||||
#[inline]
|
||||
pub fn #function_name_mut_ref(&mut self) -> ::core::option::Option<#returns_mut_ref> {
|
||||
match self {
|
||||
Self::#variant_name(#matches) => {
|
||||
::core::option::Option::Some((#matches))
|
||||
}
|
||||
_ => ::core::option::Option::None
|
||||
}
|
||||
}
|
||||
|
||||
#[doc = #doc_ref ]
|
||||
#[inline]
|
||||
pub fn #function_name_ref(&self) -> ::core::option::Option<#returns_ref> {
|
||||
match self {
|
||||
Self::#variant_name(#matches) => {
|
||||
::core::option::Option::Some((#matches))
|
||||
}
|
||||
_ => ::core::option::Option::None
|
||||
}
|
||||
}
|
||||
|
||||
#[doc = #doc_val ]
|
||||
#[inline]
|
||||
pub fn #function_name_val(self) -> ::core::result::Result<#returns_val, Self> {
|
||||
match self {
|
||||
Self::#variant_name(#matches) => {
|
||||
::core::result::Result::Ok((#matches))
|
||||
},
|
||||
_ => ::core::result::Result::Err(self)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
/// returns first the types to return, the match names, and then tokens to the field accesses
|
||||
fn named_fields_return(
|
||||
variant_name: &syn::Ident,
|
||||
(function_name_is, doc_is): (&Ident, &str),
|
||||
(function_name_mut_ref, doc_mut_ref): (&Ident, &str),
|
||||
(function_name_ref, doc_ref): (&Ident, &str),
|
||||
(function_name_val, doc_val): (&Ident, &str),
|
||||
fields: &syn::FieldsNamed,
|
||||
) -> TokenStream {
|
||||
let (returns_mut_ref, returns_ref, returns_val, matches) = match fields.named.len() {
|
||||
1 => {
|
||||
let field = fields.named.first().expect("no fields on type");
|
||||
let match_name = field.ident.as_ref().expect("expected a named field");
|
||||
|
||||
let returns = &field.ty;
|
||||
let returns_mut_ref = quote!(&mut #returns);
|
||||
let returns_ref = quote!(&#returns);
|
||||
let returns_val = quote!(#returns);
|
||||
let matches = quote!(#match_name);
|
||||
|
||||
(returns_mut_ref, returns_ref, returns_val, matches)
|
||||
}
|
||||
0 => (quote!(()), quote!(()), quote!(()), quote!(())),
|
||||
_ => {
|
||||
let mut returns_mut_ref = TokenStream::new();
|
||||
let mut returns_ref = TokenStream::new();
|
||||
let mut returns_val = TokenStream::new();
|
||||
let mut matches = TokenStream::new();
|
||||
|
||||
for field in fields.named.iter() {
|
||||
let rt = &field.ty;
|
||||
let match_name = field.ident.as_ref().expect("expected a named field");
|
||||
|
||||
returns_mut_ref.extend(quote!(&mut #rt,));
|
||||
returns_ref.extend(quote!(&#rt,));
|
||||
returns_val.extend(quote!(#rt,));
|
||||
matches.extend(quote!(#match_name,));
|
||||
}
|
||||
|
||||
(
|
||||
quote!((#returns_mut_ref)),
|
||||
quote!((#returns_ref)),
|
||||
quote!((#returns_val)),
|
||||
quote!(#matches),
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
quote!(
|
||||
#[doc = #doc_is ]
|
||||
#[inline]
|
||||
#[allow(unused_variables)]
|
||||
pub fn #function_name_is(&self) -> bool {
|
||||
matches!(self, Self::#variant_name{ #matches })
|
||||
}
|
||||
|
||||
#[doc = #doc_mut_ref ]
|
||||
#[inline]
|
||||
pub fn #function_name_mut_ref(&mut self) -> ::core::option::Option<#returns_mut_ref> {
|
||||
match self {
|
||||
Self::#variant_name{ #matches } => {
|
||||
::core::option::Option::Some((#matches))
|
||||
}
|
||||
_ => ::core::option::Option::None
|
||||
}
|
||||
}
|
||||
|
||||
#[doc = #doc_ref ]
|
||||
#[inline]
|
||||
pub fn #function_name_ref(&self) -> ::core::option::Option<#returns_ref> {
|
||||
match self {
|
||||
Self::#variant_name{ #matches } => {
|
||||
::core::option::Option::Some((#matches))
|
||||
}
|
||||
_ => ::core::option::Option::None
|
||||
}
|
||||
}
|
||||
|
||||
#[doc = #doc_val ]
|
||||
#[inline]
|
||||
pub fn #function_name_val(self) -> ::core::result::Result<#returns_val, Self> {
|
||||
match self {
|
||||
Self::#variant_name{ #matches } => {
|
||||
::core::result::Result::Ok((#matches))
|
||||
}
|
||||
_ => ::core::result::Result::Err(self)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
fn impl_all_as_fns(ast: &DeriveInput) -> TokenStream {
|
||||
let name = &ast.ident;
|
||||
let generics = &ast.generics;
|
||||
|
||||
let enum_data = if let syn::Data::Enum(data) = &ast.data {
|
||||
data
|
||||
} else {
|
||||
panic!("{} is not an enum", name);
|
||||
};
|
||||
|
||||
let mut stream = TokenStream::new();
|
||||
|
||||
for variant_data in &enum_data.variants {
|
||||
let variant_name = &variant_data.ident;
|
||||
let function_name_ref = Ident::new(
|
||||
&format!("as_{}", variant_name).to_snake_case(),
|
||||
Span::call_site(),
|
||||
);
|
||||
let doc_ref = format!(
|
||||
"Optionally returns references to the inner fields if this is a `{}::{}`, otherwise `None`",
|
||||
name,
|
||||
variant_name,
|
||||
);
|
||||
let function_name_mut_ref = Ident::new(
|
||||
&format!("as_{}_mut", variant_name).to_snake_case(),
|
||||
Span::call_site(),
|
||||
);
|
||||
let doc_mut_ref = format!(
|
||||
"Optionally returns mutable references to the inner fields if this is a `{}::{}`, otherwise `None`",
|
||||
name,
|
||||
variant_name,
|
||||
);
|
||||
|
||||
let function_name_val = Ident::new(
|
||||
&format!("into_{}", variant_name).to_snake_case(),
|
||||
Span::call_site(),
|
||||
);
|
||||
let doc_val = format!(
|
||||
"Returns the inner fields if this is a `{}::{}`, otherwise returns back the enum in the `Err` case of the result",
|
||||
name,
|
||||
variant_name,
|
||||
);
|
||||
|
||||
let function_name_is = Ident::new(
|
||||
&format!("is_{}", variant_name).to_snake_case(),
|
||||
Span::call_site(),
|
||||
);
|
||||
let doc_is = format!(
|
||||
"Returns true if this is a `{}::{}`, otherwise false",
|
||||
name, variant_name,
|
||||
);
|
||||
|
||||
let tokens = match &variant_data.fields {
|
||||
syn::Fields::Unit => unit_fields_return(variant_name, &function_name_is, &doc_is),
|
||||
syn::Fields::Unnamed(unnamed) => unnamed_fields_return(
|
||||
variant_name,
|
||||
(&function_name_is, &doc_is),
|
||||
(&function_name_mut_ref, &doc_mut_ref),
|
||||
(&function_name_ref, &doc_ref),
|
||||
(&function_name_val, &doc_val),
|
||||
unnamed,
|
||||
),
|
||||
syn::Fields::Named(named) => named_fields_return(
|
||||
variant_name,
|
||||
(&function_name_is, &doc_is),
|
||||
(&function_name_mut_ref, &doc_mut_ref),
|
||||
(&function_name_ref, &doc_ref),
|
||||
(&function_name_val, &doc_val),
|
||||
named,
|
||||
),
|
||||
};
|
||||
|
||||
stream.extend(tokens);
|
||||
}
|
||||
|
||||
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
|
||||
|
||||
quote!(
|
||||
impl #impl_generics #name #ty_generics #where_clause {
|
||||
#stream
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
/// Derive functions on an Enum for easily accessing individual items in the Enum
|
||||
#[proc_macro_derive(EnumAsInner)]
|
||||
pub fn enum_as_inner(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
||||
// get a usable token stream
|
||||
let ast: DeriveInput = parse_macro_input!(input as DeriveInput);
|
||||
|
||||
// Build the impl
|
||||
let expanded: TokenStream = impl_all_as_fns(&ast);
|
||||
|
||||
// Return the generated impl
|
||||
proc_macro::TokenStream::from(expanded)
|
||||
}
|
||||
Reference in new issue
Block a user