You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
215 lines
6.0 KiB
215 lines
6.0 KiB
use crate::{
|
|
strutil::{ShortString, TString},
|
|
ui::{
|
|
component::text::TextStyle,
|
|
display,
|
|
display::toif::Icon,
|
|
geometry::{Alignment2D, Offset, Point},
|
|
},
|
|
};
|
|
|
|
use cstr_core::CStr;
|
|
use heapless::String;
|
|
|
|
use super::display::Font;
|
|
|
|
pub trait ResultExt {
|
|
fn assert_if_debugging_ui(self, message: &str);
|
|
}
|
|
|
|
impl<T, E> ResultExt for Result<T, E> {
|
|
fn assert_if_debugging_ui(self, #[allow(unused)] message: &str) {
|
|
#[cfg(feature = "ui_debug")]
|
|
if self.is_err() {
|
|
print!("Panic from assert_if_debugging_ui: ");
|
|
println!(message);
|
|
panic!("{}", message);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Constructs a string from a C string.
|
|
///
|
|
/// # Safety
|
|
///
|
|
/// The caller is responsible that the pointer is valid, which means that:
|
|
/// (a) it points to a memory containing a valid C string (zero-terminated
|
|
/// sequence of characters), and
|
|
/// (b) that the pointer has appropriate lifetime.
|
|
pub unsafe fn from_c_str<'a>(c_str: *const cty::c_char) -> Option<&'a str> {
|
|
if c_str.is_null() {
|
|
return None;
|
|
}
|
|
unsafe {
|
|
let bytes = CStr::from_ptr(c_str).to_bytes();
|
|
if bytes.is_ascii() {
|
|
Some(core::str::from_utf8_unchecked(bytes))
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Construct str from a C array.
|
|
///
|
|
/// # Safety
|
|
///
|
|
/// The caller is responsible that the pointer is valid, which means that:
|
|
/// (a) it points to a memory containing array of characters, with length `len`,
|
|
/// and
|
|
/// (b) that the pointer has appropriate lifetime.
|
|
pub unsafe fn from_c_array<'a>(c_str: *const cty::c_char, len: usize) -> Option<&'a str> {
|
|
if c_str.is_null() {
|
|
return None;
|
|
}
|
|
unsafe {
|
|
let slice = core::slice::from_raw_parts(c_str as *const u8, len);
|
|
if slice.is_ascii() {
|
|
Some(core::str::from_utf8_unchecked(slice))
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(feature = "ui_debug")]
|
|
static mut DISABLE_ANIMATION: bool = false;
|
|
|
|
#[cfg(feature = "ui_debug")]
|
|
pub fn animation_disabled() -> bool {
|
|
// SAFETY: single-threaded access
|
|
unsafe { DISABLE_ANIMATION }
|
|
}
|
|
|
|
#[cfg(feature = "ui_debug")]
|
|
pub fn set_animation_disabled(disabled: bool) {
|
|
// SAFETY: single-threaded access
|
|
unsafe {
|
|
DISABLE_ANIMATION = disabled;
|
|
}
|
|
}
|
|
|
|
#[cfg(not(feature = "ui_debug"))]
|
|
pub fn animation_disabled() -> bool {
|
|
false
|
|
}
|
|
|
|
#[cfg(not(feature = "ui_debug"))]
|
|
pub fn set_animation_disabled(_disabled: bool) {}
|
|
|
|
/// Display an icon and a text centered relative to given `Point`.
|
|
pub fn icon_text_center(
|
|
baseline: Point,
|
|
icon: Icon,
|
|
space: i16,
|
|
text: TString<'_>,
|
|
style: TextStyle,
|
|
text_offset: Offset,
|
|
) {
|
|
text.map(|t| {
|
|
let icon_width = icon.toif.width();
|
|
let text_width = style.text_font.text_width(t);
|
|
let text_height = style.text_font.text_height();
|
|
let text_center = baseline + Offset::new((icon_width + space) / 2, text_height / 2);
|
|
let icon_center = baseline - Offset::x((text_width + space) / 2);
|
|
|
|
display::text_center(
|
|
text_center + text_offset,
|
|
t,
|
|
style.text_font,
|
|
style.text_color,
|
|
style.background_color,
|
|
);
|
|
icon.draw(
|
|
icon_center,
|
|
Alignment2D::CENTER,
|
|
style.text_color,
|
|
style.background_color,
|
|
);
|
|
});
|
|
}
|
|
|
|
/// Convert char to a ShortString.
|
|
pub fn char_to_string(ch: char) -> ShortString {
|
|
let mut s = String::new();
|
|
unwrap!(s.push(ch));
|
|
s
|
|
}
|
|
|
|
/// Returns text to be fit on one line of a given length.
|
|
/// When the text is too long to fit, it is truncated with ellipsis
|
|
/// on the left side.
|
|
/// Hardcoding 50 (via ShortString) as the length of the returned String -
|
|
/// there should not be any lines as long as this.
|
|
pub fn long_line_content_with_ellipsis(
|
|
text: &str,
|
|
ellipsis: &str,
|
|
text_font: Font,
|
|
available_width: i16,
|
|
) -> ShortString {
|
|
if text_font.text_width(text) <= available_width {
|
|
unwrap!(String::try_from(text)) // whole text can fit
|
|
} else {
|
|
// Text is longer, showing its right end with ellipsis at the beginning.
|
|
// Finding out how many additional text characters will fit in,
|
|
// starting from the right end.
|
|
let ellipsis_width = text_font.text_width(ellipsis);
|
|
let remaining_available_width = available_width - ellipsis_width;
|
|
let chars_from_right = text_font.longest_suffix(remaining_available_width, text);
|
|
|
|
build_string!(50, ellipsis, &text[text.len() - chars_from_right..])
|
|
}
|
|
}
|
|
|
|
#[macro_export]
|
|
/// Create the `Icon` constant with given name and path.
|
|
/// Possibly users can supply `true` as a third argument and this
|
|
/// will signify that the icon has empty right column.
|
|
macro_rules! include_icon {
|
|
($name:ident, $path:expr, empty_right_col = $empty:expr) => {
|
|
pub const $name: Icon = if $empty {
|
|
Icon::debug_named(include_res!($path), stringify!($name)).with_empty_right_column()
|
|
} else {
|
|
Icon::debug_named(include_res!($path), stringify!($name))
|
|
};
|
|
};
|
|
// No empty right column by default.
|
|
($name:ident, $path:expr) => {
|
|
include_icon!($name, $path, empty_right_col = false);
|
|
};
|
|
}
|
|
|
|
/// Splits a version stored as a u32 into four numbers
|
|
/// starting with the major version.
|
|
pub fn version_split(version: u32) -> [u8; 4] {
|
|
version.to_le_bytes()
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use crate::strutil;
|
|
|
|
#[test]
|
|
fn u32_to_str_valid() {
|
|
let testcases = [0, 1, 9, 10, 11, 999, u32::MAX];
|
|
let mut b = [0; 10];
|
|
|
|
for test in testcases {
|
|
let converted = strutil::format_i64(test as i64, &mut b).unwrap();
|
|
let s = test.to_string();
|
|
assert_eq!(converted, s);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn u32_to_str_small_buffer() {
|
|
let testcases = [1000, 31337, u32::MAX];
|
|
let mut b = [0; 3];
|
|
|
|
for test in testcases {
|
|
let converted = strutil::format_i64(test as i64, &mut b);
|
|
assert_eq!(converted, None)
|
|
}
|
|
}
|
|
}
|