# ============================================================================= # CONSOLE (FRAMEWORK FILE) VERSION 2026-04-19 # ============================================================================= # The console system prints text strings, scrolls the screen, and blinks # the cursor. Each letter is a tile that is rendered using tile_layer_a. # If your program doesn't need to display text, you could delete this file. # ----------------------------------------------------------------------------- module console var _input_queue_index: byte var _row: byte var _col: byte var _pos: pair var _color_addend: pair # (0..9) << 10 var _blink_save: pair var _blink: bool var cursor_visible: bool var scroll_off: bool var reverse: bool var matte: bool var ctrl_key: bool # --------------------------------------------------------------------------- func init(io_tilemap: io_tilemap) # Clear the input queue console::_input_queue_index <- io::input_queue_end_index # Start with white because the background is black console::_color_addend <- 1024 console::_print_char('{cls}') # Initialize the console tilemap io_tilemap.col_count <- 64 io_tilemap.row_count <- 32 io_tilemap.tile_codes_address <- to_address(kernel::console_grid) end func # --------------------------------------------------------------------------- func _blink_off() if console::_blink then console::_blink <- false kernel::console_grid[console::_pos] <- console::_blink_save end if end func # --------------------------------------------------------------------------- # *Assuming* _blink is false, this blinks on unless the cursor is not visible func _blink_restore() if console::cursor_visible then console::_blink <- true var c: pair c <- kernel::console_grid[console::_pos] console::_blink_save <- c c <- to_pair(math::bit_and(c, $3ff)) # remove the old color c <- to_pair(c + console::_color_addend) # add the new color c <- to_pair(math::bit_xor(c, $4000)) # reverse kernel::console_grid[console::_pos] <- c end if end func # --------------------------------------------------------------------------- # Specifies the color (Palix system theme) to be used when printing text. func set_color(color: int) console::_blink_off() if color < 0 or color > 15 do kernel::fail_code(20) # function was called with an invalid argument console::_color_addend <- to_pair(color * 1024) console::_blink_restore() end func # --------------------------------------------------------------------------- func print(s: string) console::_print(s, s.size) end func # --------------------------------------------------------------------------- func print_text(text: text) console::_print(text._chars, text.length) end func # --------------------------------------------------------------------------- func _print(text: char[], size: int) console::_blink_off() var i: int i <- 0 loop if i >= size do drop var c: pair c <- text[i] console::_print_char(c) i <- i + 1 end loop console::_blink_restore() end func # --------------------------------------------------------------------------- # Print a number func print_int(n: int) console::_blink_off() var c: int c <- n # To handle MIN_INT properly, the trick is to use negative math if n < 0 then console::print_char('-') else c <- -c end if # Extract digits from right-to-left, putting them into scratch_bytes[] var i: int, digit: int i <- 0 loop digit <- -(c % 10) unsafe(kernel::scratch_bytes)[i] <- to_byte(48 + digit) c <- c / 10 if c = 0 do drop i <- i + 1 end loop # Now read scratch_bytes[] in reverse, so digits are left-to-right loop console::_print_char(kernel::scratch_bytes[i]) i <- i - 1 if i < 0 do drop end loop console::_blink_restore() end func # --------------------------------------------------------------------------- # Print one character that is specified using its HASCII code. func print_char(c: pair) console::_blink_off() console::_print_char(c) console::_blink_restore() end func # --------------------------------------------------------------------------- func _print_char(c: pair) var col: byte var row: byte var pos: pair col <- console::_col row <- console::_row pos <- console::_pos if c < 0 do c <- to_pair(c + 256) if c < 32 then # Control codes if c = '{reset}' then console::scroll_off <- false console::reverse <- false console::matte <- false elsif c = '{soff}' then # scroll off console::scroll_off <- true elsif c = '{sup}' then # scroll up console::_scroll_up() elsif c = '{sdown}' then # scroll down # Offsets are double because they are pairs kernel::copy_memory_bytes( | to_address(kernel::console_grid) + 128, | to_address(kernel::console_grid), | 3456) # 64 * 27 * 2 kernel::set_memory_pairs( | to_address(kernel::console_grid), # 64 * 27 * 2 | 32, # space | 40) elsif c = '{cls}' then # clear screen kernel::set_memory_pairs( | to_address(kernel::console_grid), | ' ', | 1792) # 64 * 28 pos <- 0 col <- 0 row <- 0 elsif c = '{cll}' then # clear line pos <- to_pair(pos - col) col <- 0 kernel::set_memory_pairs( | to_address(kernel::console_grid) + pos * 2, | ' ', | 40) elsif c = '{tab}' then var tab_amount: int tab_amount <- 8 - to_byte(math::bit_and(col, 7)) # TODO: improve this col <- to_byte(col + tab_amount) pos <- to_pair(pos + tab_amount) if col >= 40 then pos <- to_pair(pos - col + 64) col <- 0 row <- to_byte(row + 1) end if elsif c = '{n}' then # newline (lf) pos <- to_pair(pos - col + 64) col <- 0 row <- to_byte(row + 1) elsif c = '{home}' then pos <- 0 col <- 0 row <- 0 elsif c = '{end}' then pos <- 1728 # 27 * 64 col <- 0 row <- 27 elsif c = '{cr}' then # carriage return pos <- to_pair(pos - col) col <- 0 elsif c = '{rev}' then console::reverse <- true elsif c = 15 then # {mat} console::matte <- true elsif c >= 16 and c <= 25 then # color codes console::_color_addend <- to_pair((c - 16) * 1024) elsif c = '{u}' then # up if row > 0 then pos <- to_pair(pos - 64) row <- to_byte(row - 1) end if elsif c = '{d}' then # down pos <- to_pair(pos + 64) row <- to_byte(row + 1) elsif c = '{l}' then # left if col > 0 then pos <- to_pair(pos - 1) col <- to_byte(col - 1) else if row > 0 then pos <- to_pair(pos - col - (64 - 39)) col <- 39 row <- to_byte(row - 1) end if end if elsif c = '{r}' then # right if col < 39 then pos <- to_pair(pos + 1) col <- to_byte(col + 1) else pos <- to_pair(pos - col + 64) col <- 0 row <- to_byte(row + 1) end if end if else # Add in the color bits var p: pair p <- to_pair(c + console::_color_addend) if console::reverse then p <- to_pair(math::bit_or(p, $4000)) end if if console::matte then p <- to_pair(math::bit_or(p, $8000)) end if kernel::console_grid[pos] <- p if col < 39 then pos <- to_pair(pos + 1) col <- to_byte(col + 1) else pos <- to_pair(pos - col + 64) col <- 0 row <- to_byte(row + 1) end if end if if row >= 28 then row <- to_byte(row - 1) pos <- to_pair(pos - 64) if not console::scroll_off then console::_scroll_up() end if end if console::_col <- col console::_row <- row console::_pos <- pos end func # --------------------------------------------------------------------------- func _scroll_up() # Offsets are double because they are pairs kernel::copy_memory_bytes( | to_address(kernel::console_grid), | to_address(kernel::console_grid) + 128, | 3456) # 64 * 27 * 2 kernel::set_memory_pairs( | to_address(kernel::console_grid) + 3456, # 64 * 27 * 2 | ' ', | 40) end func # --------------------------------------------------------------------------- # Extracts the next key from the keyboard buffer, returning its HASCII code. # If no key was pressed, then 0 is returned. func read_key(): byte var index: byte, end_index: byte var result: byte end_index <- io::input_queue_end_index index <- console::_input_queue_index result <- 0 loop if index = end_index do drop # Read at index var event: ^io_input_event event <- io::input_queue[index] index <- to_byte(index + 1) if index >= 16 do index <- 0 var event_kind: byte, event_keyid: byte event_kind <- event.kind event_keyid <- event.keyid if event_kind = 0 or event_kind = 1 then # key down or key repeat if event_keyid = 18 or event_keyid = 19 then # left/right ctrl console::ctrl_key <- true elsif event.hascii > 0 and event.hascii < 256 then result <- to_byte(event.hascii) drop end if else if event_keyid = 18 or event_keyid = 19 then # left/right ctrl console::ctrl_key <- false end if end if end loop console::_input_queue_index <- index return result end func # --------------------------------------------------------------------------- # Saves the current location of the cursor for use with goto_bookmark(). func get_bookmark(): pair return console::_pos end func # --------------------------------------------------------------------------- # Move the cursor to a bookmark that was made using get_bookmark() func goto_bookmark(bookmark: pair) if bookmark >= 1792 # 64 * 28 do kernel::fail_code(20) # function was called with an invalid argument console::_blink_off() # divide by 64 console::_row <- to_byte(math::shift_right_unsigned(bookmark, 6)) # remainder console::_col <- to_byte(math::bit_and(bookmark, 63)) console::_pos <- bookmark console::_blink_restore() end func # --------------------------------------------------------------------------- # The main loop should call this to blink the cursor. func think() var blink_counter: int blink_counter <- math::bit_and(io::frame_counter, 31) if not console::cursor_visible or blink_counter >= 16 then console::_blink_off() else if not console::_blink then console::_blink <- true console::_blink_restore() end if end if end func end module