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
+326
@@ -0,0 +1,326 @@
|
||||
// `ok!` and `some!` are less bloaty alternatives to the standard library's try operator (`?`).
|
||||
// Since we do not need type conversions in this crate we can fall back to much easier match
|
||||
// patterns that compile faster and produce less bloaty code.
|
||||
|
||||
macro_rules! ok {
|
||||
($expr:expr) => {
|
||||
match $expr {
|
||||
Ok(val) => val,
|
||||
Err(err) => return Err(err),
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! some {
|
||||
($expr:expr) => {
|
||||
match $expr {
|
||||
Some(val) => val,
|
||||
None => return None,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// Hidden utility module for the [`context!`](crate::context!) macro.
|
||||
#[doc(hidden)]
|
||||
pub mod __context {
|
||||
use crate::value::Value;
|
||||
use crate::Environment;
|
||||
use std::collections::BTreeMap;
|
||||
use std::rc::Rc;
|
||||
|
||||
#[inline(always)]
|
||||
pub fn make() -> BTreeMap<&'static str, Value> {
|
||||
BTreeMap::default()
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn add(ctx: &mut BTreeMap<&'static str, Value>, key: &'static str, value: Value) {
|
||||
ctx.insert(key, value);
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn build(ctx: BTreeMap<&'static str, Value>) -> Value {
|
||||
Value::from_object(ctx)
|
||||
}
|
||||
|
||||
pub fn thread_local_env() -> Rc<Environment<'static>> {
|
||||
thread_local! {
|
||||
static ENV: Rc<Environment<'static>> = Rc::new(Environment::new());
|
||||
}
|
||||
ENV.with(|x| x.clone())
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a template context from keys and values or merging in another value.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use minijinja::context;
|
||||
/// let ctx = context!{
|
||||
/// name => "Peter",
|
||||
/// location => "World",
|
||||
/// };
|
||||
/// ```
|
||||
///
|
||||
/// Alternatively if the variable name matches the key name it can
|
||||
/// be omitted:
|
||||
///
|
||||
/// ```rust
|
||||
/// # use minijinja::context;
|
||||
/// let name = "Peter";
|
||||
/// let ctx = context!{ name };
|
||||
/// ```
|
||||
///
|
||||
/// The return value is a [`Value`](crate::value::Value).
|
||||
///
|
||||
/// Note that [`context!`](crate::context!) can also be used recursively if you need to
|
||||
/// create nested objects:
|
||||
///
|
||||
/// ```rust
|
||||
/// # use minijinja::context;
|
||||
/// let ctx = context! {
|
||||
/// nav => vec![
|
||||
/// context!(path => "/", title => "Index"),
|
||||
/// context!(path => "/downloads", title => "Downloads"),
|
||||
/// context!(path => "/faq", title => "FAQ"),
|
||||
/// ]
|
||||
/// };
|
||||
/// ```
|
||||
///
|
||||
/// Additionally the macro supports a second syntax that can merge other
|
||||
/// contexts or values. In that case one or more values need to be
|
||||
/// passed with a leading `..` operator. This is useful to supply extra
|
||||
/// values into render in a common place. The order of precedence is
|
||||
/// left to right:
|
||||
///
|
||||
/// ```rust
|
||||
/// # use minijinja::context;
|
||||
/// let ctx = context! { a => "A" };
|
||||
/// let ctx = context! { ..ctx, ..context! {
|
||||
/// b => "B"
|
||||
/// }};
|
||||
///
|
||||
/// // or
|
||||
///
|
||||
/// let ctx = context! {
|
||||
/// a => "A",
|
||||
/// ..context! {
|
||||
/// b => "B"
|
||||
/// }
|
||||
/// };
|
||||
/// ```
|
||||
///
|
||||
/// The merge works with an value, not just values created by the `context!`
|
||||
/// macro and is performed lazy. This means it also works with dynamic
|
||||
/// [`Object`](crate::value::Object)s. The merge uses the underlying
|
||||
/// [`merge_maps`](crate::value::merge_maps) function.
|
||||
///
|
||||
/// # Note on Conversions
|
||||
///
|
||||
/// This macro uses [`Value::from_serialize`](crate::Value::from_serialize)
|
||||
/// for conversions.
|
||||
///
|
||||
/// This macro currently does not move passed values. Future versions of
|
||||
/// MiniJinja are going to change the move behavior and it's recommended to not
|
||||
/// depend on this implicit reference behavior. You should thus pass values
|
||||
/// with `&value` if you intend on still being able to reference them
|
||||
/// after the macro invocation.
|
||||
#[macro_export]
|
||||
macro_rules! context {
|
||||
() => {
|
||||
$crate::__context::build($crate::__context::make())
|
||||
};
|
||||
(
|
||||
$($key:ident $(=> $value:expr)?),*
|
||||
$(, .. $ctx:expr),* $(,)?
|
||||
) => {{
|
||||
let mut ctx = $crate::__context::make();
|
||||
$(
|
||||
$crate::__context_pair!(ctx, $key $(=> $value)?);
|
||||
)*
|
||||
let ctx = $crate::__context::build(ctx);
|
||||
let merge_ctx = [
|
||||
$(
|
||||
$crate::value::Value::from($ctx),
|
||||
)*
|
||||
];
|
||||
if merge_ctx.is_empty() {
|
||||
ctx
|
||||
} else {
|
||||
$crate::value::merge_maps(
|
||||
merge_ctx.into_iter().rev().chain(::std::iter::once(ctx)))
|
||||
}
|
||||
}};
|
||||
(
|
||||
$(.. $ctx:expr),* $(,)?
|
||||
) => {{
|
||||
$crate::value::merge_maps([
|
||||
$(
|
||||
$crate::value::Value::from($ctx),
|
||||
)*
|
||||
].into_iter().rev())
|
||||
}};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
#[doc(hidden)]
|
||||
macro_rules! __context_pair {
|
||||
($ctx:ident, $key:ident) => {{
|
||||
$crate::__context_pair!($ctx, $key => $key);
|
||||
}};
|
||||
($ctx:ident, $key:ident => $value:literal) => {
|
||||
$crate::__context::add(&mut $ctx, stringify!($key), $crate::value::Value::from($value));
|
||||
};
|
||||
($ctx:ident, $key:ident => context! { $($inner:tt)* }) => {
|
||||
$crate::__context::add(&mut $ctx, stringify!($key), $crate::context! { $($inner)* });
|
||||
};
|
||||
($ctx:ident, $key:ident => context!($($inner:tt)*)) => {
|
||||
$crate::__context::add(&mut $ctx, stringify!($key), $crate::context!($($inner)*));
|
||||
};
|
||||
($ctx:ident, $key:ident => $value:expr) => {
|
||||
$crate::__context::add(
|
||||
&mut $ctx,
|
||||
stringify!($key),
|
||||
$crate::value::Value::from_serialize(&$value),
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
/// An utility macro to create arguments for function calls.
|
||||
///
|
||||
/// This creates a slice of values on the stack which can be
|
||||
/// passed to [`call`](crate::value::Value::call),
|
||||
/// [`call_method`](crate::value::Value::call_method),
|
||||
/// [`apply_filter`](crate::State::apply_filter),
|
||||
/// [`perform_test`](crate::State::perform_test) or similar
|
||||
/// APIs that take slices of values.
|
||||
///
|
||||
/// It supports both positional and keyword arguments.
|
||||
/// To mark a parameter as keyword argument define it as
|
||||
/// `name => value`, otherwise just use `value`.
|
||||
///
|
||||
/// ```
|
||||
/// # use minijinja::{value::Value, args, Environment};
|
||||
/// # let env = Environment::default();
|
||||
/// # let state = &env.empty_state();
|
||||
/// # let value = Value::from(());
|
||||
/// value.call(state, args!(1, 2, foo => "bar"));
|
||||
/// ```
|
||||
///
|
||||
/// Note that this like [`context!`](crate::context) goes through
|
||||
/// [`Value::from_serialize`](crate::value::Value::from_serialize).
|
||||
#[macro_export]
|
||||
macro_rules! args {
|
||||
() => { &[][..] as &[$crate::value::Value] };
|
||||
($($arg:tt)*) => { $crate::__args_helper!(branch [[$($arg)*]], [$($arg)*]) };
|
||||
}
|
||||
|
||||
/// Utility macro for `args!`
|
||||
#[macro_export]
|
||||
#[doc(hidden)]
|
||||
macro_rules! __args_helper {
|
||||
// branch helper between `args` and `kwargs`.
|
||||
//
|
||||
// It analyzes the first bracket enclosed tt bundle to see if kwargs are
|
||||
// used. If yes, it uses `kwargs` to handle the second tt
|
||||
// bundle, otherwise it uses `args`.
|
||||
(branch [[]], $args:tt) => { $crate::__args_helper!(args $args) };
|
||||
(branch [[$n:ident => $e:expr]], $args:tt) => { $crate::__args_helper!(kwargs $args) };
|
||||
(branch [[$n:ident => $e:expr, $($r:tt)*]], $args:tt) => { $crate::__args_helper!(kwargs $args) };
|
||||
(branch [[$e:expr]], $args:tt) => { $crate::__args_helper!(args $args) };
|
||||
(branch [[$e:expr, $($rest:tt)*]], $args:tt) => { $crate::__args_helper!(branch [[$($rest)*]], $args) };
|
||||
|
||||
// creates args on the stack
|
||||
(args [$($arg:tt)*]) => {&{
|
||||
let mut args = Vec::<$crate::value::Value>::new();
|
||||
$crate::__args_helper!(peel args, args, false, [$($arg)*]);
|
||||
args
|
||||
}[..]};
|
||||
|
||||
// creates args with kwargs on the stack
|
||||
(kwargs [$($arg:tt)*]) => {&{
|
||||
let mut args = Vec::<$crate::value::Value>::new();
|
||||
let mut kwargs = Vec::<(&str, $crate::value::Value)>::new();
|
||||
$crate::__args_helper!(peel args, kwargs, false, [$($arg)*]);
|
||||
args.push($crate::value::Kwargs::from_iter(kwargs.into_iter()).into());
|
||||
args
|
||||
}[..]};
|
||||
|
||||
// Peels a single argument from the arguments and stuffs them into
|
||||
// `$args` or `$kwargs` depending on type.
|
||||
(peel $args:ident, $kwargs:ident, $has_kwargs:ident, []) => {};
|
||||
(peel $args:ident, $kwargs:ident, $has_kwargs:ident, [$name:ident => $expr:expr]) => {
|
||||
$kwargs.push((stringify!($name), $crate::value::Value::from_serialize(&$expr)));
|
||||
};
|
||||
(peel $args:ident, $kwargs:ident, $has_kwargs:ident, [$name:ident => $expr:expr, $($rest:tt)*]) => {
|
||||
$kwargs.push((stringify!($name), $crate::value::Value::from_serialize(&$expr)));
|
||||
$crate::__args_helper!(peel $args, $kwargs, true, [$($rest)*]);
|
||||
};
|
||||
(peel $args:ident, $kwargs:ident, false, [$expr:expr]) => {
|
||||
$args.push($crate::value::Value::from_serialize(&$expr));
|
||||
};
|
||||
(peel $args:ident, $kwargs:ident, false, [$expr:expr, $($rest:tt)*]) => {
|
||||
$args.push($crate::value::Value::from_serialize(&$expr));
|
||||
$crate::__args_helper!(peel $args, $kwargs, false, [$($rest)*]);
|
||||
};
|
||||
}
|
||||
|
||||
/// A macro similar to [`format!`] but that uses MiniJinja for rendering.
|
||||
///
|
||||
/// This can be used to quickly render a MiniJinja template into a string
|
||||
/// without having to create an environment first which can be useful in
|
||||
/// some situations. Note however that the template is re-parsed every
|
||||
/// time the [`render!`](crate::render) macro is called which is potentially
|
||||
/// slow.
|
||||
///
|
||||
/// There are two forms for this macro. The default form takes template
|
||||
/// source and context variables, the extended form also lets you provide
|
||||
/// a custom environment that should be used rather than a default one.
|
||||
/// The context variables are passed the same way as with the
|
||||
/// [`context!`](crate::context) macro.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// Passing context explicitly:
|
||||
///
|
||||
/// ```
|
||||
/// # use minijinja::render;
|
||||
/// println!("{}", render!("Hello {{ name }}!", name => "World"));
|
||||
/// ```
|
||||
///
|
||||
/// Passing variables with the default name:
|
||||
///
|
||||
/// ```
|
||||
/// # use minijinja::render;
|
||||
/// let name = "World";
|
||||
/// println!("{}", render!("Hello {{ name }}!", name));
|
||||
/// ```
|
||||
///
|
||||
/// Passing an explicit environment:
|
||||
///
|
||||
/// ```
|
||||
/// # use minijinja::{Environment, render};
|
||||
/// let env = Environment::new();
|
||||
/// println!("{}", render!(in env, "Hello {{ name }}!", name => "World"));
|
||||
/// ```
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// This macro panics if the format string is an invalid template or the
|
||||
/// template evaluation failed.
|
||||
#[macro_export]
|
||||
macro_rules! render {
|
||||
(
|
||||
in $env:expr,
|
||||
$tmpl:expr
|
||||
$(, $key:ident $(=> $value:expr)?)* $(,)?
|
||||
) => {
|
||||
($env).render_str($tmpl, $crate::context! { $($key $(=> $value)? ,)* })
|
||||
.expect("failed to render expression")
|
||||
};
|
||||
(
|
||||
$tmpl:expr
|
||||
$(, $key:ident $(=> $value:expr)?)* $(,)?
|
||||
) => {
|
||||
$crate::render!(in $crate::__context::thread_local_env(), $tmpl, $($key $(=> $value)? ,)*)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user