/** * HULK Console functionality. */ module hulk.console; import hulk.fb; import hulk.kfont; import hulk.ver; import hulk.rtc; import hulk.writef; /** * Represent the HULK console. * * Coordinate (0,0) is the top-left console position. */ struct Console { /** Border color. */ private enum BORDER_COLOR = 0xFF8000u; /** Heading color. */ private enum HEADING_COLOR = 0x0066FFu; /** Console page width in text columns. */ private static __gshared size_t m_width; /** Console page height in text rows. */ private static __gshared size_t m_height; /** Current console page. */ private static __gshared size_t m_page; /** Current page cursor X position. */ private static __gshared size_t m_x; /** Current page cursor Y position. */ private static __gshared size_t m_y; /** Number of delayed newline characters. */ private static __gshared size_t m_newlines; /** Active console escape code. */ private static __gshared char m_escape_code; /** Flag to indicate the previous character was an escape character. */ private static __gshared bool m_escape; /** * Initialize the console. */ public static void initialize() { size_t fb_console_height = Fb.height - Kfont.line_height - 1; m_width = (Fb.width - 1) / 2 / Kfont.advance; m_height = fb_console_height / Kfont.line_height; } /** * Clear the console. */ public static void clear() { Fb.clear(); m_page = 0u; m_x = 0u; m_y = 0u; draw(); } /** * Write a character to the console (buffer newlines). * * @param ch Character to write. */ public static void write(char ch) { if (ch == '\n') { render_heading_end(); m_escape_code = 0u; m_newlines++; } else { dowrite(ch); } } private static void render_heading_start() { if ('0' <= m_escape_code && m_escape_code <= '9' && m_x < (m_width - 1)) { size_t heading_level = m_escape_code - ('0' - 1); size_t nx = m_x + heading_level; if (nx >= m_width) { nx = m_width - 1; } size_t hx = fb_x(m_page, m_x); size_t hy = fb_y(m_y) + Kfont.line_height / 2 - 1; size_t hwidth = (nx - m_x - 1) * Kfont.advance + Kfont.advance / 2 - 1; Fb.rect(hx, hy, hwidth, 2, HEADING_COLOR); Fb.rect(hx + hwidth, fb_y(m_y) + Kfont.line_height / 8, 2, Kfont.line_height * 3 / 4, HEADING_COLOR); m_x = nx; } } private static void render_heading_end() { if ('0' <= m_escape_code && m_escape_code <= '9' && m_x < (m_width - 1)) { size_t heading_level = m_escape_code - ('0' - 1); size_t nx = m_x + heading_level; if (nx >= m_width) { nx = m_width - 1; } size_t hwidth = (nx - m_x - 1) * Kfont.advance + Kfont.advance / 2 - 1; size_t hx = fb_x(m_page, nx) - hwidth; size_t hy = fb_y(m_y) + Kfont.line_height / 2 - 1; Fb.rect(hx, hy, hwidth, 2, HEADING_COLOR); Fb.rect(hx - 2, fb_y(m_y) + Kfont.line_height / 8, 2, Kfont.line_height * 3 / 4, HEADING_COLOR); m_x = nx; } } /** * Write a character to the console (no newline buffering). * * @param ch Character to write. */ private static void dowrite(char ch) { if (m_newlines != 0u) { size_t newlines = m_newlines; m_newlines = 0u; for (size_t i = 0u; i < newlines; i++) { dowrite('\n'); } } if (m_escape) { m_escape_code = ch; render_heading_start(); m_escape = false; return; } if (ch == '\a') { m_escape = true; return; } if (ch == '\n') { m_x = 0u; m_y++; } else { render_char(fb_x(m_page, m_x), fb_y(m_y), ch); m_x++; if (m_x == m_width) { m_x = 0u; m_y++; } } if (m_y == m_height) { if (m_page == 0u) { m_page = 1u; m_x = 0u; m_y = 0u; } else { m_y--; shift_rows(); } } } /** * Shift console rows. */ private static void shift_rows() { /* Shift up page 0 */ Fb.copy_rect(fb_x(0u, 0u), fb_y(1u), m_width * Kfont.advance, (m_height - 1u) * Kfont.line_height, fb_x(0u, 0u), fb_y(0u)); /* Copy page 1 first row to page 0 last row. */ Fb.copy_rect(fb_x(1u, 0u), fb_y(0u), m_width * Kfont.advance, Kfont.line_height, fb_x(0u, 0u), fb_y(m_height - 1u)); /* Shift up page 1 */ Fb.copy_rect(fb_x(1u, 0u), fb_y(1u), m_width * Kfont.advance, (m_height - 1u) * Kfont.line_height, fb_x(1u, 0u), fb_y(0u)); /* Erase page 1 last row. */ Fb.rect(fb_x(1u, 0u), fb_y(m_height - 1u), m_width * Kfont.advance, Kfont.line_height, 0u); } /** * Render a character. * * @param x X position. * @param y Y position. * @param ch Character to render. */ private static void render_char(size_t x, size_t y, char ch) { if (ch > 127) { ch = 0u; } const(CharInfo) * ci = &Kfont.chars[ch]; Fb.blit_alpha_bitmap(x + ci.left, y + ci.top, ci.bitmap, ci.width, ci.height); } /** * Get the framebuffer X coordinate corresponding to the console page X position. */ private static size_t fb_x(size_t page, size_t x) { return x * Kfont.advance + page * (m_width * Kfont.advance + 1); } /** * Get the framebuffer Y coordinate corresponding to the console page Y position. */ private static size_t fb_y(size_t y) { return (y + 1) * Kfont.line_height + 1; } /** * Draw console. */ private static draw() { static __gshared string header_text = "Welcome to HOS v" ~ VERSION ~ "!"; Fb.rect(0u, Kfont.line_height, Fb.width, 1, BORDER_COLOR); Fb.rect(m_width * Kfont.advance, Kfont.line_height, 1, Fb.height - Kfont.line_height, BORDER_COLOR); size_t x = 0; foreach (c; header_text) { render_char(x, 0, c); x += Kfont.advance; } update_header(); } /** * Update console header. */ public static update_header() { __gshared uint x; x = Fb.width - 8 * Kfont.advance; Fb.rect(x - 1, 0, 1, Kfont.line_height, BORDER_COLOR); Fb.rect(x, 0, Fb.width - x, Kfont.line_height, 0); Rtc.time rtc_time = Rtc.read_rtc_time(); writef(function(ubyte ch) { render_char(x, 0, ch); x += Kfont.advance; }, "%02u:%02u:%02u", rtc_time.hour, rtc_time.minute, rtc_time.second); } }