vga_framebuffer/
lib.rs

1//! VGA Frame Buffer for Embedded Microcontrollers
2//!
3//! Generates an 800 x 600 @ 60 Hz SVGA signal from a 48 column x 36 row
4//! monochrome text buffer. The image has a border.
5//!
6//! TODO: Implement smooth scrolling in the vertical direction with an extra
7//! text row.
8//!
9//!
10//! Width = 400 double width pixels => 400 = 8 + (48 x 8) + 8
11//!
12//! Height = 600 pixels => 600 = 12 + (36 x 16) + 12
13//!
14//! ```ignore
15//! <-------------- 400 px, pixel doubled to 800 px ------------->
16//! +------------------------------------------------------------+
17//! |<--> 8 pixel border     ^                8 pixel border <-->|
18//! |                        | 12 px border                      |
19//! |                        v                                   |
20//! |    +--------------------------------------------------+    |
21//! |    | <--^------ 48 chars x 8 px = 384  px ----------->|    |
22//! |    |    |                                             |    |
23//! |    |    |                                             |    |
24//! |    |    | 36 rows x 16 px = 576 px                    |    |
25//! |    |    |                                             |    |
26//! |    |    |                                             |    |
27//! |    |    v                                             |    |
28//! |    +--------------------------------------------------+    |
29//! |                          ^                                 |
30//! |                          | 12 px border                    |
31//! |                          v                                 |
32//! +------------------------------------------------------------+
33//! ```
34//!
35//! Requires pixels to be emitted with a 20 MHz pixel clock (against a nominal
36//! 40 MHz pixel clock, in order to acheive the horizontal doubling).
37//!
38//! In order to maintain performance, only one font size is supported: 8x16
39//! pixels. But you can substitute your own font if required (e.g. for
40//! Teletext support).
41//!
42//! There is optional cursor support. Rather than try and check each text cell
43//! at render time to see if it is in the cursor position, we swap chars in
44//! and out of the text buffer as the cursor moves. It's a little more
45//! expensive, but the cost is at text write time, not at render time (and so
46//! it won't break sync).
47//!
48//! See https://github.com/thejpster/monotron for an example.
49
50#![no_std]
51#![cfg_attr(feature = "const_fn", feature(const_fn))]
52
53// ***************************************************************************
54//
55// External Crates
56//
57// ***************************************************************************
58
59extern crate console_traits;
60#[macro_use]
61extern crate const_ft;
62
63// ***************************************************************************
64//
65// Sub-modules
66//
67// ***************************************************************************
68
69mod charset;
70pub mod freebsd_cp850;
71pub mod freebsd_teletext;
72mod maps;
73
74// ***************************************************************************
75//
76// Imports
77//
78// ***************************************************************************
79
80pub use charset::*;
81pub use console_traits::*;
82use core::sync::atomic::{AtomicUsize, Ordering};
83use maps::RGB_MAPS;
84
85// ***************************************************************************
86//
87// Public Constants
88//
89// ***************************************************************************
90
91/// Number of lines in frame buffer
92pub const MODE0_USABLE_LINES: usize = 576;
93/// Number of columns in frame buffer
94pub const MODE0_USABLE_COLS: usize = 384;
95/// How many words in a line (including the border)
96pub const MODE0_HORIZONTAL_OCTETS: usize = 50;
97/// How many words in a line (excluding the border)
98pub const MODE0_USABLE_HORIZONTAL_OCTETS: usize = 48;
99/// How many characters in a row
100pub const MODE0_TEXT_NUM_COLS: usize = MODE0_USABLE_COLS / MAX_FONT_WIDTH;
101/// Highest X co-ord for text
102pub const MODE0_TEXT_MAX_COL: usize = MODE0_TEXT_NUM_COLS - 1;
103/// How many rows of characters on the screen
104pub const MODE0_TEXT_NUM_ROWS: usize = MODE0_USABLE_LINES / MAX_FONT_HEIGHT;
105/// Highest Y co-ord for text
106pub const MODE0_TEXT_MAX_ROW: usize = MODE0_TEXT_NUM_ROWS - 1;
107
108/// Number of pixels in a scan-line in Mode 2
109pub const MODE2_WIDTH_PIXELS: usize = 384;
110/// Number of scan-lines in an image in Mode 2. Note, we print each one twice.
111pub const MODE2_USABLE_LINES: usize = 288;
112
113// ***************************************************************************
114//
115// Private Constants
116//
117// ***************************************************************************
118
119// See http://tinyvga.com/vga-timing/800x600@60Hz
120// These values have been adjusted to assume a 20 MHz pixel clock
121const H_VISIBLE_AREA_20MHZ: u32 = 400;
122const H_FRONT_PORCH_20MHZ: u32 = 20;
123const H_SYNC_PULSE_20MHZ: u32 = 64;
124const H_BACK_PORCH_20MHZ: u32 = 44;
125const H_WHOLE_LINE_20MHZ: u32 =
126    H_VISIBLE_AREA_20MHZ + H_FRONT_PORCH_20MHZ + H_SYNC_PULSE_20MHZ + H_BACK_PORCH_20MHZ;
127// These values have been adjusted to assume a 40 MHz pixel clock
128// const H_VISIBLE_AREA_40MHZ: u32 = 800;
129// const H_FRONT_PORCH_40MHZ: u32 = 40;
130// const H_SYNC_PULSE_40MHZ: u32 = 128;
131// const H_BACK_PORCH_40MHZ: u32 = 88;
132// const H_WHOLE_LINE_40MHZ: u32 =
133//     H_VISIBLE_AREA_40MHZ + H_FRONT_PORCH_40MHZ + H_SYNC_PULSE_40MHZ + H_BACK_PORCH_40MHZ;
134const V_VISIBLE_AREA: usize = 600;
135const V_FRONT_PORCH: usize = 1;
136const V_SYNC_PULSE: usize = 4;
137const V_BACK_PORCH: usize = 23;
138const V_WHOLE_FRAME: usize = V_SYNC_PULSE + V_BACK_PORCH + V_VISIBLE_AREA + V_FRONT_PORCH;
139const V_TOP_BORDER: usize = 12;
140const V_BOTTOM_BORDER: usize = 12;
141
142const MAX_FONT_HEIGHT: usize = 16;
143const MAX_FONT_WIDTH: usize = 8;
144const V_SYNC_PULSE_FIRST: usize = 0;
145const V_BACK_PORCH_FIRST: usize = V_SYNC_PULSE_FIRST + V_SYNC_PULSE;
146const V_TOP_BORDER_FIRST: usize = V_BACK_PORCH_FIRST + V_BACK_PORCH;
147const V_TOP_BORDER_LAST: usize = V_DATA_FIRST - 1;
148const V_DATA_FIRST: usize = V_TOP_BORDER_FIRST + V_TOP_BORDER;
149const V_DATA_LAST: usize = V_BOTTOM_BORDER_FIRST - 1;
150const V_BOTTOM_BORDER_FIRST: usize = V_DATA_FIRST + (MAX_FONT_HEIGHT * MODE0_TEXT_NUM_ROWS);
151const V_BOTTOM_BORDER_LAST: usize = V_FRONT_PORCH_FIRST - 1;
152const V_FRONT_PORCH_FIRST: usize = V_BOTTOM_BORDER_FIRST + V_BOTTOM_BORDER;
153
154// White on Blue
155const DEFAULT_ATTR: Attr = Attr::new(Colour::White, Colour::Blue);
156
157const CURSOR: Char = Char::LowLine;
158
159// ***************************************************************************
160//
161// Public Traits
162//
163// ***************************************************************************
164
165/// Implement this on your microcontroller's timer object.
166pub trait Hardware {
167    /// Called at start-up to configure timer.
168    ///
169    /// The timer must be periodic, with period `width`, which is measured
170    /// clock ticks (or VGA pixels), assuming the given clock rate. If you
171    /// have a clock that runs at half the given rate, then double the given
172    /// values.
173    ///
174    /// You will receive calls to `write_pixels` as pixels are generated. Do
175    /// not emit any pixels until the `line_start` timer elapses (store them
176    /// in a FIFO).
177    ///
178    /// The H-Sync pin must rise at the start of the loop and fall after
179    /// `sync_end` clock ticks. We don't control it here because that would
180    /// add too much latency - you must change the H-Sync GPIO pin early in
181    /// the ISR yourself.
182    ///
183    /// V-Sync is controlled by the current line number; you should implement
184    /// `vsync_on` and `vsync_off` which this code will call at the
185    /// appropriate time.
186    fn configure(&mut self, mode_info: &ModeInfo);
187
188    /// Called when V-Sync needs to be high.
189    fn vsync_on(&mut self);
190
191    /// Called when V-Sync needs to be low.
192    fn vsync_off(&mut self);
193
194    /// Called word by word as pixels are calculated
195    fn write_pixels(&mut self, xrgb: XRGBColour);
196}
197
198// ***************************************************************************
199//
200// Private Traits
201//
202// ***************************************************************************
203
204/// Describes the parameters for a particular video mode.
205trait VideoMode {
206    /// How many octets wide is a given scan-line.
207    fn octets(&self) -> usize;
208    /// How many pixels per second are in the video stream?
209    fn clock_speed(&self) -> u32;
210}
211
212// ***************************************************************************
213//
214// Public Types
215//
216// ***************************************************************************
217
218/// Describes a video mode.
219#[derive(Debug)]
220pub struct ModeInfo {
221    /// Number of pixels in a line (including blanking)
222    pub width: u32,
223    /// Number of pixels in a line (excluding blanking)
224    pub visible_width: u32,
225    /// Elapsed time (in `clock_rate` pixels) before H-Sync needs to fall
226    pub sync_end: u32,
227    /// Elapsed time (in `clock_rate` pixels) before line_start ISR needs to
228    /// fire
229    pub line_start: u32,
230    /// The pixel clock rate in Hz (e.g. 40_000_000 for 40 MHz)
231    pub clock_rate: u32,
232    /// Number of lines on the screen (including blanking)
233    pub num_lines: u32,
234    /// Number of lines on the screen (excluding blanking)
235    pub visible_lines: u32,
236}
237
238/// This structure represents the framebuffer - a 2D array of monochome pixels.
239///
240/// The framebuffer is stored as an array of horizontal lines, where each line
241/// is comprised of 8 bit words. This suits our timing needs as although the
242/// SPI peripheral on an LM4F120 which can emit 16 bits at a time, 8 proves
243/// easier to work with.
244#[repr(C)]
245pub struct FrameBuffer<T>
246where
247    T: Hardware,
248{
249    line_no: AtomicUsize,
250    frame: usize,
251    // Add one extra row because 600 doesn't divide by 16
252    text_buffer: [Mode0TextRow; MODE0_TEXT_NUM_ROWS + 1],
253    // Allows us to map any visible line to any other visible line.
254    roller_buffer: [u16; MODE0_USABLE_LINES],
255    hw: Option<T>,
256    attr: Attr,
257    pos: Position,
258    mode: ControlCharMode,
259    escape_mode: EscapeCharMode,
260    mode2: Option<Mode2>,
261    font: Option<*const u8>,
262    cursor_visible: bool,
263    under_cursor: Char,
264}
265
266/// This structure describes the attributes for a Char.
267/// They're all packed into 8 bits to save RAM.
268#[derive(Copy, Clone, Eq, PartialEq, Debug)]
269pub struct Attr(u8);
270
271#[derive(Copy, Clone, Eq, PartialEq, Debug)]
272pub enum Colour {
273    White = 7,
274    Yellow = 6,
275    Magenta = 5,
276    Red = 4,
277    Cyan = 3,
278    Green = 2,
279    Blue = 1,
280    Black = 0,
281}
282
283/// Represents 8 pixels, each of which can be any 3-bit RGB colour
284#[derive(Debug, Copy, Clone)]
285pub struct XRGBColour(pub u32);
286
287/// Represents Mode2 1-bpp graphics
288pub struct Mode2 {
289    buffer: *const u8,
290    start: usize,
291    end: usize,
292}
293
294/// A point on the screen.
295/// The arguments are X (column), Y (row)
296#[derive(Copy, Clone, Eq, PartialEq, Debug)]
297pub struct Point(pub usize, pub usize);
298
299/// You can set this on a row to make the text double-height. This was common
300/// on the BBC Micro in Mode 7/Teletext mode.
301#[derive(Copy, Clone, Eq, PartialEq, Debug)]
302pub enum DoubleHeightMode {
303    Normal,
304    Top,
305    Bottom,
306}
307
308#[derive(Copy, Clone)]
309pub struct Mode0TextRow {
310    pub double_height: DoubleHeightMode,
311    pub glyphs: [(Char, Attr); MODE0_TEXT_NUM_COLS],
312}
313
314// ***************************************************************************
315//
316// Private Types
317//
318// ***************************************************************************
319
320// None
321
322// ***************************************************************************
323//
324// Impl for Public Types
325//
326// ***************************************************************************
327
328impl<T> FrameBuffer<T>
329where
330    T: Hardware,
331{
332    /// Create a new FrameBuffer.
333    const_ft! {
334        // We can't use `pub const` as const-fn isn't supported with generics.
335        pub fn new() -> FrameBuffer<T> {
336            FrameBuffer {
337                line_no: AtomicUsize::new(0),
338                frame: 0,
339                text_buffer: [Mode0TextRow {
340                    double_height: DoubleHeightMode::Normal,
341                    glyphs: [(Char::Null, DEFAULT_ATTR); MODE0_TEXT_NUM_COLS],
342                }; MODE0_TEXT_NUM_ROWS + 1],
343                roller_buffer: [0; MODE0_USABLE_LINES],
344                hw: None,
345                pos: Position {
346                    row: Row(0),
347                    col: Col(0),
348                },
349                attr: DEFAULT_ATTR,
350                mode: ControlCharMode::Interpret,
351                escape_mode: EscapeCharMode::Waiting,
352                mode2: None,
353                font: None,
354                cursor_visible: true,
355                under_cursor: Char::Space,
356            }
357        }
358    }
359
360    /// Initialise the hardware (by calling the `configure` callback).
361    pub fn init(&mut self, mut hw: T) {
362        // This all assumes an 8-pixel font (i.e. 1 byte or two per u16)
363        assert_eq!(MAX_FONT_WIDTH, 8);
364        let mode_info = ModeInfo {
365            /// Number of pixels in a line (including blanking)
366            width: H_WHOLE_LINE_20MHZ,
367            /// Number of pixels in a line (excluding blanking)
368            visible_width: H_VISIBLE_AREA_20MHZ,
369            /// Elapsed time (in `clock_rate` pixels) before H-Sync needs to
370            /// fall
371            sync_end: H_SYNC_PULSE_20MHZ,
372            /// Elapsed time (in `clock_rate` pixels) before line_start ISR
373            /// needs to fire
374            line_start: H_SYNC_PULSE_20MHZ + H_BACK_PORCH_20MHZ,
375            /// The pixel clock rate in Hz (e.g. 40_000_000 for 40 MHz)
376            clock_rate: 20_000_000,
377            /// Number of lines on the screen (including blanking)
378            num_lines: V_WHOLE_FRAME as u32,
379            /// Number of lines on the screen (excluding blanking)
380            visible_lines: V_VISIBLE_AREA as u32,
381        };
382
383        hw.configure(&mode_info);
384        self.hw = Some(hw);
385        for (idx, line) in self.roller_buffer.iter_mut().enumerate() {
386            *line = idx as u16;
387        }
388        self.clear();
389    }
390
391    pub fn borrow_hw_mut(&mut self) -> Option<&mut T> {
392        if let Some(x) = &mut self.hw {
393            Some(x)
394        } else {
395            None
396        }
397    }
398
399    pub fn borrow_hw(&self) -> Option<&T> {
400        if let Some(x) = &self.hw {
401            Some(x)
402        } else {
403            None
404        }
405    }
406
407    pub fn set_cursor_visible(&mut self, visible: bool) {
408        if visible != self.cursor_visible {
409            if visible {
410                self.cursor_visible = true;
411                self.under_cursor = self.current_cell().0;
412                self.current_cell().0 = CURSOR;
413            } else {
414                self.cursor_visible = false;
415                self.current_cell().0 = self.under_cursor;
416            }
417        }
418    }
419
420    /// Enable mode2 - a 1-bit-per-pixel graphical buffer which is coloured
421    /// according to the colour attributes for the matching text cells.
422    /// Supply a u8 slice that is some multiple of MODE0_USABLE_HORIZONTAL_OCTETS long.
423    /// The buffer will be line-doubled and so can be up to 288 lines long.
424    pub fn mode2(&mut self, buffer: &[u8], start_line: usize) {
425        let length = buffer.len();
426        let buffer_lines = length / MODE0_USABLE_HORIZONTAL_OCTETS;
427        let mode2 = Mode2 {
428            buffer: buffer.as_ptr(),
429            start: start_line,
430            // Framebuffer is line-doubled
431            end: start_line + (2 * buffer_lines),
432        };
433        self.mode2 = Some(mode2);
434    }
435
436    pub fn mode2_shift(&mut self, new_start_line: usize) {
437        if let Some(mode2) = self.mode2.as_mut() {
438            mode2.start = new_start_line;
439        }
440    }
441
442    /// Releases the memory for mode2. The rendering code may keep
443    /// reading this memory buffer up until the end of the frame.
444    pub fn mode2_release(&mut self) {
445        let mut mode2_opt = None;
446        core::mem::swap(&mut self.mode2, &mut mode2_opt);
447    }
448
449    pub fn map_line(&mut self, visible_line: u16, rendered_line: u16) {
450        if (rendered_line as usize) < MODE0_USABLE_LINES {
451            if let Some(n) = self.roller_buffer.get_mut(visible_line as usize) {
452                *n = rendered_line;
453            }
454        }
455    }
456
457    /// Returns the current frame number.
458    pub fn frame(&self) -> usize {
459        self.frame
460    }
461
462    /// Returns the current visible line number or None in the blanking period.
463    pub fn line(&self) -> Option<usize> {
464        let line = self.line_no.load(Ordering::Relaxed);
465        if line >= V_DATA_FIRST && line <= V_DATA_LAST {
466            Some(line - V_DATA_FIRST)
467        } else {
468            None
469        }
470    }
471
472    /// Returns the number of lines since startup.
473    pub fn total_line(&self) -> u64 {
474        let line_a = self.line_no.load(Ordering::Relaxed);
475        let mut f = self.frame;
476        let line_b = self.line_no.load(Ordering::Relaxed);
477        if line_b < line_a {
478            // We wrapped - read new frame
479            f = self.frame;
480        }
481        ((f as u64) * (V_WHOLE_FRAME as u64)) + (line_b as u64)
482    }
483
484    /// Call this at the start of every line.
485    pub fn isr_sol(&mut self) {
486        match self.line_no.load(Ordering::Relaxed) {
487            V_BOTTOM_BORDER_FIRST..=V_BOTTOM_BORDER_LAST => {
488                self.solid_line();
489            }
490            V_TOP_BORDER_FIRST..=V_TOP_BORDER_LAST => {
491                self.solid_line();
492            }
493            V_DATA_FIRST..=V_DATA_LAST => {
494                self.calculate_pixels();
495            }
496            V_BACK_PORCH_FIRST => {
497                if let Some(ref mut hw) = self.hw {
498                    hw.vsync_off();
499                }
500            }
501            V_FRONT_PORCH_FIRST => {
502                // End of visible frame - increment counter
503                self.frame = self.frame.wrapping_add(1);
504            }
505            V_WHOLE_FRAME => {
506                // Wrap around
507                self.line_no.store(0, Ordering::Relaxed);
508                if let Some(ref mut hw) = self.hw {
509                    hw.vsync_on();
510                }
511            }
512            _ => {
513                // No output on this line
514            }
515        }
516        self.line_no.fetch_add(1, Ordering::Relaxed);
517    }
518
519    /// Calculate a solid line of pixels for the border.
520    /// @todo allow the border colour/pattern to be set
521    fn solid_line(&mut self) {
522        if let Some(ref mut hw) = self.hw {
523            // Middle bit
524            for _ in 0..MODE0_HORIZONTAL_OCTETS {
525                hw.write_pixels(XRGBColour::new(0xFF, 0xFF, 0xFF));
526            }
527        }
528    }
529
530    /// Calculate the pixels for the given video line.
531    ///
532    /// Converts each glyph into 8 pixels, then pushes them out as RGB
533    /// triplets to the callback function (to be buffered).
534    fn calculate_pixels(&mut self) {
535        let real_line = self.line_no.load(Ordering::Relaxed) - V_DATA_FIRST;
536        let line = self.roller_buffer[real_line as usize] as usize;
537        let text_row = line / MAX_FONT_HEIGHT;
538        let row = &self.text_buffer[text_row];
539        let font_row = match row.double_height {
540            DoubleHeightMode::Normal => line % MAX_FONT_HEIGHT,
541            DoubleHeightMode::Top => (line % MAX_FONT_HEIGHT) / 2,
542            DoubleHeightMode::Bottom => ((line % MAX_FONT_HEIGHT) + MAX_FONT_HEIGHT) / 2,
543        };
544        let font_table = self
545            .font
546            .unwrap_or_else(|| freebsd_cp850::FONT_DATA.as_ptr());
547        if let Some(ref mut hw) = self.hw {
548            // Left border
549            hw.write_pixels(XRGBColour::new(0xFF, 0xFF, 0xFF));
550
551            let mut need_text = true;
552            if let Some(mode2) = self.mode2.as_ref() {
553                if line >= mode2.start && line < mode2.end && text_row < MODE0_TEXT_NUM_ROWS {
554                    // Pixels in the middle
555
556                    // Our framebuffer is line-doubled
557                    let framebuffer_line = (line - mode2.start) >> 1;
558
559                    // Find the block of bytes for this scan-line
560                    let start = framebuffer_line * MODE0_USABLE_HORIZONTAL_OCTETS;
561                    let framebuffer_offsets = (start as isize)
562                        ..(start as isize + MODE0_USABLE_HORIZONTAL_OCTETS as isize);
563
564                    // Write out the bytes with colour from the text-buffer
565                    for ((_, attr), framebuffer_offset) in self.text_buffer[text_row]
566                        .glyphs
567                        .iter()
568                        .zip(framebuffer_offsets)
569                    {
570                        let w = unsafe { *mode2.buffer.offset(framebuffer_offset) };
571                        // RGB_MAPs is a lookup of (pixels, fg, bg) -> (r,g,b)
572                        // Each row is 4 bytes. The row index is
573                        // 0bFFFBBBPPPPPPPP, where F = foreground, B =
574                        // background, P = 8-bit pixels.
575                        let rgb_addr = unsafe {
576                            RGB_MAPS
577                                .as_ptr()
578                                .offset(((attr.0 as isize) * 256_isize) + (w as isize))
579                        };
580                        let rgb_word = unsafe { *rgb_addr };
581                        hw.write_pixels(rgb_word);
582                    }
583                    need_text = false;
584                }
585            }
586
587            if need_text {
588                // Characters in the middle
589                let font_table = unsafe { font_table.add(font_row) };
590                for (ch, attr) in row.glyphs.iter() {
591                    let index = (*ch as isize) * (MAX_FONT_HEIGHT as isize);
592                    let mono_pixels = unsafe { *font_table.offset(index) };
593                    // RGB_MAPs is a lookup of (pixels, fg, bg) -> (r,g,b)
594                    // Each row is 4 bytes. The row index is
595                    // 0bFFFBBBPPPPPPPP, where F = foreground, B =
596                    // background, P = 8-bit pixels.
597                    let rgb_addr = unsafe {
598                        RGB_MAPS
599                            .as_ptr()
600                            .offset(((attr.0 as isize) * 256_isize) + (mono_pixels as isize))
601                    };
602                    let rgb_word = unsafe { *rgb_addr };
603                    hw.write_pixels(rgb_word);
604                }
605            }
606
607            // Right border
608            hw.write_pixels(XRGBColour::new(0xFF, 0xFF, 0xFF));
609        }
610    }
611
612    /// Change the current font
613    pub fn set_custom_font(&mut self, new_font: Option<&'static [u8]>) {
614        self.font = match new_font {
615            // The given font
616            Some(x) => {
617                assert_eq!(x.len(), 256 * MAX_FONT_HEIGHT);
618                Some(x.as_ptr())
619            }
620            // The default font
621            None => None,
622        };
623    }
624
625    /// Clears the screen and resets the cursor to 0,0.
626    pub fn clear(&mut self) {
627        for row in self.text_buffer.iter_mut() {
628            for slot in row.glyphs.iter_mut() {
629                *slot = (Char::Space, self.attr);
630            }
631            row.double_height = DoubleHeightMode::Normal;
632        }
633        self.pos = Position::origin();
634    }
635
636    /// Puts a glyph on screen at the specified place
637    pub fn write_glyph_at(&mut self, glyph: Char, pos: Position, attr: Option<Attr>) {
638        if self.cursor_visible && (pos.row == self.pos.row) && (pos.col == self.pos.col) {
639            self.under_cursor = glyph;
640            self.text_buffer[pos.row.0 as usize].glyphs[pos.col.0 as usize].1 =
641                attr.unwrap_or(self.attr);
642        } else if (pos.col <= self.get_width()) && (pos.row <= self.get_height()) {
643            self.text_buffer[pos.row.0 as usize].glyphs[pos.col.0 as usize] =
644                (glyph, attr.unwrap_or(self.attr));
645        }
646    }
647
648    /// Read a glyph on screen at the specified place
649    pub fn read_glyph_at(&mut self, pos: Position) -> Option<(Char, Attr)> {
650        if self.cursor_visible && (pos.row == self.pos.row) && (pos.col == self.pos.col) {
651            Some((
652                self.under_cursor,
653                self.text_buffer[pos.row.0 as usize].glyphs[pos.col.0 as usize].1,
654            ))
655        } else if (pos.col <= self.get_width()) && (pos.row <= self.get_height()) {
656            Some(self.text_buffer[pos.row.0 as usize].glyphs[pos.col.0 as usize])
657        } else {
658            None
659        }
660    }
661
662    /// Puts a glyph on screen at the current position.
663    pub fn write_glyph(&mut self, glyph: Char, attr: Option<Attr>) {
664        if self.cursor_visible {
665            self.under_cursor = glyph;
666            self.current_cell().1 = attr.unwrap_or(self.attr);
667        } else {
668            *self.current_cell() = (glyph, attr.unwrap_or(self.attr));
669        }
670        self.move_cursor_right().unwrap();
671    }
672
673    /// Write a single Unicode char to the screen at the current position.
674    pub fn write_char(&mut self, ch: u8, attr: Option<Attr>) {
675        self.write_glyph(Char::from_byte(ch), attr);
676    }
677
678    /// Changes the attribute for a given position, leaving the glyph unchanged.
679    pub fn set_attr_at(&mut self, pos: Position, attr: Attr) {
680        self.text_buffer[pos.row.0 as usize].glyphs[pos.col.0 as usize].1 = attr;
681    }
682
683    /// Change font height for a given line.
684    pub fn set_line_mode_at(&mut self, row: Row, double_height: DoubleHeightMode) {
685        self.text_buffer[row.0 as usize].double_height = double_height;
686    }
687
688    /// Change font height for the current line.
689    pub fn set_line_mode(&mut self, double_height: DoubleHeightMode) {
690        self.text_buffer[self.pos.row.0 as usize].double_height = double_height;
691    }
692
693    /// Change the current character attribute
694    pub fn set_attr(&mut self, attr: Attr) -> Attr {
695        let old = self.attr;
696        self.attr = attr;
697        old
698    }
699
700    /// Get the current character attribute
701    pub fn get_attr(&mut self) -> Attr {
702        self.attr
703    }
704
705    fn current_cell(&mut self) -> &mut (Char, Attr) {
706        &mut self.text_buffer[self.pos.row.0 as usize].glyphs[self.pos.col.0 as usize]
707    }
708}
709
710impl<T> BaseConsole for FrameBuffer<T>
711where
712    T: Hardware,
713{
714    type Error = ();
715
716    /// Gets the last col on the screen.
717    fn get_width(&self) -> Col {
718        Col(MODE0_TEXT_MAX_COL as u8)
719    }
720
721    /// Gets the last row on the screen.
722    fn get_height(&self) -> Row {
723        Row(MODE0_TEXT_MAX_ROW as u8)
724    }
725
726    /// Set the horizontal position for the next text output.
727    fn set_col(&mut self, col: Col) -> Result<(), Self::Error> {
728        if col <= self.get_width() {
729            if self.cursor_visible {
730                self.current_cell().0 = self.under_cursor;
731                self.pos.col = col;
732                self.under_cursor = self.current_cell().0;
733                self.current_cell().0 = CURSOR;
734            } else {
735                self.pos.col = col;
736            }
737            Ok(())
738        } else {
739            Err(())
740        }
741    }
742
743    /// Set the vertical position for the next text output.
744    fn set_row(&mut self, row: Row) -> Result<(), Self::Error> {
745        if row <= self.get_height() {
746            if self.cursor_visible {
747                self.current_cell().0 = self.under_cursor;
748                self.pos.row = row;
749                self.under_cursor = self.current_cell().0;
750                self.current_cell().0 = CURSOR;
751            } else {
752                self.pos.row = row;
753            }
754            Ok(())
755        } else {
756            Err(())
757        }
758    }
759
760    /// Set the horizontal and vertical position for the next text output.
761    fn set_pos(&mut self, pos: Position) -> Result<(), Self::Error> {
762        if pos.row <= self.get_height() && pos.col <= self.get_width() {
763            if self.cursor_visible {
764                self.current_cell().0 = self.under_cursor;
765                self.pos = pos;
766                self.under_cursor = self.current_cell().0;
767                self.current_cell().0 = CURSOR;
768            } else {
769                self.pos = pos;
770            }
771            Ok(())
772        } else {
773            Err(())
774        }
775    }
776
777    /// Get the current screen position.
778    fn get_pos(&self) -> Position {
779        self.pos
780    }
781
782    /// Set the control char mode
783    fn set_control_char_mode(&mut self, mode: ControlCharMode) {
784        self.mode = mode;
785    }
786
787    /// Get the current control char mode
788    fn get_control_char_mode(&self) -> ControlCharMode {
789        self.mode
790    }
791
792    /// Set the escape char mode
793    fn set_escape_char_mode(&mut self, mode: EscapeCharMode) {
794        self.escape_mode = mode;
795    }
796
797    /// Get the current escape char mode
798    fn get_escape_char_mode(&self) -> EscapeCharMode {
799        self.escape_mode
800    }
801
802    /// Called when the screen needs to scroll up one row.
803    fn scroll_screen(&mut self) -> Result<(), Self::Error> {
804        let old_cursor = self.cursor_visible;
805        self.set_cursor_visible(false);
806        for line in 0..MODE0_TEXT_NUM_ROWS - 1 {
807            self.text_buffer[line] = self.text_buffer[line + 1];
808        }
809        for slot in self.text_buffer[MODE0_TEXT_MAX_ROW].glyphs.iter_mut() {
810            *slot = (Char::Space, self.attr);
811        }
812        self.set_cursor_visible(old_cursor);
813        Ok(())
814    }
815}
816
817impl<T> AsciiConsole for FrameBuffer<T>
818where
819    T: Hardware,
820{
821    /// Handle an escape char.
822    /// We take a, b, c, d, e, f, g, h as being a background colour and A..H as being a foreground colour.
823    /// 'Z' means clear the screen.
824    fn handle_escape(&mut self, escaped_char: u8) -> bool {
825        match escaped_char {
826            b'W' => {
827                self.attr.set_fg(Colour::White);
828            }
829            b'Y' => {
830                self.attr.set_fg(Colour::Yellow);
831            }
832            b'M' => {
833                self.attr.set_fg(Colour::Magenta);
834            }
835            b'R' => {
836                self.attr.set_fg(Colour::Red);
837            }
838            b'C' => {
839                self.attr.set_fg(Colour::Cyan);
840            }
841            b'G' => {
842                self.attr.set_fg(Colour::Green);
843            }
844            b'B' => {
845                self.attr.set_fg(Colour::Blue);
846            }
847            b'K' => {
848                self.attr.set_fg(Colour::Black);
849            }
850            b'w' => {
851                self.attr.set_bg(Colour::White);
852            }
853            b'y' => {
854                self.attr.set_bg(Colour::Yellow);
855            }
856            b'm' => {
857                self.attr.set_bg(Colour::Magenta);
858            }
859            b'r' => {
860                self.attr.set_bg(Colour::Red);
861            }
862            b'c' => {
863                self.attr.set_bg(Colour::Cyan);
864            }
865            b'g' => {
866                self.attr.set_bg(Colour::Green);
867            }
868            b'b' => {
869                self.attr.set_bg(Colour::Blue);
870            }
871            b'k' => {
872                self.attr.set_bg(Colour::Black);
873            }
874            b'^' => {
875                self.set_line_mode(DoubleHeightMode::Top);
876            }
877            b'v' => {
878                self.set_line_mode(DoubleHeightMode::Bottom);
879            }
880            b'-' => {
881                self.set_line_mode(DoubleHeightMode::Normal);
882            }
883            b'Z' => {
884                self.clear();
885            }
886            _ => {}
887        }
888        // We only have single char sequences
889        true
890    }
891
892    /// Write a single Unicode char to the screen at the given position
893    /// without updating the current position.
894    fn write_char_at(&mut self, ch: u8, pos: Position) -> Result<(), Self::Error> {
895        if self.cursor_visible && (pos.row == self.pos.row) && (pos.col == self.pos.col) {
896            self.under_cursor = Char::from_byte(ch);
897            self.text_buffer[pos.row.0 as usize].glyphs[pos.col.0 as usize].1 = self.attr;
898        } else if (pos.col <= self.get_width()) && (pos.row <= self.get_height()) {
899            self.text_buffer[pos.row.0 as usize].glyphs[pos.col.0 as usize] =
900                (Char::from_byte(ch), self.attr);
901        }
902        Ok(())
903    }
904}
905
906impl<T> core::fmt::Write for FrameBuffer<T>
907where
908    T: Hardware,
909{
910    fn write_str(&mut self, s: &str) -> core::fmt::Result {
911        for ch in s.chars() {
912            self.write_character(Char::map_char(ch) as u8)
913                .map_err(|_| core::fmt::Error)?;
914        }
915        Ok(())
916    }
917}
918
919impl Attr {
920    const FG_BITS: u8 = 0b0011_1000;
921    const BG_BITS: u8 = 0b0000_0111;
922
923    pub const fn new(fg: Colour, bg: Colour) -> Attr {
924        Attr(((fg as u8) << 3) + (bg as u8))
925    }
926
927    pub fn set_fg(&mut self, fg: Colour) -> &mut Attr {
928        self.0 = ((fg as u8) << 3) + (self.0 & !Self::FG_BITS);
929        self
930    }
931
932    pub fn set_bg(&mut self, bg: Colour) -> &mut Attr {
933        self.0 = (self.0 & !Self::BG_BITS) + (bg as u8);
934        self
935    }
936
937    pub fn as_u8(self) -> u8 {
938        self.0
939    }
940}
941
942impl core::default::Default for Attr {
943    fn default() -> Self {
944        DEFAULT_ATTR
945    }
946}
947
948impl Colour {
949    /// Generate 8 pixels in RGB which are all this colour
950    pub fn into_pixels(self) -> XRGBColour {
951        match self {
952            Colour::White => XRGBColour::new(0xFF, 0xFF, 0xFF),
953            Colour::Yellow => XRGBColour::new(0xFF, 0xFF, 0x00),
954            Colour::Magenta => XRGBColour::new(0xFF, 0x00, 0xFF),
955            Colour::Red => XRGBColour::new(0xFF, 0x00, 0x00),
956            Colour::Cyan => XRGBColour::new(0x00, 0xFF, 0xFF),
957            Colour::Green => XRGBColour::new(0x00, 0xFF, 0x00),
958            Colour::Blue => XRGBColour::new(0x00, 0x00, 0xFF),
959            Colour::Black => XRGBColour::new(0x00, 0x00, 0x00),
960        }
961    }
962}
963
964impl XRGBColour {
965    /// Create a new block of 8 coloured pixels by mixing 8 red/black pixels,
966    /// 8 green/black pixels and 8 blue/black pixels.
967    pub const fn new(red: u8, green: u8, blue: u8) -> XRGBColour {
968        XRGBColour(((red as u32) << 16) | ((green as u32) << 8) | (blue as u32))
969    }
970
971    /// Get the 8 red/black pixels in the bottom 8 bits
972    pub const fn red(self) -> u32 {
973        (self.0 >> 16) & 0xFF
974    }
975
976    /// Get the 8 green/black pixels in the bottom 8 bits
977    pub const fn green(self) -> u32 {
978        (self.0 >> 8) & 0xFF
979    }
980
981    /// Get the 8 blue/black pixels in the bottom 8 bits
982    pub const fn blue(self) -> u32 {
983        self.0 & 0xFF
984    }
985
986    /// Pixel must be in the range 0..7, where 0 is the rightmost pixel
987    pub const fn pixel_has_red(self, pixel: u8) -> bool {
988        ((self.0 >> (16 + (7 - pixel))) & 1) == 1
989    }
990
991    /// Pixel must be in the range 0..7, where 0 is the rightmost pixel
992    pub const fn pixel_has_green(self, pixel: u8) -> bool {
993        ((self.0 >> (8 + (7 - pixel))) & 1) == 1
994    }
995
996    /// Pixel must be in the range 0..7, where 0 is the rightmost pixel
997    pub const fn pixel_has_blue(self, pixel: u8) -> bool {
998        ((self.0 >> (7 - pixel)) & 1) == 1
999    }
1000}
1001
1002// ***************************************************************************
1003//
1004// Impl for Private Types
1005//
1006// ***************************************************************************
1007
1008// ***************************************************************************
1009//
1010// Public Functions
1011//
1012// ***************************************************************************
1013
1014// ***************************************************************************
1015//
1016// Private Functions
1017//
1018// ***************************************************************************
1019
1020// ***************************************************************************
1021//
1022// End of File
1023//
1024// ***************************************************************************