Skip to main content

firefly_rust/
misc.rs

1use crate::*;
2
3/// System settings. Can be requested using [`get_settings`].
4#[derive(Clone, Debug)]
5pub struct Settings {
6    /// The preferred color scheme of the player.
7    pub theme: Theme,
8
9    /// The configured interface language.
10    pub language: Language,
11
12    /// If true, the screen is rotated 180 degrees.
13    ///
14    /// In other words, the player holds the device upside-down.
15    /// The touchpad is now on the right and the buttons are on the left.
16    pub rotate_screen: bool,
17
18    /// The player has photosensitivity. The app should avoid any rapid flashes.
19    pub reduce_flashing: bool,
20
21    /// The player wants increased contrast for colors.
22    ///
23    /// If set, the black and white colors in the default
24    /// palette are adjusted automatically. All other colors
25    /// in the default palette or all colors in a custom palette
26    /// should be adjusted by the app.
27    pub contrast: bool,
28
29    /// If true, the player wants to see easter eggs, holiday effects, and weird jokes.
30    pub easter_eggs: bool,
31}
32
33#[derive(PartialEq, Eq, Copy, Clone, Debug, Default)]
34pub enum Language {
35    /// en ๐Ÿ‡ฌ๐Ÿ‡ง ๐Ÿ’‚
36    #[default]
37    English,
38    /// nl ๐Ÿ‡ณ๐Ÿ‡ฑ ๐Ÿง€
39    Dutch,
40    /// fr ๐Ÿ‡ซ๐Ÿ‡ท ๐Ÿฅ
41    French,
42    /// de ๐Ÿ‡ฉ๐Ÿ‡ช ๐Ÿฅจ
43    German,
44    /// it ๐Ÿ‡ฎ๐Ÿ‡น ๐Ÿ•
45    Italian,
46    /// pl ๐Ÿ‡ต๐Ÿ‡ฑ ๐ŸฅŸ
47    Polish,
48    /// ro ๐Ÿ‡ท๐Ÿ‡ด ๐Ÿง›
49    Romanian,
50    /// ru ๐Ÿ‡ท๐Ÿ‡บ ๐Ÿช†
51    Russian,
52    /// es ๐Ÿ‡ช๐Ÿ‡ธ ๐Ÿ‚
53    Spanish,
54    /// sv ๐Ÿ‡ธ๐Ÿ‡ช โ„๏ธ
55    Swedish,
56    /// tr ๐Ÿ‡น๐Ÿ‡ท ๐Ÿ•Œ
57    Turkish,
58    /// uk ๐Ÿ‡บ๐Ÿ‡ฆ โœŠ
59    Ukrainian,
60    /// tp ๐Ÿ‡จ๐Ÿ‡ฆ ๐Ÿ™‚
61    TokiPona,
62}
63
64impl Language {
65    #[must_use]
66    pub fn from_code(b: [u8; 2]) -> Option<Self> {
67        let code = match b {
68            [b'd', b'e'] => Self::German,
69            [b'e', b'n'] => Self::English,
70            [b'e', b's'] => Self::Spanish,
71            [b'f', b'r'] => Self::French,
72            [b'i', b't'] => Self::Italian,
73            [b'n', b'l'] => Self::Dutch,
74            [b'p', b'o'] => Self::Polish,
75            [b'r', b'o'] => Self::Romanian,
76            [b'r', b'u'] => Self::Russian,
77            [b's', b'v'] => Self::Swedish,
78            [b't', b'p'] => Self::TokiPona,
79            [b't', b'r'] => Self::Turkish,
80            [b'u', b'k'] => Self::Ukrainian,
81            _ => return None,
82        };
83        Some(code)
84    }
85
86    #[must_use]
87    pub fn code_array(self) -> [u8; 2] {
88        match self {
89            Self::English => [b'e', b'n'],
90            Self::Dutch => [b'n', b'l'],
91            Self::French => [b'f', b'r'],
92            Self::German => [b'd', b'e'],
93            Self::Italian => [b'i', b't'],
94            Self::Polish => [b'p', b'o'],
95            Self::Romanian => [b'r', b'o'],
96            Self::Russian => [b'r', b'u'],
97            Self::Spanish => [b'e', b's'],
98            Self::Swedish => [b's', b'v'],
99            Self::Turkish => [b't', b'r'],
100            Self::Ukrainian => [b'u', b'k'],
101            Self::TokiPona => [b't', b'p'],
102        }
103    }
104
105    #[must_use]
106    pub fn code_str(self) -> &'static str {
107        match self {
108            Self::English => "en",
109            Self::Dutch => "nl",
110            Self::French => "fr",
111            Self::German => "de",
112            Self::Italian => "it",
113            Self::Polish => "po",
114            Self::Romanian => "ro",
115            Self::Russian => "ru",
116            Self::Spanish => "es",
117            Self::Swedish => "sv",
118            Self::Turkish => "tr",
119            Self::Ukrainian => "uk",
120            Self::TokiPona => "tp",
121        }
122    }
123
124    /// The language name in English.
125    #[must_use]
126    pub fn name_english(self) -> &'static str {
127        match self {
128            Self::English => "English",
129            Self::Dutch => "Dutch",
130            Self::French => "French",
131            Self::German => "German",
132            Self::Italian => "Italian",
133            Self::Polish => "Polish",
134            Self::Romanian => "Romanian",
135            Self::Russian => "Russian",
136            Self::Spanish => "Spanish",
137            Self::Swedish => "Swedish",
138            Self::TokiPona => "TokiPona",
139            Self::Turkish => "Turkish",
140            Self::Ukrainian => "Ukrainian",
141        }
142    }
143
144    /// The language name in the language itself (endonym).
145    #[must_use]
146    pub fn name_native(self) -> &'static str {
147        match self {
148            Self::English => "English",
149            Self::Dutch => "Nederlands",
150            Self::French => "Franรงais",
151            Self::German => "Deutsch",
152            Self::Italian => "Italiano",
153            Self::Polish => "Polski",
154            Self::Romanian => "Romรขnฤƒ",
155            Self::Russian => "ะ ัƒััะบะธะน",
156            Self::Spanish => "Espaรฑol",
157            Self::Swedish => "Svenska",
158            Self::TokiPona => "toki pona",
159            Self::Turkish => "Tรผrkรงe",
160            Self::Ukrainian => "ะฃะบั€ะฐั—ะฝััŒะบะฐ",
161        }
162    }
163
164    /// ISO 8859 encoding slug for the language.
165    ///
166    /// Useful for dynamically loading the correct font for the given language.
167    #[must_use]
168    pub fn encoding(self) -> &'static str {
169        match self {
170            // Just like English, Dutch has very little non-ASCII characters
171            // which can be avoided in translations to make it possible to stick
172            // to the smaller fonts.
173            Self::English | Self::Dutch | Self::TokiPona => "ascii",
174            Self::Italian | Self::Spanish | Self::Swedish => "iso_8859_1",
175            Self::German | Self::French => "iso_8859_2",
176            Self::Russian | Self::Ukrainian => "iso_8859_5",
177            Self::Turkish => "iso_8859_9",
178            Self::Polish => "iso_8859_13",
179            Self::Romanian => "iso_8859_16",
180        }
181    }
182}
183
184/// The preferred color scheme of the peer.
185///
186/// Can be useful for:
187///
188/// * Making UI that matches the system UI.
189/// * Preventing image flashes by making the UI background
190///   the same as in the system UI.
191/// * Providing and auto-switching the dark and light mode.
192#[derive(Clone, Copy, Debug)]
193pub struct Theme {
194    pub id: u8,
195    /// The main color of text and boxes.
196    pub primary: Color,
197    /// The color of disable options, muted text, etc.
198    pub secondary: Color,
199    /// The color of important elements, active options, etc.
200    pub accent: Color,
201    /// The background color, the most contrast color to primary.
202    pub bg: Color,
203}
204
205impl Default for Theme {
206    fn default() -> Self {
207        Self {
208            id: 0,
209            primary: Color::Black,
210            secondary: Color::LightGray,
211            accent: Color::Green,
212            bg: Color::White,
213        }
214    }
215}
216
217/// Log a debug message.
218pub fn log_debug(t: &str) {
219    let ptr = t.as_ptr() as u32;
220    let len = t.len() as u32;
221    unsafe {
222        bindings::log_debug(ptr, len);
223    }
224}
225
226/// Log an error message.
227pub fn log_error(t: &str) {
228    let ptr = t.as_ptr() as u32;
229    let len = t.len() as u32;
230    unsafe {
231        bindings::log_error(ptr, len);
232    }
233}
234
235/// Set the random seed.
236pub fn set_seed(seed: u32) {
237    unsafe {
238        bindings::set_seed(seed);
239    }
240}
241
242/// Get a random value.
243#[must_use]
244pub fn get_random() -> u32 {
245    unsafe { bindings::get_random() }
246}
247
248/// Get the Peer's name.
249///
250/// The name is guaranteed to be a valid ASCII string
251/// and have between 1 and 16 characters.
252#[cfg(feature = "alloc")]
253#[must_use]
254pub fn get_name_buf(p: Peer) -> alloc::string::String {
255    let mut buf = [0u8; 16];
256    let name = get_name(p, &mut buf);
257    alloc::string::String::from(name)
258}
259
260/// Get the Peer's name.
261///
262/// The name is guaranteed to be a valid ASCII string
263/// and have between 1 and 16 characters.
264#[must_use]
265pub fn get_name(p: Peer, buf: &mut [u8; 16]) -> &str {
266    let ptr = buf.as_ptr() as u32;
267    let len = unsafe { bindings::get_name(u32::from(p.0), ptr) };
268    let buf = &buf[..len as usize];
269    unsafe { core::str::from_utf8_unchecked(buf) }
270}
271
272/// Get the peer's system settings.
273///
274/// **IMPORTANT:** This is the only function that accepts as input not only [`Peer`]
275/// but also [`Me`], which might lead to a state drift if used incorrectly.
276/// See [the docs](https://docs.fireflyzero.com/dev/net/) for more info.
277#[must_use]
278pub fn get_settings<P: AnyPeer>(p: P) -> Settings {
279    let raw = unsafe { bindings::get_settings(u32::from(p.into_u8())) };
280    let code = [(raw >> 8) as u8, raw as u8];
281    let language = Language::from_code(code).unwrap_or_default();
282    let flags = raw >> 16;
283    let theme = raw >> 32;
284    let theme = Theme {
285        id: theme as u8,
286        primary: parse_color(theme >> 20),
287        secondary: parse_color(theme >> 16),
288        accent: parse_color(theme >> 12),
289        bg: parse_color(theme >> 8),
290    };
291    Settings {
292        theme,
293        language,
294        rotate_screen: (flags & 0b0001) != 0,
295        reduce_flashing: (flags & 0b0010) != 0,
296        contrast: (flags & 0b0100) != 0,
297        easter_eggs: (flags & 0b1000) != 0,
298    }
299}
300
301fn parse_color(c: u64) -> Color {
302    Color::from((c as u8 & 0xf) + 1)
303}
304
305/// Exit the app after the current update is finished.
306pub fn quit() {
307    unsafe { bindings::quit() }
308}
309
310mod bindings {
311    #[link(wasm_import_module = "misc")]
312    unsafe extern "C" {
313        pub(crate) unsafe fn log_debug(ptr: u32, len: u32);
314        pub(crate) unsafe fn log_error(ptr: u32, len: u32);
315        pub(crate) unsafe fn set_seed(seed: u32);
316        pub(crate) unsafe fn get_random() -> u32;
317        pub(crate) unsafe fn get_name(idx: u32, ptr: u32) -> u32;
318        pub(crate) unsafe fn get_settings(idx: u32) -> u64;
319        pub(crate) unsafe fn quit();
320    }
321}
322
323#[cfg(test)]
324mod tests {
325    use super::*;
326
327    #[test]
328    fn test_language_code_roundtrip() {
329        let mut valid_codes = 0;
330        let letters = "abcdefghijklmnopqrstuvwxyz";
331        for fst in letters.as_bytes() {
332            for snd in letters.as_bytes() {
333                let given = [*fst, *snd];
334                let Some(lang) = Language::from_code(given) else {
335                    continue;
336                };
337                valid_codes += 1;
338                let actual = lang.code_array();
339                assert_eq!(actual, given);
340            }
341        }
342        assert!(valid_codes > 8);
343    }
344}