1use crate::console::{Console, PixelsXY};
20use endbasic_core::{
21 ArgSep, ArgSepSyntax, CallError, CallResult, Callable, CallableMetadata,
22 CallableMetadataBuilder, ExprType, RequiredValueSyntax, Scope, SingularArgSyntax,
23};
24use std::borrow::Cow;
25use std::cell::RefCell;
26use std::convert::TryFrom;
27use std::rc::Rc;
28
29use crate::MachineBuilder;
30
31pub mod lcd;
32
33const CATEGORY: &str = "Graphics
35The EndBASIC console overlays text and graphics in the same canvas. The consequence of this \
36design choice is that the console has two coordinate systems: the character-based system, used by
37the commands described in HELP \"CONSOLE\", and the pixel-based system, used by the commands \
38described in this section.";
39
40fn parse_coordinate(scope: &Scope<'_>, narg: u8) -> CallResult<i16> {
42 let i = scope.get_integer(narg);
43 match i16::try_from(i) {
44 Ok(i) => Ok(i),
45 Err(_) => {
46 Err(CallError::Syntax(scope.get_pos(narg), format!("Coordinate {} out of range", i)))
47 }
48 }
49}
50
51fn parse_coordinates(scope: &Scope<'_>, xarg: u8, yarg: u8) -> CallResult<PixelsXY> {
53 Ok(PixelsXY { x: parse_coordinate(scope, xarg)?, y: parse_coordinate(scope, yarg)? })
54}
55
56fn parse_radius(scope: &Scope<'_>, narg: u8) -> CallResult<u16> {
58 let i = scope.get_integer(narg);
59 match u16::try_from(i) {
60 Ok(i) => Ok(i),
61 Err(_) if i < 0 => {
62 Err(CallError::Syntax(scope.get_pos(narg), format!("Radius {} must be positive", i)))
63 }
64 Err(_) => Err(CallError::Syntax(scope.get_pos(narg), format!("Radius {} out of range", i))),
65 }
66}
67
68pub struct GfxCircleCommand {
70 metadata: Rc<CallableMetadata>,
71 console: Rc<RefCell<dyn Console>>,
72}
73
74impl GfxCircleCommand {
75 pub fn new(console: Rc<RefCell<dyn Console>>) -> Rc<Self> {
77 Rc::from(Self {
78 metadata: CallableMetadataBuilder::new("GFX_CIRCLE")
79 .with_syntax(&[(
80 &[
81 SingularArgSyntax::RequiredValue(
82 RequiredValueSyntax {
83 name: Cow::Borrowed("x"),
84 vtype: ExprType::Integer,
85 },
86 ArgSepSyntax::Exactly(ArgSep::Long),
87 ),
88 SingularArgSyntax::RequiredValue(
89 RequiredValueSyntax {
90 name: Cow::Borrowed("y"),
91 vtype: ExprType::Integer,
92 },
93 ArgSepSyntax::Exactly(ArgSep::Long),
94 ),
95 SingularArgSyntax::RequiredValue(
96 RequiredValueSyntax {
97 name: Cow::Borrowed("r"),
98 vtype: ExprType::Integer,
99 },
100 ArgSepSyntax::End,
101 ),
102 ],
103 None,
104 )])
105 .with_category(CATEGORY)
106 .with_description(
107 "Draws a circle of radius r centered at (x,y).
108The outline of the circle is drawn using the foreground color as selected by COLOR and the \
109area of the circle is left untouched.",
110 )
111 .build(),
112 console,
113 })
114 }
115}
116
117impl Callable for GfxCircleCommand {
118 fn metadata(&self) -> Rc<CallableMetadata> {
119 self.metadata.clone()
120 }
121
122 fn exec(&self, scope: Scope<'_>) -> CallResult<()> {
123 debug_assert_eq!(3, scope.nargs());
124 let xy = parse_coordinates(&scope, 0, 1)?;
125 let r = parse_radius(&scope, 2)?;
126
127 self.console.borrow_mut().draw_circle(xy, r)?;
128 Ok(())
129 }
130}
131
132pub struct GfxCirclefCommand {
134 metadata: Rc<CallableMetadata>,
135 console: Rc<RefCell<dyn Console>>,
136}
137
138impl GfxCirclefCommand {
139 pub fn new(console: Rc<RefCell<dyn Console>>) -> Rc<Self> {
141 Rc::from(Self {
142 metadata: CallableMetadataBuilder::new("GFX_CIRCLEF")
143 .with_syntax(&[(
144 &[
145 SingularArgSyntax::RequiredValue(
146 RequiredValueSyntax {
147 name: Cow::Borrowed("x"),
148 vtype: ExprType::Integer,
149 },
150 ArgSepSyntax::Exactly(ArgSep::Long),
151 ),
152 SingularArgSyntax::RequiredValue(
153 RequiredValueSyntax {
154 name: Cow::Borrowed("y"),
155 vtype: ExprType::Integer,
156 },
157 ArgSepSyntax::Exactly(ArgSep::Long),
158 ),
159 SingularArgSyntax::RequiredValue(
160 RequiredValueSyntax {
161 name: Cow::Borrowed("r"),
162 vtype: ExprType::Integer,
163 },
164 ArgSepSyntax::End,
165 ),
166 ],
167 None,
168 )])
169 .with_category(CATEGORY)
170 .with_description(
171 "Draws a filled circle of radius r centered at (x,y).
172The outline and area of the circle are drawn using the foreground color as selected by COLOR.",
173 )
174 .build(),
175 console,
176 })
177 }
178}
179
180impl Callable for GfxCirclefCommand {
181 fn metadata(&self) -> Rc<CallableMetadata> {
182 self.metadata.clone()
183 }
184
185 fn exec(&self, scope: Scope<'_>) -> CallResult<()> {
186 debug_assert_eq!(3, scope.nargs());
187 let xy = parse_coordinates(&scope, 0, 1)?;
188 let r = parse_radius(&scope, 2)?;
189
190 self.console.borrow_mut().draw_circle_filled(xy, r)?;
191 Ok(())
192 }
193}
194
195pub struct GfxHeightFunction {
197 metadata: Rc<CallableMetadata>,
198 console: Rc<RefCell<dyn Console>>,
199}
200
201impl GfxHeightFunction {
202 pub fn new(console: Rc<RefCell<dyn Console>>) -> Rc<Self> {
204 Rc::from(Self {
205 metadata: CallableMetadataBuilder::new("GFX_HEIGHT")
206 .with_return_type(ExprType::Integer)
207 .with_syntax(&[(&[], None)])
208 .with_category(CATEGORY)
209 .with_description(
210 "Returns the height in pixels of the graphical console.
211See GFX_WIDTH to query the other dimension.",
212 )
213 .build(),
214 console,
215 })
216 }
217}
218
219impl Callable for GfxHeightFunction {
220 fn metadata(&self) -> Rc<CallableMetadata> {
221 self.metadata.clone()
222 }
223
224 fn exec(&self, scope: Scope<'_>) -> CallResult<()> {
225 debug_assert_eq!(0, scope.nargs());
226 let size = self.console.borrow().size_pixels()?;
227 scope.return_integer(i32::from(size.height))
228 }
229}
230
231pub struct GfxLineCommand {
233 metadata: Rc<CallableMetadata>,
234 console: Rc<RefCell<dyn Console>>,
235}
236
237impl GfxLineCommand {
238 pub fn new(console: Rc<RefCell<dyn Console>>) -> Rc<Self> {
240 Rc::from(Self {
241 metadata: CallableMetadataBuilder::new("GFX_LINE")
242 .with_syntax(&[(
243 &[
244 SingularArgSyntax::RequiredValue(
245 RequiredValueSyntax {
246 name: Cow::Borrowed("x1"),
247 vtype: ExprType::Integer,
248 },
249 ArgSepSyntax::Exactly(ArgSep::Long),
250 ),
251 SingularArgSyntax::RequiredValue(
252 RequiredValueSyntax {
253 name: Cow::Borrowed("y1"),
254 vtype: ExprType::Integer,
255 },
256 ArgSepSyntax::Exactly(ArgSep::Long),
257 ),
258 SingularArgSyntax::RequiredValue(
259 RequiredValueSyntax {
260 name: Cow::Borrowed("x2"),
261 vtype: ExprType::Integer,
262 },
263 ArgSepSyntax::Exactly(ArgSep::Long),
264 ),
265 SingularArgSyntax::RequiredValue(
266 RequiredValueSyntax {
267 name: Cow::Borrowed("y2"),
268 vtype: ExprType::Integer,
269 },
270 ArgSepSyntax::End,
271 ),
272 ],
273 None,
274 )])
275 .with_category(CATEGORY)
276 .with_description(
277 "Draws a line from (x1,y1) to (x2,y2).
278The line is drawn using the foreground color as selected by COLOR.",
279 )
280 .build(),
281 console,
282 })
283 }
284}
285
286impl Callable for GfxLineCommand {
287 fn metadata(&self) -> Rc<CallableMetadata> {
288 self.metadata.clone()
289 }
290
291 fn exec(&self, scope: Scope<'_>) -> CallResult<()> {
292 debug_assert_eq!(4, scope.nargs());
293 let x1y1 = parse_coordinates(&scope, 0, 1)?;
294 let x2y2 = parse_coordinates(&scope, 2, 3)?;
295
296 self.console.borrow_mut().draw_line(x1y1, x2y2)?;
297 Ok(())
298 }
299}
300
301pub struct GfxPixelCommand {
303 metadata: Rc<CallableMetadata>,
304 console: Rc<RefCell<dyn Console>>,
305}
306
307impl GfxPixelCommand {
308 pub fn new(console: Rc<RefCell<dyn Console>>) -> Rc<Self> {
310 Rc::from(Self {
311 metadata: CallableMetadataBuilder::new("GFX_PIXEL")
312 .with_syntax(&[(
313 &[
314 SingularArgSyntax::RequiredValue(
315 RequiredValueSyntax {
316 name: Cow::Borrowed("x"),
317 vtype: ExprType::Integer,
318 },
319 ArgSepSyntax::Exactly(ArgSep::Long),
320 ),
321 SingularArgSyntax::RequiredValue(
322 RequiredValueSyntax {
323 name: Cow::Borrowed("y"),
324 vtype: ExprType::Integer,
325 },
326 ArgSepSyntax::End,
327 ),
328 ],
329 None,
330 )])
331 .with_category(CATEGORY)
332 .with_description(
333 "Draws a pixel at (x,y).
334The pixel is drawn using the foreground color as selected by COLOR.",
335 )
336 .build(),
337 console,
338 })
339 }
340}
341
342impl Callable for GfxPixelCommand {
343 fn metadata(&self) -> Rc<CallableMetadata> {
344 self.metadata.clone()
345 }
346
347 fn exec(&self, scope: Scope<'_>) -> CallResult<()> {
348 debug_assert_eq!(2, scope.nargs());
349 let xy = parse_coordinates(&scope, 0, 1)?;
350
351 self.console.borrow_mut().draw_pixel(xy)?;
352 Ok(())
353 }
354}
355
356pub struct GfxRectCommand {
358 metadata: Rc<CallableMetadata>,
359 console: Rc<RefCell<dyn Console>>,
360}
361
362impl GfxRectCommand {
363 pub fn new(console: Rc<RefCell<dyn Console>>) -> Rc<Self> {
365 Rc::from(Self {
366 metadata: CallableMetadataBuilder::new("GFX_RECT")
367 .with_syntax(&[(
368 &[
369 SingularArgSyntax::RequiredValue(
370 RequiredValueSyntax {
371 name: Cow::Borrowed("x1"),
372 vtype: ExprType::Integer,
373 },
374 ArgSepSyntax::Exactly(ArgSep::Long),
375 ),
376 SingularArgSyntax::RequiredValue(
377 RequiredValueSyntax {
378 name: Cow::Borrowed("y1"),
379 vtype: ExprType::Integer,
380 },
381 ArgSepSyntax::Exactly(ArgSep::Long),
382 ),
383 SingularArgSyntax::RequiredValue(
384 RequiredValueSyntax {
385 name: Cow::Borrowed("x2"),
386 vtype: ExprType::Integer,
387 },
388 ArgSepSyntax::Exactly(ArgSep::Long),
389 ),
390 SingularArgSyntax::RequiredValue(
391 RequiredValueSyntax {
392 name: Cow::Borrowed("y2"),
393 vtype: ExprType::Integer,
394 },
395 ArgSepSyntax::End,
396 ),
397 ],
398 None,
399 )])
400 .with_category(CATEGORY)
401 .with_description(
402 "Draws a rectangle from (x1,y1) to (x2,y2).
403The outline of the rectangle is drawn using the foreground color as selected by COLOR and the \
404area of the rectangle is left untouched.",
405 )
406 .build(),
407 console,
408 })
409 }
410}
411
412impl Callable for GfxRectCommand {
413 fn metadata(&self) -> Rc<CallableMetadata> {
414 self.metadata.clone()
415 }
416
417 fn exec(&self, scope: Scope<'_>) -> CallResult<()> {
418 debug_assert_eq!(4, scope.nargs());
419 let x1y1 = parse_coordinates(&scope, 0, 1)?;
420 let x2y2 = parse_coordinates(&scope, 2, 3)?;
421
422 self.console.borrow_mut().draw_rect(x1y1, x2y2)?;
423 Ok(())
424 }
425}
426
427pub struct GfxRectfCommand {
429 metadata: Rc<CallableMetadata>,
430 console: Rc<RefCell<dyn Console>>,
431}
432
433impl GfxRectfCommand {
434 pub fn new(console: Rc<RefCell<dyn Console>>) -> Rc<Self> {
436 Rc::from(Self {
437 metadata: CallableMetadataBuilder::new("GFX_RECTF")
438 .with_syntax(&[(
439 &[
440 SingularArgSyntax::RequiredValue(
441 RequiredValueSyntax {
442 name: Cow::Borrowed("x1"),
443 vtype: ExprType::Integer,
444 },
445 ArgSepSyntax::Exactly(ArgSep::Long),
446 ),
447 SingularArgSyntax::RequiredValue(
448 RequiredValueSyntax {
449 name: Cow::Borrowed("y1"),
450 vtype: ExprType::Integer,
451 },
452 ArgSepSyntax::Exactly(ArgSep::Long),
453 ),
454 SingularArgSyntax::RequiredValue(
455 RequiredValueSyntax {
456 name: Cow::Borrowed("x2"),
457 vtype: ExprType::Integer,
458 },
459 ArgSepSyntax::Exactly(ArgSep::Long),
460 ),
461 SingularArgSyntax::RequiredValue(
462 RequiredValueSyntax {
463 name: Cow::Borrowed("y2"),
464 vtype: ExprType::Integer,
465 },
466 ArgSepSyntax::End,
467 ),
468 ],
469 None,
470 )])
471 .with_category(CATEGORY)
472 .with_description(
473 "Draws a filled rectangle from (x1,y1) to (x2,y2).
474The outline and area of the rectangle are drawn using the foreground color as selected by COLOR.",
475 )
476 .build(),
477 console,
478 })
479 }
480}
481
482impl Callable for GfxRectfCommand {
483 fn metadata(&self) -> Rc<CallableMetadata> {
484 self.metadata.clone()
485 }
486
487 fn exec(&self, scope: Scope<'_>) -> CallResult<()> {
488 debug_assert_eq!(4, scope.nargs());
489 let x1y1 = parse_coordinates(&scope, 0, 1)?;
490 let x2y2 = parse_coordinates(&scope, 2, 3)?;
491
492 self.console.borrow_mut().draw_rect_filled(x1y1, x2y2)?;
493 Ok(())
494 }
495}
496
497pub struct GfxSyncCommand {
499 metadata: Rc<CallableMetadata>,
500 console: Rc<RefCell<dyn Console>>,
501}
502
503impl GfxSyncCommand {
504 pub fn new(console: Rc<RefCell<dyn Console>>) -> Rc<Self> {
506 Rc::from(Self {
507 metadata: CallableMetadataBuilder::new("GFX_SYNC")
508 .with_syntax(&[
509 (&[], None),
510 (
511 &[SingularArgSyntax::RequiredValue(
512 RequiredValueSyntax {
513 name: Cow::Borrowed("enabled"),
514 vtype: ExprType::Boolean,
515 },
516 ArgSepSyntax::End,
517 )],
518 None,
519 ),
520 ])
521 .with_category(CATEGORY)
522 .with_description(
523 "Controls the video syncing flag and/or forces a sync.
524With no arguments, this command triggers a video sync without updating the video syncing flag. \
525When enabled? is specified, this updates the video syncing flag accordingly and triggers a video \
526sync if enabled? is TRUE.
527When video syncing is enabled, all console commands immediately refresh the console. This is \
528useful to see the effects of the commands right away, which is why this is the default mode in the \
529interpreter. However, this is a *very* inefficient way of drawing.
530When video syncing is disabled, all console updates are buffered until video syncing is enabled \
531again. This is perfect to draw complex graphics efficiently. If this is what you want to do, \
532you should disable syncing first, render a frame, call GFX_SYNC to flush the frame, repeat until \
533you are done, and then enable video syncing again. Note that the textual cursor is not visible \
534when video syncing is disabled.
535WARNING: Be aware that if you disable video syncing in the interactive interpreter, you will not \
536be able to see what you are typing any longer until you reenable video syncing.",
537 )
538 .build(),
539 console,
540 })
541 }
542}
543
544impl Callable for GfxSyncCommand {
545 fn metadata(&self) -> Rc<CallableMetadata> {
546 self.metadata.clone()
547 }
548
549 fn exec(&self, scope: Scope<'_>) -> CallResult<()> {
550 if scope.nargs() == 0 {
551 self.console.borrow_mut().sync_now()?;
552 Ok(())
553 } else {
554 debug_assert_eq!(1, scope.nargs());
555 let enabled = scope.get_boolean(0);
556
557 let mut console = self.console.borrow_mut();
558 if enabled {
559 console.show_cursor()?;
560 } else {
561 console.hide_cursor()?;
562 }
563 console.set_sync(enabled)?;
564 Ok(())
565 }
566 }
567}
568
569pub struct GfxWidthFunction {
571 metadata: Rc<CallableMetadata>,
572 console: Rc<RefCell<dyn Console>>,
573}
574
575impl GfxWidthFunction {
576 pub fn new(console: Rc<RefCell<dyn Console>>) -> Rc<Self> {
578 Rc::from(Self {
579 metadata: CallableMetadataBuilder::new("GFX_WIDTH")
580 .with_return_type(ExprType::Integer)
581 .with_syntax(&[(&[], None)])
582 .with_category(CATEGORY)
583 .with_description(
584 "Returns the width in pixels of the graphical console.
585See GFX_HEIGHT to query the other dimension.",
586 )
587 .build(),
588 console,
589 })
590 }
591}
592
593impl Callable for GfxWidthFunction {
594 fn metadata(&self) -> Rc<CallableMetadata> {
595 self.metadata.clone()
596 }
597
598 fn exec(&self, scope: Scope<'_>) -> CallResult<()> {
599 debug_assert_eq!(0, scope.nargs());
600 let size = self.console.borrow().size_pixels()?;
601 scope.return_integer(i32::from(size.width))
602 }
603}
604
605pub fn add_all(machine: &mut MachineBuilder, console: Rc<RefCell<dyn Console>>) {
607 machine.add_callable(GfxCircleCommand::new(console.clone()));
608 machine.add_callable(GfxCirclefCommand::new(console.clone()));
609 machine.add_callable(GfxHeightFunction::new(console.clone()));
610 machine.add_callable(GfxLineCommand::new(console.clone()));
611 machine.add_callable(GfxPixelCommand::new(console.clone()));
612 machine.add_callable(GfxRectCommand::new(console.clone()));
613 machine.add_callable(GfxRectfCommand::new(console.clone()));
614 machine.add_callable(GfxSyncCommand::new(console.clone()));
615 machine.add_callable(GfxWidthFunction::new(console));
616}
617
618#[cfg(test)]
619mod tests {
620 use super::*;
621 use crate::console::SizeInPixels;
622 use crate::testutils::*;
623
624 fn check_errors_two_xy(name: &'static str) {
626 for args in &["1, 2, , 4", "1, 2, 3", "1, 2, 3, 4, 5", "2; 3, 4"] {
627 check_stmt_compilation_err(
628 format!("1:1: {} expected x1%, y1%, x2%, y2%", name),
629 &format!("{} {}", name, args),
630 );
631 }
632
633 for args in &["-40000, 1, 1, 1", "1, -40000, 1, 1", "1, 1, -40000, 1", "1, 1, 1, -40000"] {
634 let pos = name.len() + 1 + args.find('-').unwrap() + 1;
635 check_stmt_err(
636 format!("1:{}: Coordinate -40000 out of range", pos),
637 &format!("{} {}", name, args),
638 );
639 }
640
641 for args in &["40000, 1, 1, 1", "1, 40000, 1, 1", "1, 1, 40000, 1", "1, 1, 1, 40000"] {
642 let pos = name.len() + 1 + args.find('4').unwrap() + 1;
643 check_stmt_err(
644 format!("1:{}: Coordinate 40000 out of range", pos),
645 &format!("{} {}", name, args),
646 );
647 }
648
649 for args in &["\"a\", 1, 1, 1", "1, \"a\", 1, 1", "1, 1, \"a\", 1", "1, 1, 1, \"a\""] {
650 let stmt = &format!("{} {}", name, args);
651 let pos = stmt.find('"').unwrap() + 1;
652 check_stmt_compilation_err(format!("1:{}: STRING is not a number", pos), stmt);
653 }
654 }
655
656 fn check_errors_xy_radius(name: &'static str) {
658 for args in &["1, , 3", "1, 2", "1, 2, 3, 4"] {
659 check_stmt_compilation_err(
660 format!("1:1: {} expected x%, y%, r%", name),
661 &format!("{} {}", name, args),
662 );
663 }
664 check_stmt_compilation_err(
665 format!("1:{}: {} expected x%, y%, r%", name.len() + 3, name),
666 &format!("{} 2; 3, 4", name),
667 );
668
669 for args in &["-40000, 1, 1", "1, -40000, 1"] {
670 let pos = name.len() + 1 + args.find('-').unwrap() + 1;
671 check_stmt_err(
672 format!("1:{}: Coordinate -40000 out of range", pos),
673 &format!("{} {}", name, args),
674 );
675 }
676 check_stmt_err(
677 format!("1:{}: Radius -40000 must be positive", name.len() + 8),
678 &format!("{} 1, 1, -40000", name),
679 );
680
681 for args in &["40000, 1, 1", "1, 40000, 1"] {
682 let pos = name.len() + 1 + args.find('4').unwrap() + 1;
683 check_stmt_err(
684 format!("1:{}: Coordinate 40000 out of range", pos),
685 &format!("{} {}", name, args),
686 );
687 }
688 check_stmt_err(
689 format!("1:{}: Radius 80000 out of range", name.len() + 8),
690 &format!("{} 1, 1, 80000", name),
691 );
692
693 for args in &["\"a\", 1, 1", "1, \"a\", 1", "1, 1, \"a\""] {
694 let stmt = &format!("{} {}", name, args);
695 let pos = stmt.find('"').unwrap() + 1;
696 check_stmt_compilation_err(format!("1:{}: STRING is not a number", pos), stmt);
697 }
698
699 check_stmt_err(
700 format!("1:{}: Radius -1 must be positive", name.len() + 8),
701 &format!("{} 1, 1, -1", name),
702 );
703 }
704
705 #[test]
706 fn test_gfx_circle_ok() {
707 Tester::default()
708 .run("GFX_CIRCLE 0, 0, 0")
709 .expect_output([CapturedOut::DrawCircle(PixelsXY { x: 0, y: 0 }, 0)])
710 .check();
711
712 Tester::default()
713 .run("GFX_CIRCLE 1.1, 2.3, 2.5")
714 .expect_output([CapturedOut::DrawCircle(PixelsXY { x: 1, y: 2 }, 3)])
715 .check();
716
717 Tester::default()
718 .run("GFX_CIRCLE -31000, -32000, 31000")
719 .expect_output([CapturedOut::DrawCircle(PixelsXY { x: -31000, y: -32000 }, 31000)])
720 .check();
721 }
722
723 #[test]
724 fn test_gfx_circle_errors() {
725 check_errors_xy_radius("GFX_CIRCLE");
726 }
727
728 #[test]
729 fn test_gfx_circlef_ok() {
730 Tester::default()
731 .run("GFX_CIRCLEF 0, 0, 0")
732 .expect_output([CapturedOut::DrawCircleFilled(PixelsXY { x: 0, y: 0 }, 0)])
733 .check();
734
735 Tester::default()
736 .run("GFX_CIRCLEF 1.1, 2.3, 2.5")
737 .expect_output([CapturedOut::DrawCircleFilled(PixelsXY { x: 1, y: 2 }, 3)])
738 .check();
739
740 Tester::default()
741 .run("GFX_CIRCLEF -31000, -32000, 31000")
742 .expect_output([CapturedOut::DrawCircleFilled(
743 PixelsXY { x: -31000, y: -32000 },
744 31000,
745 )])
746 .check();
747 }
748
749 #[test]
750 fn test_gfx_circlef_errors() {
751 check_errors_xy_radius("GFX_CIRCLEF");
752 }
753
754 #[test]
755 fn test_gfx_height() {
756 let mut t = Tester::default();
757 t.get_console().borrow_mut().set_size_pixels(SizeInPixels::new(1, 768));
758 t.run("result = GFX_HEIGHT").expect_var("result", 768i32).check();
759
760 check_expr_error("1:10: Graphical console size not yet set", "GFX_HEIGHT");
761
762 check_expr_compilation_error("1:10: GFX_HEIGHT expected no arguments", "GFX_HEIGHT()");
763 check_expr_compilation_error("1:10: GFX_HEIGHT expected no arguments", "GFX_HEIGHT(1)");
764 }
765
766 #[test]
767 fn test_gfx_line_ok() {
768 Tester::default()
769 .run("GFX_LINE 1, 2, 3, 4")
770 .expect_output([CapturedOut::DrawLine(
771 PixelsXY { x: 1, y: 2 },
772 PixelsXY { x: 3, y: 4 },
773 )])
774 .check();
775
776 Tester::default()
777 .run("GFX_LINE -31000.3, -32000.2, 31000.4, 31999.8")
778 .expect_output([CapturedOut::DrawLine(
779 PixelsXY { x: -31000, y: -32000 },
780 PixelsXY { x: 31000, y: 32000 },
781 )])
782 .check();
783 }
784
785 #[test]
786 fn test_gfx_line_errors() {
787 check_errors_two_xy("GFX_LINE");
788 }
789
790 #[test]
791 fn test_gfx_pixel_ok() {
792 Tester::default()
793 .run("GFX_PIXEL 1, 2")
794 .expect_output([CapturedOut::DrawPixel(PixelsXY { x: 1, y: 2 })])
795 .check();
796
797 Tester::default()
798 .run("GFX_PIXEL -31000, -32000")
799 .expect_output([CapturedOut::DrawPixel(PixelsXY { x: -31000, y: -32000 })])
800 .check();
801
802 Tester::default()
803 .run("GFX_PIXEL 30999.5, 31999.7")
804 .expect_output([CapturedOut::DrawPixel(PixelsXY { x: 31000, y: 32000 })])
805 .check();
806 }
807
808 #[test]
809 fn test_gfx_pixel_errors() {
810 for cmd in &["GFX_PIXEL , 2", "GFX_PIXEL 1, 2, 3", "GFX_PIXEL 1"] {
811 check_stmt_compilation_err("1:1: GFX_PIXEL expected x%, y%", cmd);
812 }
813 check_stmt_compilation_err("1:12: GFX_PIXEL expected x%, y%", "GFX_PIXEL 1; 2");
814
815 for cmd in &["GFX_PIXEL -40000, 1", "GFX_PIXEL 1, -40000"] {
816 check_stmt_err(
817 format!("1:{}: Coordinate -40000 out of range", cmd.find('-').unwrap() + 1),
818 cmd,
819 );
820 }
821
822 for cmd in &["GFX_PIXEL \"a\", 1", "GFX_PIXEL 1, \"a\""] {
823 let pos = cmd.find('"').unwrap() + 1;
824 check_stmt_compilation_err(format!("1:{}: STRING is not a number", pos), cmd);
825 }
826 }
827
828 #[test]
829 fn test_gfx_rect_ok() {
830 Tester::default()
831 .run("GFX_RECT 1.1, 2.3, 2.5, 3.9")
832 .expect_output([CapturedOut::DrawRect(
833 PixelsXY { x: 1, y: 2 },
834 PixelsXY { x: 3, y: 4 },
835 )])
836 .check();
837
838 Tester::default()
839 .run("GFX_RECT -31000, -32000, 31000, 32000")
840 .expect_output([CapturedOut::DrawRect(
841 PixelsXY { x: -31000, y: -32000 },
842 PixelsXY { x: 31000, y: 32000 },
843 )])
844 .check();
845 }
846
847 #[test]
848 fn test_gfx_rect_errors() {
849 check_errors_two_xy("GFX_RECT");
850 }
851
852 #[test]
853 fn test_gfx_rectf_ok() {
854 Tester::default()
855 .run("GFX_RECTF 1.1, 2.3, 2.5, 3.9")
856 .expect_output([CapturedOut::DrawRectFilled(
857 PixelsXY { x: 1, y: 2 },
858 PixelsXY { x: 3, y: 4 },
859 )])
860 .check();
861
862 Tester::default()
863 .run("GFX_RECTF -31000, -32000, 31000, 32000")
864 .expect_output([CapturedOut::DrawRectFilled(
865 PixelsXY { x: -31000, y: -32000 },
866 PixelsXY { x: 31000, y: 32000 },
867 )])
868 .check();
869 }
870
871 #[test]
872 fn test_gfx_rectf_errors() {
873 check_errors_two_xy("GFX_RECTF");
874 }
875
876 #[test]
877 fn test_gfx_sync_ok() {
878 Tester::default().run("GFX_SYNC").expect_output([CapturedOut::SyncNow]).check();
879 Tester::default()
880 .run("GFX_SYNC TRUE")
881 .expect_output([CapturedOut::ShowCursor, CapturedOut::SetSync(true)])
882 .check();
883 Tester::default()
884 .run("GFX_SYNC FALSE")
885 .expect_output([CapturedOut::HideCursor, CapturedOut::SetSync(false)])
886 .check();
887 }
888
889 #[test]
890 fn test_gfx_sync_errors() {
891 check_stmt_compilation_err("1:1: GFX_SYNC expected <> | <enabled?>", "GFX_SYNC 2, 3");
892 check_stmt_compilation_err("1:10: Expected BOOLEAN but found INTEGER", "GFX_SYNC 2");
893 }
894
895 #[test]
896 fn test_gfx_width() {
897 let mut t = Tester::default();
898 t.get_console().borrow_mut().set_size_pixels(SizeInPixels::new(12345, 1));
899 t.run("result = GFX_WIDTH").expect_var("result", 12345i32).check();
900
901 check_expr_error("1:10: Graphical console size not yet set", "GFX_WIDTH");
902
903 check_expr_compilation_error("1:10: GFX_WIDTH expected no arguments", "GFX_WIDTH()");
904 check_expr_compilation_error("1:10: GFX_WIDTH expected no arguments", "GFX_WIDTH(1)");
905 }
906}