pros_devices/
screen.rs

1//! Brain screen display and touch functions.
2//!
3//! Contains user calls to the v5 screen for touching and displaying graphics.
4//! The [`Fill`] trait can be used to draw shapes and text to the screen.
5
6use alloc::{ffi::CString, string::String, vec::Vec};
7
8use pros_core::{bail_on, map_errno};
9use pros_sys::PROS_ERR;
10use snafu::Snafu;
11
12use crate::color::{IntoRgb, Rgb};
13
14#[derive(Debug, Eq, PartialEq)]
15/// Represents the physical display on the V5 Brain.
16pub struct Screen {
17    writer_buffer: String,
18    current_line: i16,
19}
20
21impl core::fmt::Write for Screen {
22    fn write_str(&mut self, text: &str) -> core::fmt::Result {
23        for character in text.chars() {
24            if character == '\n' {
25                if self.current_line > (Self::MAX_VISIBLE_LINES as i16 - 2) {
26                    self.scroll(0, Self::LINE_HEIGHT)
27                        .map_err(|_| core::fmt::Error)?;
28                } else {
29                    self.current_line += 1;
30                }
31
32                self.flush_writer().map_err(|_| core::fmt::Error)?;
33            } else {
34                self.writer_buffer.push(character);
35            }
36        }
37
38        self.fill(
39            &Text::new(
40                self.writer_buffer.as_str(),
41                TextPosition::Line(self.current_line),
42                TextFormat::Medium,
43            ),
44            Rgb::WHITE,
45        )
46        .map_err(|_| core::fmt::Error)?;
47
48        Ok(())
49    }
50}
51
52/// A type implementing this trait can draw a filled shape to the display.
53pub trait Fill {
54    /// The type of error that can be generated when drawing to the screen.
55    type Error;
56
57    /// Draw a filled shape to the display.
58    fn fill(&self, screen: &mut Screen, color: impl IntoRgb) -> Result<(), Self::Error>;
59}
60
61/// A type implementing this trait can draw an outlined shape to the display.
62pub trait Stroke {
63    /// The type of error that can be generated when drawing to the screen.
64    type Error;
65
66    /// Draw an outlined shape to the display.
67    fn stroke(&self, screen: &mut Screen, color: impl IntoRgb) -> Result<(), Self::Error>;
68}
69
70#[derive(Debug, Clone, Copy, Eq, PartialEq)]
71/// A circle that can be drawn on the screen.
72pub struct Circle {
73    x: i16,
74    y: i16,
75    radius: i16,
76}
77
78impl Circle {
79    /// Create a circle with the given coordinates and radius.
80    /// The coordinates are the center of the circle.
81    pub const fn new(x: i16, y: i16, radius: i16) -> Self {
82        Self { x, y, radius }
83    }
84}
85
86impl Fill for Circle {
87    type Error = ScreenError;
88
89    fn fill(&self, _screen: &mut Screen, color: impl IntoRgb) -> Result<(), Self::Error> {
90        bail_on!(PROS_ERR as u32, unsafe {
91            pros_sys::screen_set_pen(color.into_rgb().into())
92        });
93        bail_on!(PROS_ERR as u32, unsafe {
94            pros_sys::screen_fill_circle(self.x, self.y, self.radius)
95        });
96
97        Ok(())
98    }
99}
100
101impl Stroke for Circle {
102    type Error = ScreenError;
103
104    fn stroke(&self, _screen: &mut Screen, color: impl IntoRgb) -> Result<(), Self::Error> {
105        bail_on!(PROS_ERR as u32, unsafe {
106            pros_sys::screen_set_pen(color.into_rgb().into())
107        });
108        bail_on!(PROS_ERR as u32, unsafe {
109            pros_sys::screen_draw_circle(self.x, self.y, self.radius)
110        });
111
112        Ok(())
113    }
114}
115
116#[derive(Debug, Clone, Copy, Eq, PartialEq)]
117/// A line that can be drawn on the screen.
118/// The width is the same as the pen width.
119pub struct Line {
120    x0: i16,
121    y0: i16,
122    x1: i16,
123    y1: i16,
124}
125
126impl Line {
127    /// Create a new line with the given coordinates.
128    pub const fn new(x0: i16, y0: i16, x1: i16, y1: i16) -> Self {
129        Self { x0, y0, x1, y1 }
130    }
131}
132
133impl Fill for Line {
134    type Error = ScreenError;
135
136    fn fill(&self, _screen: &mut Screen, color: impl IntoRgb) -> Result<(), Self::Error> {
137        bail_on!(PROS_ERR as u32, unsafe {
138            pros_sys::screen_set_pen(color.into_rgb().into())
139        });
140        bail_on!(PROS_ERR as u32, unsafe {
141            pros_sys::screen_draw_line(self.x0, self.y0, self.x1, self.y1)
142        });
143
144        Ok(())
145    }
146}
147
148#[derive(Debug, Clone, Copy, Eq, PartialEq)]
149/// A rectangle that can be drawn on the screen.
150pub struct Rect {
151    x0: i16,
152    y0: i16,
153    x1: i16,
154    y1: i16,
155}
156
157impl Rect {
158    /// Create a new rectangle with the given coordinates.
159    pub const fn new(start_x: i16, start_y: i16, end_x: i16, end_y: i16) -> Self {
160        Self {
161            x0: start_x,
162            y0: start_y,
163            x1: end_x,
164            y1: end_y,
165        }
166    }
167}
168
169impl Stroke for Rect {
170    type Error = ScreenError;
171
172    fn stroke(&self, _screen: &mut Screen, color: impl IntoRgb) -> Result<(), Self::Error> {
173        bail_on!(PROS_ERR as u32, unsafe {
174            pros_sys::screen_set_pen(color.into_rgb().into())
175        });
176        bail_on!(PROS_ERR as u32, unsafe {
177            pros_sys::screen_draw_rect(self.x0, self.y0, self.x1, self.y1)
178        });
179
180        Ok(())
181    }
182}
183
184impl Fill for Rect {
185    type Error = ScreenError;
186
187    fn fill(&self, _screen: &mut Screen, color: impl IntoRgb) -> Result<(), Self::Error> {
188        bail_on!(PROS_ERR as u32, unsafe {
189            pros_sys::screen_set_pen(color.into_rgb().into())
190        });
191        bail_on!(PROS_ERR as u32, unsafe {
192            pros_sys::screen_fill_rect(self.x0, self.y0, self.x1, self.y1)
193        });
194
195        Ok(())
196    }
197}
198
199#[repr(i32)]
200#[derive(Debug, Clone, Copy, Eq, PartialEq)]
201/// Options for how a text object should be formatted.
202pub enum TextFormat {
203    /// Small text.
204    Small = pros_sys::E_TEXT_SMALL,
205    /// Medium text.
206    Medium = pros_sys::E_TEXT_MEDIUM,
207    /// Large text.
208    Large = pros_sys::E_TEXT_LARGE,
209    /// Medium horizontally centered text.
210    MediumCenter = pros_sys::E_TEXT_MEDIUM_CENTER,
211    /// Large horizontally centered text.
212    LargeCenter = pros_sys::E_TEXT_LARGE_CENTER,
213}
214
215impl From<TextFormat> for pros_sys::text_format_e_t {
216    fn from(value: TextFormat) -> pros_sys::text_format_e_t {
217        value as _
218    }
219}
220
221#[derive(Debug, Clone, Copy, Eq, PartialEq)]
222/// The position of a text object on the screen.
223pub enum TextPosition {
224    /// A point to draw the text at.
225    Point(i16, i16),
226    /// A line number to draw the text at.
227    Line(i16),
228}
229
230#[derive(Debug, Clone, Eq, PartialEq)]
231/// A peice of text that can be drawn on the display.
232pub struct Text {
233    position: TextPosition,
234    text: CString,
235    format: TextFormat,
236}
237
238impl Text {
239    /// Create a new text with a given position and format
240    pub fn new(text: &str, position: TextPosition, format: TextFormat) -> Self {
241        Self {
242            text: CString::new(text)
243                .expect("CString::new encountered NULL (U+0000) byte in non-terminating position."),
244            position,
245            format,
246        }
247    }
248}
249
250impl Fill for Text {
251    type Error = ScreenError;
252
253    fn fill(&self, _screen: &mut Screen, color: impl IntoRgb) -> Result<(), Self::Error> {
254        bail_on!(PROS_ERR as u32, unsafe {
255            pros_sys::screen_set_pen(color.into_rgb().into())
256        });
257        bail_on!(PROS_ERR as u32, unsafe {
258            match self.position {
259                TextPosition::Point(x, y) => {
260                    pros_sys::screen_print_at(self.format.into(), x, y, self.text.as_ptr())
261                }
262                TextPosition::Line(line) => {
263                    pros_sys::screen_print(self.format.into(), line, self.text.as_ptr())
264                }
265            }
266        });
267
268        Ok(())
269    }
270}
271
272#[derive(Debug, Clone, Copy, Eq, PartialEq)]
273/// A touch event on the screen.
274pub struct TouchEvent {
275    /// Touch state.
276    pub state: TouchState,
277    /// X coordinate of the touch.
278    pub x: i16,
279    /// Y coordinate of the touch.
280    pub y: i16,
281    /// how many times the screen has been pressed.
282    pub press_count: i32,
283    /// how many times the screen has been released.
284    pub release_count: i32,
285}
286
287impl TryFrom<pros_sys::screen_touch_status_s_t> for TouchEvent {
288    type Error = ScreenError;
289
290    fn try_from(value: pros_sys::screen_touch_status_s_t) -> Result<Self, Self::Error> {
291        Ok(Self {
292            state: value.touch_status.try_into()?,
293            x: value.x,
294            y: value.y,
295            press_count: value.press_count,
296            release_count: value.release_count,
297        })
298    }
299}
300
301#[repr(i32)]
302#[derive(Debug, Clone, Copy, Eq, PartialEq)]
303/// The state of a given touch.
304pub enum TouchState {
305    /// The touch has been released.
306    Released = pros_sys::E_TOUCH_RELEASED,
307    /// The screen has been touched.
308    Pressed = pros_sys::E_TOUCH_PRESSED,
309    /// The touch is still being held.
310    Held = pros_sys::E_TOUCH_HELD,
311}
312
313impl TryFrom<pros_sys::last_touch_e_t> for TouchState {
314    type Error = ScreenError;
315
316    fn try_from(value: pros_sys::last_touch_e_t) -> Result<Self, Self::Error> {
317        bail_on!(pros_sys::E_TOUCH_ERROR, value);
318
319        Ok(match value {
320            pros_sys::E_TOUCH_RELEASED => Self::Released,
321            pros_sys::E_TOUCH_PRESSED => Self::Pressed,
322            pros_sys::E_TOUCH_HELD => Self::Held,
323            _ => unreachable!(),
324        })
325    }
326}
327
328impl From<TouchState> for pros_sys::last_touch_e_t {
329    fn from(value: TouchState) -> pros_sys::last_touch_e_t {
330        value as _
331    }
332}
333
334impl Screen {
335    /// The maximum number of lines that can be visible on the screen at once.
336    pub const MAX_VISIBLE_LINES: usize = 12;
337
338    /// The height of a single line of text on the screen.
339    pub const LINE_HEIGHT: i16 = 20;
340
341    /// The horizontal resolution of the display.
342    pub const HORIZONTAL_RESOLUTION: i16 = 480;
343
344    /// The vertical resolution of the writable part of the display.
345    pub const VERTICAL_RESOLUTION: i16 = 240;
346
347    /// Create a new screen.
348    ///
349    /// # Safety
350    ///
351    /// Creating new `Screen`s is inherently unsafe due to the possibility of constructing
352    /// more than one screen at once allowing multiple mutable references to the same
353    /// hardware device. Prefer using [`Peripherals`](crate::peripherals::Peripherals) to register devices if possible.
354    pub unsafe fn new() -> Self {
355        Self {
356            current_line: 0,
357            writer_buffer: String::default(),
358        }
359    }
360
361    fn flush_writer(&mut self) -> Result<(), ScreenError> {
362        self.fill(
363            &Text::new(
364                self.writer_buffer.as_str(),
365                TextPosition::Line(self.current_line),
366                TextFormat::Medium,
367            ),
368            Rgb::WHITE,
369        )?;
370
371        self.writer_buffer.clear();
372
373        Ok(())
374    }
375
376    /// Scroll the entire display buffer.
377    ///
378    /// This function effectively y-offsets all pixels drawn to the display buffer by
379    /// a number (`offset`) of pixels.
380    pub fn scroll(&mut self, start: i16, offset: i16) -> Result<(), ScreenError> {
381        bail_on!(PROS_ERR as u32, unsafe {
382            pros_sys::screen_scroll(start, offset)
383        });
384
385        Ok(())
386    }
387
388    /// Scroll a region of the screen.
389    ///
390    /// This will effectively y-offset the display buffer in this area by
391    /// `offset` pixels.
392    pub fn scroll_area(
393        &mut self,
394        x0: i16,
395        y0: i16,
396        x1: i16,
397        y1: i16,
398        offset: i16,
399    ) -> Result<(), ScreenError> {
400        bail_on!(PROS_ERR as u32, unsafe {
401            pros_sys::screen_scroll_area(x0, y0, x1, y1, offset)
402        });
403
404        Ok(())
405    }
406
407    /// Draw a filled object to the screen.
408    pub fn fill(
409        &mut self,
410        shape: &impl Fill<Error = ScreenError>,
411        color: impl IntoRgb,
412    ) -> Result<(), ScreenError> {
413        shape.fill(self, color)
414    }
415
416    /// Draw an outlined object to the screen.
417    pub fn stroke(
418        &mut self,
419        shape: &impl Stroke<Error = ScreenError>,
420        color: impl IntoRgb,
421    ) -> Result<(), ScreenError> {
422        shape.stroke(self, color)
423    }
424
425    /// Wipe the entire display buffer, filling it with a specified color.
426    pub fn erase(color: impl IntoRgb) -> Result<(), ScreenError> {
427        bail_on!(PROS_ERR as u32, unsafe {
428            pros_sys::screen_set_eraser(color.into_rgb().into())
429        });
430        bail_on!(PROS_ERR as u32, unsafe { pros_sys::screen_erase() });
431
432        Ok(())
433    }
434
435    /// Draw a color to a specified pixel position on the screen.
436    pub fn draw_pixel(x: i16, y: i16) -> Result<(), ScreenError> {
437        bail_on!(PROS_ERR as u32, unsafe {
438            pros_sys::screen_draw_pixel(x, y)
439        });
440
441        Ok(())
442    }
443
444    /// Draw a buffer of pixel colors to a specified region of the screen.
445    pub fn draw_buffer<T, I>(
446        &mut self,
447        x0: i16,
448        y0: i16,
449        x1: i16,
450        y1: i16,
451        buf: T,
452        src_stride: i32,
453    ) -> Result<(), ScreenError>
454    where
455        T: IntoIterator<Item = I>,
456        I: IntoRgb,
457    {
458        let raw_buf = buf
459            .into_iter()
460            .map(|i| i.into_rgb().into())
461            .collect::<Vec<_>>();
462        // Convert the coordinates to u32 to avoid overflows when multiplying.
463        let expected_size = ((x1 - x0) as u32 * (y1 - y0) as u32) as usize;
464        if raw_buf.len() != expected_size {
465            return Err(ScreenError::CopyBufferWrongSize {
466                buffer_size: raw_buf.len(),
467                expected_size,
468            });
469        }
470
471        // SAFETY: The buffer is guaranteed to be the correct size.
472        bail_on!(PROS_ERR as u32, unsafe {
473            pros_sys::screen_copy_area(x0, y0, x1, y1, raw_buf.as_ptr(), src_stride)
474        });
475
476        Ok(())
477    }
478
479    /// Draw an error box to the screen.
480    ///
481    /// This function is internally used by the pros-rs panic handler for displaying
482    /// panic messages graphically before exiting.
483    pub fn draw_error(&mut self, msg: &str) -> Result<(), ScreenError> {
484        const ERROR_BOX_MARGIN: i16 = 16;
485        const ERROR_BOX_PADDING: i16 = 16;
486        const LINE_MAX_WIDTH: usize = 52;
487
488        let error_box_rect = Rect::new(
489            ERROR_BOX_MARGIN,
490            ERROR_BOX_MARGIN,
491            Self::HORIZONTAL_RESOLUTION - ERROR_BOX_MARGIN,
492            Self::VERTICAL_RESOLUTION - ERROR_BOX_MARGIN,
493        );
494
495        self.fill(&error_box_rect, Rgb::RED)?;
496        self.stroke(&error_box_rect, Rgb::WHITE)?;
497
498        let mut buffer = String::new();
499        let mut line: i16 = 0;
500
501        for (i, character) in msg.char_indices() {
502            if !character.is_ascii_control() {
503                buffer.push(character);
504            }
505
506            if character == '\n' || ((buffer.len() % LINE_MAX_WIDTH == 0) && (i > 0)) {
507                self.fill(
508                    &Text::new(
509                        buffer.as_str(),
510                        TextPosition::Point(
511                            ERROR_BOX_MARGIN + ERROR_BOX_PADDING,
512                            ERROR_BOX_MARGIN + ERROR_BOX_PADDING + (line * Self::LINE_HEIGHT),
513                        ),
514                        TextFormat::Small,
515                    ),
516                    Rgb::WHITE,
517                )?;
518
519                line += 1;
520                buffer.clear();
521            }
522        }
523
524        self.fill(
525            &Text::new(
526                buffer.as_str(),
527                TextPosition::Point(
528                    ERROR_BOX_MARGIN + ERROR_BOX_PADDING,
529                    ERROR_BOX_MARGIN + ERROR_BOX_PADDING + (line * Self::LINE_HEIGHT),
530                ),
531                TextFormat::Small,
532            ),
533            Rgb::WHITE,
534        )?;
535
536        Ok(())
537    }
538
539    /// Get the current touch status of the screen.
540    pub fn touch_status(&self) -> Result<TouchEvent, ScreenError> {
541        unsafe { pros_sys::screen_touch_status() }.try_into()
542    }
543}
544
545#[derive(Debug, Snafu)]
546/// Errors that can occur when interacting with the screen.
547pub enum ScreenError {
548    /// Another resource is currently trying to access the screen mutex.
549    ConcurrentAccess,
550
551    /// The given buffer of colors was wrong size to fill the specified area.
552    CopyBufferWrongSize {
553        /// The size of the buffer.
554        buffer_size: usize,
555        /// The expected size of the buffer.
556        expected_size: usize,
557    },
558}
559
560map_errno! {
561    ScreenError {
562        EACCES => Self::ConcurrentAccess,
563    }
564}