1use 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)]
15pub 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
52pub trait Fill {
54 type Error;
56
57 fn fill(&self, screen: &mut Screen, color: impl IntoRgb) -> Result<(), Self::Error>;
59}
60
61pub trait Stroke {
63 type Error;
65
66 fn stroke(&self, screen: &mut Screen, color: impl IntoRgb) -> Result<(), Self::Error>;
68}
69
70#[derive(Debug, Clone, Copy, Eq, PartialEq)]
71pub struct Circle {
73 x: i16,
74 y: i16,
75 radius: i16,
76}
77
78impl Circle {
79 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)]
117pub struct Line {
120 x0: i16,
121 y0: i16,
122 x1: i16,
123 y1: i16,
124}
125
126impl Line {
127 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)]
149pub struct Rect {
151 x0: i16,
152 y0: i16,
153 x1: i16,
154 y1: i16,
155}
156
157impl Rect {
158 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)]
201pub enum TextFormat {
203 Small = pros_sys::E_TEXT_SMALL,
205 Medium = pros_sys::E_TEXT_MEDIUM,
207 Large = pros_sys::E_TEXT_LARGE,
209 MediumCenter = pros_sys::E_TEXT_MEDIUM_CENTER,
211 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)]
222pub enum TextPosition {
224 Point(i16, i16),
226 Line(i16),
228}
229
230#[derive(Debug, Clone, Eq, PartialEq)]
231pub struct Text {
233 position: TextPosition,
234 text: CString,
235 format: TextFormat,
236}
237
238impl Text {
239 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)]
273pub struct TouchEvent {
275 pub state: TouchState,
277 pub x: i16,
279 pub y: i16,
281 pub press_count: i32,
283 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)]
303pub enum TouchState {
305 Released = pros_sys::E_TOUCH_RELEASED,
307 Pressed = pros_sys::E_TOUCH_PRESSED,
309 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 pub const MAX_VISIBLE_LINES: usize = 12;
337
338 pub const LINE_HEIGHT: i16 = 20;
340
341 pub const HORIZONTAL_RESOLUTION: i16 = 480;
343
344 pub const VERTICAL_RESOLUTION: i16 = 240;
346
347 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 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 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 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 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 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 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 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 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 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 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 pub fn touch_status(&self) -> Result<TouchEvent, ScreenError> {
541 unsafe { pros_sys::screen_touch_status() }.try_into()
542 }
543}
544
545#[derive(Debug, Snafu)]
546pub enum ScreenError {
548 ConcurrentAccess,
550
551 CopyBufferWrongSize {
553 buffer_size: usize,
555 expected_size: usize,
557 },
558}
559
560map_errno! {
561 ScreenError {
562 EACCES => Self::ConcurrentAccess,
563 }
564}