|
| 1 | +//! Code for handling a text-buffer. |
| 2 | +
|
| 3 | +// ----------------------------------------------------------------------------- |
| 4 | +// Licence Statement |
| 5 | +// ----------------------------------------------------------------------------- |
| 6 | +// Copyright (c) Jonathan 'theJPster' Pallant and the Neotron Developers, 2023 |
| 7 | +// |
| 8 | +// This program is free software: you can redistribute it and/or modify it under |
| 9 | +// the terms of the GNU General Public License as published by the Free Software |
| 10 | +// Foundation, either version 3 of the License, or (at your option) any later |
| 11 | +// version. |
| 12 | +// |
| 13 | +// This program is distributed in the hope that it will be useful, but WITHOUT |
| 14 | +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
| 15 | +// FOR A PARTICULAR PURPOSE. See the GNU General Public License for more |
| 16 | +// details. |
| 17 | +// |
| 18 | +// You should have received a copy of the GNU General Public License along with |
| 19 | +// this program. If not, see <https://www.gnu.org/licenses/>. |
| 20 | +// ----------------------------------------------------------------------------- |
| 21 | + |
| 22 | +// ----------------------------------------------------------------------------- |
| 23 | +// Sub-modules |
| 24 | +// ----------------------------------------------------------------------------- |
| 25 | + |
| 26 | +// ----------------------------------------------------------------------------- |
| 27 | +// Imports |
| 28 | +// ----------------------------------------------------------------------------- |
| 29 | + |
| 30 | +use core::sync::atomic::{AtomicPtr, AtomicU16, AtomicU8, Ordering}; |
| 31 | + |
| 32 | +use neotron_common_bios::video::{ |
| 33 | + Attr, Glyph, GlyphAttr, TextBackgroundColour, TextForegroundColour, |
| 34 | +}; |
| 35 | + |
| 36 | +// ----------------------------------------------------------------------------- |
| 37 | +// Types |
| 38 | +// ----------------------------------------------------------------------------- |
| 39 | + |
| 40 | +/// Holds some data necessary to present a text console. |
| 41 | +/// |
| 42 | +/// Used by Core 0 to control writes to a shared text-buffer. |
| 43 | +pub struct TextConsole { |
| 44 | + current_col: AtomicU16, |
| 45 | + current_row: AtomicU16, |
| 46 | + text_buffer: AtomicPtr<GlyphAttr>, |
| 47 | + current_attr: AtomicU8, |
| 48 | +} |
| 49 | + |
| 50 | +impl TextConsole { |
| 51 | + /// Create a TextConsole. |
| 52 | + /// |
| 53 | + /// Has no buffer associated with it |
| 54 | + pub const fn new() -> TextConsole { |
| 55 | + TextConsole { |
| 56 | + current_row: AtomicU16::new(0), |
| 57 | + current_col: AtomicU16::new(0), |
| 58 | + text_buffer: AtomicPtr::new(core::ptr::null_mut()), |
| 59 | + // White on Black, with the default palette |
| 60 | + current_attr: AtomicU8::new( |
| 61 | + Attr::new( |
| 62 | + TextForegroundColour::WHITE, |
| 63 | + TextBackgroundColour::BLACK, |
| 64 | + false, |
| 65 | + ) |
| 66 | + .0, |
| 67 | + ), |
| 68 | + } |
| 69 | + } |
| 70 | + |
| 71 | + /// Update the text buffer we are using. |
| 72 | + /// |
| 73 | + /// Will reset the cursor. The screen is not cleared. |
| 74 | + pub fn set_text_buffer( |
| 75 | + &self, |
| 76 | + text_buffer: &'static mut [GlyphAttr; |
| 77 | + crate::vga::MAX_TEXT_ROWS * crate::vga::MAX_TEXT_COLS], |
| 78 | + ) { |
| 79 | + self.text_buffer |
| 80 | + .store(text_buffer.as_mut_ptr(), Ordering::Relaxed) |
| 81 | + } |
| 82 | + |
| 83 | + /// Place a single Code Page 850 encoded 8-bit character on the screen. |
| 84 | + /// |
| 85 | + /// Adjusts the current row and column automatically. Also understands |
| 86 | + /// Carriage Return and New Line bytes. |
| 87 | + pub fn write_font_glyph(&self, glyph: Glyph) { |
| 88 | + // Load from global state |
| 89 | + let mut row = self.current_row.load(Ordering::Relaxed); |
| 90 | + let mut col = self.current_col.load(Ordering::Relaxed); |
| 91 | + let buffer = self.text_buffer.load(Ordering::Relaxed); |
| 92 | + |
| 93 | + if !buffer.is_null() { |
| 94 | + self.write_at(glyph, buffer, &mut row, &mut col); |
| 95 | + // Push back to global state |
| 96 | + self.current_row.store(row, Ordering::Relaxed); |
| 97 | + self.current_col.store(col, Ordering::Relaxed); |
| 98 | + } |
| 99 | + } |
| 100 | + |
| 101 | + /// Moves the text cursor to the specified row and column. |
| 102 | + /// |
| 103 | + /// If a value is out of bounds, the cursor is not moved in that axis. |
| 104 | + pub fn move_to(&self, row: u16, col: u16) { |
| 105 | + let mode = crate::vga::VIDEO_MODE.get_mode(); |
| 106 | + let num_rows = mode.text_height().unwrap_or(0); |
| 107 | + let num_cols = mode.text_width().unwrap_or(0); |
| 108 | + if row < num_rows { |
| 109 | + self.current_row.store(row, Ordering::Relaxed); |
| 110 | + } |
| 111 | + if col < num_cols { |
| 112 | + self.current_col.store(col, Ordering::Relaxed); |
| 113 | + } |
| 114 | + } |
| 115 | + |
| 116 | + /// Convert a Unicode Scalar Value to a font glyph. |
| 117 | + /// |
| 118 | + /// Zero-width and modifier Unicode Scalar Values (e.g. `U+0301 COMBINING, |
| 119 | + /// ACCENT`) are not supported. Normalise your Unicode before calling |
| 120 | + /// this function. |
| 121 | + fn map_char_to_glyph(input: char) -> Glyph { |
| 122 | + // This fixed table only works for the default font. When we support |
| 123 | + // changing font, we will need to plug-in a different table for each font. |
| 124 | + let index = match input { |
| 125 | + '\u{0000}'..='\u{007F}' => input as u8, |
| 126 | + '\u{00A0}' => 255, // NBSP |
| 127 | + '\u{00A1}' => 173, // ¡ |
| 128 | + '\u{00A2}' => 189, // ¢ |
| 129 | + '\u{00A3}' => 156, // £ |
| 130 | + '\u{00A4}' => 207, // ¤ |
| 131 | + '\u{00A5}' => 190, // ¥ |
| 132 | + '\u{00A6}' => 221, // ¦ |
| 133 | + '\u{00A7}' => 245, // § |
| 134 | + '\u{00A8}' => 249, // ¨ |
| 135 | + '\u{00A9}' => 184, // © |
| 136 | + '\u{00AA}' => 166, // ª |
| 137 | + '\u{00AB}' => 174, // « |
| 138 | + '\u{00AC}' => 170, // ¬ |
| 139 | + '\u{00AD}' => 240, // SHY |
| 140 | + '\u{00AE}' => 169, // ® |
| 141 | + '\u{00AF}' => 238, // ¯ |
| 142 | + '\u{00B0}' => 248, // ° |
| 143 | + '\u{00B1}' => 241, // ± |
| 144 | + '\u{00B2}' => 253, // ² |
| 145 | + '\u{00B3}' => 252, // ³ |
| 146 | + '\u{00B4}' => 239, // ´ |
| 147 | + '\u{00B5}' => 230, // µ |
| 148 | + '\u{00B6}' => 244, // ¶ |
| 149 | + '\u{00B7}' => 250, // · |
| 150 | + '\u{00B8}' => 247, // ¸ |
| 151 | + '\u{00B9}' => 251, // ¹ |
| 152 | + '\u{00BA}' => 167, // º |
| 153 | + '\u{00BB}' => 175, // » |
| 154 | + '\u{00BC}' => 172, // ¼ |
| 155 | + '\u{00BD}' => 171, // ½ |
| 156 | + '\u{00BE}' => 243, // ¾ |
| 157 | + '\u{00BF}' => 168, // ¿ |
| 158 | + '\u{00C0}' => 183, // À |
| 159 | + '\u{00C1}' => 181, // Á |
| 160 | + '\u{00C2}' => 182, // Â |
| 161 | + '\u{00C3}' => 199, // Ã |
| 162 | + '\u{00C4}' => 142, // Ä |
| 163 | + '\u{00C5}' => 143, // Å |
| 164 | + '\u{00C6}' => 146, // Æ |
| 165 | + '\u{00C7}' => 128, // Ç |
| 166 | + '\u{00C8}' => 212, // È |
| 167 | + '\u{00C9}' => 144, // É |
| 168 | + '\u{00CA}' => 210, // Ê |
| 169 | + '\u{00CB}' => 211, // Ë |
| 170 | + '\u{00CC}' => 222, // Ì |
| 171 | + '\u{00CD}' => 214, // Í |
| 172 | + '\u{00CE}' => 215, // Î |
| 173 | + '\u{00CF}' => 216, // Ï |
| 174 | + '\u{00D0}' => 209, // Ð |
| 175 | + '\u{00D1}' => 165, // Ñ |
| 176 | + '\u{00D2}' => 227, // Ò |
| 177 | + '\u{00D3}' => 224, // Ó |
| 178 | + '\u{00D4}' => 226, // Ô |
| 179 | + '\u{00D5}' => 229, // Õ |
| 180 | + '\u{00D6}' => 153, // Ö |
| 181 | + '\u{00D7}' => 158, // × |
| 182 | + '\u{00D8}' => 157, // Ø |
| 183 | + '\u{00D9}' => 235, // Ù |
| 184 | + '\u{00DA}' => 233, // Ú |
| 185 | + '\u{00DB}' => 234, // Û |
| 186 | + '\u{00DC}' => 154, // Ü |
| 187 | + '\u{00DD}' => 237, // Ý |
| 188 | + '\u{00DE}' => 232, // Þ |
| 189 | + '\u{00DF}' => 225, // ß |
| 190 | + '\u{00E0}' => 133, // à |
| 191 | + '\u{00E1}' => 160, // á |
| 192 | + '\u{00E2}' => 131, // â |
| 193 | + '\u{00E3}' => 198, // ã |
| 194 | + '\u{00E4}' => 132, // ä |
| 195 | + '\u{00E5}' => 134, // å |
| 196 | + '\u{00E6}' => 145, // æ |
| 197 | + '\u{00E7}' => 135, // ç |
| 198 | + '\u{00E8}' => 138, // è |
| 199 | + '\u{00E9}' => 130, // é |
| 200 | + '\u{00EA}' => 136, // ê |
| 201 | + '\u{00EB}' => 137, // ë |
| 202 | + '\u{00EC}' => 141, // ì |
| 203 | + '\u{00ED}' => 161, // í |
| 204 | + '\u{00EE}' => 140, // î |
| 205 | + '\u{00EF}' => 139, // ï |
| 206 | + '\u{00F0}' => 208, // ð |
| 207 | + '\u{00F1}' => 164, // ñ |
| 208 | + '\u{00F2}' => 149, // ò |
| 209 | + '\u{00F3}' => 162, // ó |
| 210 | + '\u{00F4}' => 147, // ô |
| 211 | + '\u{00F5}' => 228, // õ |
| 212 | + '\u{00F6}' => 148, // ö |
| 213 | + '\u{00F7}' => 246, // ÷ |
| 214 | + '\u{00F8}' => 155, // ø |
| 215 | + '\u{00F9}' => 151, // ù |
| 216 | + '\u{00FA}' => 163, // ú |
| 217 | + '\u{00FB}' => 150, // û |
| 218 | + '\u{00FC}' => 129, // ü |
| 219 | + '\u{00FD}' => 236, // ý |
| 220 | + '\u{00FE}' => 231, // þ |
| 221 | + '\u{00FF}' => 152, // ÿ |
| 222 | + '\u{0131}' => 213, // ı |
| 223 | + '\u{0192}' => 159, // ƒ |
| 224 | + '\u{2017}' => 242, // ‗ |
| 225 | + '\u{2500}' => 196, // ─ |
| 226 | + '\u{2502}' => 179, // │ |
| 227 | + '\u{250C}' => 218, // ┌ |
| 228 | + '\u{2510}' => 191, // ┐ |
| 229 | + '\u{2514}' => 192, // └ |
| 230 | + '\u{2518}' => 217, // ┘ |
| 231 | + '\u{251C}' => 195, // ├ |
| 232 | + '\u{2524}' => 180, // ┤ |
| 233 | + '\u{252C}' => 194, // ┬ |
| 234 | + '\u{2534}' => 193, // ┴ |
| 235 | + '\u{253C}' => 197, // ┼ |
| 236 | + '\u{2550}' => 205, // ═ |
| 237 | + '\u{2551}' => 186, // ║ |
| 238 | + '\u{2554}' => 201, // ╔ |
| 239 | + '\u{2557}' => 187, // ╗ |
| 240 | + '\u{255A}' => 200, // ╚ |
| 241 | + '\u{255D}' => 188, // ╝ |
| 242 | + '\u{2560}' => 204, // ╠ |
| 243 | + '\u{2563}' => 185, // ╣ |
| 244 | + '\u{2566}' => 203, // ╦ |
| 245 | + '\u{2569}' => 202, // ╩ |
| 246 | + '\u{256C}' => 206, // ╬ |
| 247 | + '\u{2580}' => 223, // ▀ |
| 248 | + '\u{2584}' => 220, // ▄ |
| 249 | + '\u{2588}' => 219, // █ |
| 250 | + '\u{2591}' => 176, // ░ |
| 251 | + '\u{2592}' => 177, // ▒ |
| 252 | + '\u{2593}' => 178, // ▓ |
| 253 | + '\u{25A0}' => 254, // ■ |
| 254 | + _ => b'?', |
| 255 | + }; |
| 256 | + Glyph(index) |
| 257 | + } |
| 258 | + |
| 259 | + /// Put a single character at a specified point on screen. |
| 260 | + /// |
| 261 | + /// The character is relative to the current font, but newline and carriage |
| 262 | + /// return will be interpreted appropriately. The given `row` and `col` are |
| 263 | + /// updated. |
| 264 | + fn write_at(&self, glyph: Glyph, buffer: *mut GlyphAttr, row: &mut u16, col: &mut u16) { |
| 265 | + let mode = crate::vga::VIDEO_MODE.get_mode(); |
| 266 | + let num_rows = mode.text_height().unwrap_or(0) as usize; |
| 267 | + let num_cols = mode.text_width().unwrap_or(0) as usize; |
| 268 | + let attr = Attr(self.current_attr.load(Ordering::Relaxed)); |
| 269 | + |
| 270 | + if glyph.0 == b'\r' { |
| 271 | + *col = 0; |
| 272 | + } else if glyph.0 == b'\n' { |
| 273 | + *col = 0; |
| 274 | + *row += 1; |
| 275 | + } else { |
| 276 | + let offset = (*col as usize) + (num_cols * (*row as usize)); |
| 277 | + // Note (safety): This is safe as we bound `col` and `row` |
| 278 | + unsafe { |
| 279 | + buffer |
| 280 | + .add(offset) |
| 281 | + .write_volatile(GlyphAttr::new(glyph, attr)) |
| 282 | + }; |
| 283 | + *col += 1; |
| 284 | + } |
| 285 | + if *col == (num_cols as u16) { |
| 286 | + *col = 0; |
| 287 | + *row += 1; |
| 288 | + } |
| 289 | + |
| 290 | + if *row == (num_rows as u16) { |
| 291 | + // Stay on last line |
| 292 | + *row = (num_rows - 1) as u16; |
| 293 | + |
| 294 | + unsafe { core::ptr::copy(buffer.add(num_cols), buffer, num_cols * (num_rows - 1)) }; |
| 295 | + |
| 296 | + for blank_col in 0..num_cols { |
| 297 | + let offset = blank_col + (num_cols * (*row as usize)); |
| 298 | + unsafe { |
| 299 | + buffer |
| 300 | + .add(offset) |
| 301 | + .write_volatile(GlyphAttr::new(Glyph(b' '), attr)) |
| 302 | + }; |
| 303 | + } |
| 304 | + } |
| 305 | + } |
| 306 | + |
| 307 | + /// Store a new attribute to be used for subsequent characters. |
| 308 | + pub fn change_attr(&self, attr: Attr) { |
| 309 | + let value = attr.0; |
| 310 | + self.current_attr.store(value, Ordering::Relaxed); |
| 311 | + } |
| 312 | +} |
| 313 | + |
| 314 | +unsafe impl Sync for TextConsole {} |
| 315 | + |
| 316 | +impl core::fmt::Write for &TextConsole { |
| 317 | + /// Allows us to call `writeln!(some_text_console, "hello")` |
| 318 | + fn write_str(&mut self, s: &str) -> core::fmt::Result { |
| 319 | + // Load from global state |
| 320 | + let mut row = self.current_row.load(Ordering::Relaxed); |
| 321 | + let mut col = self.current_col.load(Ordering::Relaxed); |
| 322 | + let buffer = self.text_buffer.load(Ordering::Relaxed); |
| 323 | + |
| 324 | + if !buffer.is_null() { |
| 325 | + for ch in s.chars() { |
| 326 | + let b = TextConsole::map_char_to_glyph(ch); |
| 327 | + self.write_at(b, buffer, &mut row, &mut col); |
| 328 | + } |
| 329 | + |
| 330 | + // Push back to global state |
| 331 | + self.current_row.store(row, Ordering::Relaxed); |
| 332 | + self.current_col.store(col, Ordering::Relaxed); |
| 333 | + } |
| 334 | + |
| 335 | + Ok(()) |
| 336 | + } |
| 337 | +} |
| 338 | + |
| 339 | +// ----------------------------------------------------------------------------- |
| 340 | +// Static and Const Data |
| 341 | +// ----------------------------------------------------------------------------- |
| 342 | + |
| 343 | +// ----------------------------------------------------------------------------- |
| 344 | +// Functions |
| 345 | +// ----------------------------------------------------------------------------- |
| 346 | + |
| 347 | +// ----------------------------------------------------------------------------- |
| 348 | +// End of file |
| 349 | +// ----------------------------------------------------------------------------- |
0 commit comments