pros_simulator/host/
lcd.rs

1use std::mem::replace;
2
3use pros_simulator_interface::{LcdLines, SimulatorEvent, LCD_HEIGHT, LCD_WIDTH};
4use pros_sys::error as errno;
5use tokio::sync::Mutex;
6use wasmtime::{AsContextMut, Table};
7
8use crate::interface::SimulatorInterface;
9
10#[derive(Debug)]
11pub struct AlreadyInitializedError;
12
13pub struct LcdColors {
14    pub background: u32,
15    pub foreground: u32,
16}
17
18pub struct Lcd {
19    lines: LcdLines,
20    interface: SimulatorInterface,
21    initialized: bool,
22    button_presses: [bool; 3],
23    button_callbacks: [Option<u32>; 3],
24}
25
26impl Lcd {
27    pub fn new(interface: SimulatorInterface) -> Self {
28        Self {
29            lines: Default::default(),
30            interface,
31            initialized: false,
32            button_presses: [false; 3],
33            button_callbacks: [None; 3],
34        }
35    }
36
37    fn assert_initialized(&self) -> Result<(), i32> {
38        if !self.initialized {
39            tracing::error!("Not initialized");
40            return Err(errno::ENXIO);
41        }
42        Ok(())
43    }
44
45    fn assert_line_in_bounds(&self, line: i32) -> Result<(), i32> {
46        if line < 0 || line >= LCD_HEIGHT as i32 {
47            tracing::error!("Line {line} not in bounds");
48            return Err(errno::EINVAL);
49        }
50        Ok(())
51    }
52
53    fn assert_text_length_in_bounds(&self, text: &str) -> Result<(), i32> {
54        if text.len() > LCD_WIDTH as usize {
55            tracing::error!("Text too long for LCD");
56            return Err(errno::EINVAL);
57        }
58        Ok(())
59    }
60
61    pub fn initialize(&mut self) -> Result<(), AlreadyInitializedError> {
62        if self.initialized {
63            return Err(AlreadyInitializedError);
64        }
65        self.initialized = true;
66        self.button_presses = Default::default();
67        self.button_callbacks = Default::default();
68        self.interface.send(SimulatorEvent::LcdInitialized);
69        Ok(())
70    }
71
72    pub fn set_line(&mut self, line: i32, text: &str) -> Result<(), i32> {
73        self.assert_initialized()?;
74        self.assert_line_in_bounds(line)?;
75        self.assert_text_length_in_bounds(text)?;
76
77        self.lines[line as usize] = text.to_string();
78        self.interface
79            .send(SimulatorEvent::LcdUpdated(self.lines.clone()));
80        Ok(())
81    }
82
83    pub fn clear(&mut self) -> Result<(), i32> {
84        self.assert_initialized()?;
85        for line in &mut self.lines {
86            line.clear();
87        }
88        self.interface
89            .send(SimulatorEvent::LcdUpdated(self.lines.clone()));
90        Ok(())
91    }
92
93    pub fn clear_line(&mut self, line: i32) -> Result<(), i32> {
94        self.assert_initialized()?;
95        self.assert_line_in_bounds(line)?;
96
97        self.lines[line as usize] = String::new();
98        self.interface
99            .send(SimulatorEvent::LcdUpdated(self.lines.clone()));
100        Ok(())
101    }
102
103    pub fn set_btn_press_callback(&mut self, button: usize, callback: u32) -> Result<(), i32> {
104        self.assert_initialized()?;
105
106        self.button_callbacks[button] = Some(callback);
107        Ok(())
108    }
109
110    /// Marks certain LCD buttons as being pressed. If a button was not pressed before
111    /// but is now, the callback for that button will be called.
112    pub async fn press(
113        lcd: &Mutex<Self>,
114        mut store: impl AsContextMut<Data = impl Send>,
115        callback_table: Table,
116        buttons: [bool; 3],
117    ) -> anyhow::Result<()> {
118        let mut lcd = lcd.lock().await;
119        let previous_presses = replace(&mut lcd.button_presses, buttons);
120        let callbacks = lcd.button_callbacks;
121        drop(lcd);
122
123        for (index, button_pressed) in buttons.iter().enumerate() {
124            if *button_pressed && !previous_presses[index] {
125                if let Some(cb_index) = &callbacks[index] {
126                    let callback = callback_table.get(&mut store, *cb_index).unwrap();
127                    let callback = callback.funcref().unwrap().unwrap();
128                    let callback = callback.typed::<(), ()>(&mut store).unwrap();
129                    callback.call_async(&mut store, ()).await?;
130                }
131            }
132        }
133
134        Ok(())
135    }
136}