This repository has been archived on 2026-08-31. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
donglora/vendor/fixed/src/bytes.rs
T
swaitsandClaude Opus 4.6 3102fc747a 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>
2026-03-29 20:42:54 -06:00

381 lines
11 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Copyright © 2018–2025 Trevor Spiteri
// This library is free software: you can redistribute it and/or
// modify it under the terms of either
//
// * the Apache License, Version 2.0 or
// * the MIT License
//
// at your option.
//
// You should have recieved copies of the Apache License and the MIT
// License along with the library. If not, see
// <https://www.apache.org/licenses/LICENSE-2.0> and
// <https://opensource.org/licenses/MIT>.
// Kept trimmed: no underscores at beginning or end of slice
#[derive(Clone, Copy, Debug)]
pub struct DigitsUnds<'a> {
bytes: &'a [u8],
digits: usize,
}
impl<'a> DigitsUnds<'a> {
pub const EMPTY: DigitsUnds<'a> = DigitsUnds::new(&[]);
pub const fn new(bytes: &'a [u8]) -> DigitsUnds<'a> {
let mut digits = 0;
let mut leading_unds = 0;
let mut trailing_unds = 0;
let mut rem_bytes = bytes;
while let Some((&byte, rem)) = rem_bytes.split_first() {
rem_bytes = rem;
if byte == b'_' {
trailing_unds += 1;
} else {
if digits == 0 {
leading_unds = trailing_unds;
}
digits += 1;
trailing_unds = 0;
}
}
let without_trailing_unds = bytes.split_at(bytes.len() - trailing_unds).0;
let without_leading_unds = without_trailing_unds.split_at(leading_unds).1;
DigitsUnds {
bytes: without_leading_unds,
digits,
}
}
#[inline]
pub const fn len(self) -> usize {
self.digits
}
#[inline]
pub const fn is_empty(self) -> bool {
self.digits == 0
}
pub const fn split_at(self, mid: usize) -> (DigitsUnds<'a>, DigitsUnds<'a>) {
let mut remaining_digits = mid;
let mut unds = 0;
let mut rem_bytes = self.bytes;
while let Some((&byte, rem)) = rem_bytes.split_first() {
rem_bytes = rem;
if byte == b'_' {
unds += 1;
} else {
remaining_digits -= 1;
if remaining_digits == 0 {
break;
}
}
}
if remaining_digits > 0 {
panic!("index out of bounds");
}
let first = DigitsUnds {
bytes: self.bytes.split_at(mid + unds).0,
digits: mid,
};
// skip over underscores between first part and last part
while let Some((&b'_', rem)) = rem_bytes.split_first() {
rem_bytes = rem;
}
(
first,
DigitsUnds {
bytes: rem_bytes,
digits: self.digits - mid,
},
)
}
#[inline]
pub const fn split_first(self) -> Option<(u8, DigitsUnds<'a>)> {
let Some((&first, mut rem_bytes)) = self.bytes.split_first() else {
return None;
};
// first byte is never underscore
debug_assert!(first != b'_');
// skip over underscores between first digit and last part
while let Some((&b'_', rem)) = rem_bytes.split_first() {
rem_bytes = rem;
}
Some((
first,
DigitsUnds {
bytes: rem_bytes,
digits: self.digits - 1,
},
))
}
#[inline]
const fn split_last(self) -> Option<(u8, DigitsUnds<'a>)> {
let Some((&last, mut rem_bytes)) = self.bytes.split_last() else {
return None;
};
// last byte is never underscore
debug_assert!(last != b'_');
// skip over underscores between first part and last digit
while let Some((&b'_', rem)) = rem_bytes.split_last() {
rem_bytes = rem;
}
Some((
last,
DigitsUnds {
bytes: rem_bytes,
digits: self.digits - 1,
},
))
}
const fn split_leading_zeros(self) -> (usize, DigitsUnds<'a>) {
let mut zeros = 0;
let mut rem = self;
while let Some((b'0', rest)) = rem.split_first() {
zeros += 1;
rem = rest;
}
(zeros, rem)
}
const fn split_trailing_zeros(self) -> (usize, DigitsUnds<'a>) {
let mut zeros = 0;
let mut rem = self;
while let Some((b'0', rest)) = rem.split_last() {
zeros += 1;
rem = rest;
}
(zeros, rem)
}
}
#[derive(Clone, Copy, Debug)]
pub struct DigitsExp<'a> {
leading_zeros: usize,
part1: DigitsUnds<'a>,
part2: DigitsUnds<'a>,
trailing_zeros: usize,
}
impl<'a> DigitsExp<'a> {
const EMPTY: DigitsExp<'a> = DigitsExp {
leading_zeros: 0,
part1: DigitsUnds::EMPTY,
part2: DigitsUnds::EMPTY,
trailing_zeros: 0,
};
const fn new1(digits: DigitsUnds<'a>) -> DigitsExp<'a> {
let (leading_zeros, rest) = digits.split_leading_zeros();
let (trailing_zeros, rest) = rest.split_trailing_zeros();
DigitsExp {
leading_zeros,
part1: rest,
part2: DigitsUnds::EMPTY,
trailing_zeros,
}
}
const fn new2(digits1: DigitsUnds<'a>, digits2: DigitsUnds<'a>) -> DigitsExp<'a> {
let (mut leading_zeros, mut digits1) = digits1.split_leading_zeros();
let digits2 = if digits1.is_empty() {
let (more_leading_zeros, new_digits1) = digits2.split_leading_zeros();
leading_zeros += more_leading_zeros;
digits1 = new_digits1;
DigitsUnds::EMPTY
} else {
digits2
};
let (mut trailing_zeros, digits2) = digits2.split_trailing_zeros();
if digits2.is_empty() {
let (more_trailing_zeros, new_digits1) = digits1.split_trailing_zeros();
trailing_zeros += more_trailing_zeros;
digits1 = new_digits1;
}
DigitsExp {
leading_zeros,
part1: digits1,
part2: digits2,
trailing_zeros,
}
}
// exp.unsigned_abs() must fit in usize, and results must have lengths that fit in usize
pub const fn new_int_frac(
int: DigitsUnds<'a>,
frac: DigitsUnds<'a>,
exp: i32,
) -> Option<(DigitsExp<'a>, DigitsExp<'a>)> {
if int.len() > usize::MAX - frac.len() {
return None;
}
let abs_exp = exp.unsigned_abs() as usize;
if abs_exp as u32 != exp.unsigned_abs() {
return None;
}
let (mut int, mut frac) = if exp == 0 {
(DigitsExp::new1(int), DigitsExp::new1(frac))
} else if exp < 0 {
if let Some(extra_zeros) = abs_exp.checked_sub(int.len()) {
let mut frac = DigitsExp::new2(int, frac);
frac.trailing_zeros = 0;
if extra_zeros > usize::MAX - frac.len() {
return None;
}
frac.leading_zeros += extra_zeros;
(DigitsExp::EMPTY, frac)
} else {
let int = int.split_at(int.len() - abs_exp);
(DigitsExp::new1(int.0), DigitsExp::new2(int.1, frac))
}
} else {
// exp > 0
if let Some(extra_zeros) = abs_exp.checked_sub(frac.len()) {
let mut int = DigitsExp::new2(int, frac);
int.leading_zeros = 0;
if extra_zeros > usize::MAX - int.len() {
return None;
}
int.trailing_zeros += extra_zeros;
(int, DigitsExp::EMPTY)
} else {
let frac = frac.split_at(abs_exp);
(DigitsExp::new2(int, frac.0), DigitsExp::new1(frac.1))
}
};
int.leading_zeros = 0;
if int.part1.is_empty() && int.part2.is_empty() {
int.trailing_zeros = 0;
}
frac.trailing_zeros = 0;
if frac.part2.is_empty() && frac.part1.is_empty() {
frac.leading_zeros = 0;
}
Some((int, frac))
}
#[inline]
pub const fn len(self) -> usize {
self.leading_zeros + self.part1.len() + self.part2.len() + self.trailing_zeros
}
#[inline]
pub const fn is_empty(self) -> bool {
self.len() == 0
}
pub const fn split_at(self, mut mid: usize) -> (DigitsExp<'a>, DigitsExp<'a>) {
let mut first = DigitsExp::EMPTY;
let mut last = self;
if mid == 0 {
return (first, last);
}
if mid < self.leading_zeros {
(first.leading_zeros, last.leading_zeros) = (mid, self.leading_zeros - mid);
return (first, last);
}
(first.leading_zeros, last.leading_zeros) = (self.leading_zeros, 0);
mid -= self.leading_zeros;
if mid == 0 {
return (first, last);
}
if mid < self.part1.len() {
(first.part1, last.part1) = self.part1.split_at(mid);
return (first, last);
}
first.part1 = self.part1;
last.part1 = self.part2;
last.part2 = DigitsUnds::EMPTY;
mid -= self.part1.len();
if mid == 0 {
return (first, last);
}
if mid < self.part2.len() {
(first.part2, last.part1) = self.part2.split_at(mid);
return (first, last);
}
first.part2 = self.part2;
last.leading_zeros = self.trailing_zeros;
last.part1 = DigitsUnds::EMPTY;
last.trailing_zeros = 0;
mid -= self.part2.len();
if mid == 0 {
return (first, last);
}
if mid < self.trailing_zeros {
(first.trailing_zeros, last.leading_zeros) = (mid, self.trailing_zeros - mid);
return (first, last);
}
(first.trailing_zeros, last.leading_zeros) = (self.trailing_zeros, 0);
mid -= self.trailing_zeros;
if mid == 0 {
return (first, last);
}
panic!("index out of bounds");
}
// no automatic renormalization done after split_first
#[inline]
pub const fn split_first(self) -> Option<(u8, DigitsExp<'a>)> {
if self.leading_zeros > 0 {
return Some((
b'0',
DigitsExp {
leading_zeros: self.leading_zeros - 1,
..self
},
));
}
if let Some((first, rest)) = self.part1.split_first() {
return Some((
first,
DigitsExp {
part1: rest,
..self
},
));
}
if let Some((first, rest)) = self.part2.split_first() {
return Some((
first,
DigitsExp {
part2: rest,
..self
},
));
}
if self.trailing_zeros > 0 {
return Some((
b'0',
DigitsExp {
trailing_zeros: self.trailing_zeros - 1,
..self
},
));
}
None
}
}