// Visual keyboard + mouse demo: one square per jlKeyE, lit when its // key is held, and a small pointer drawn at the live mouse position. // Holding the left mouse button while the pointer is over a cell lights // it as if the corresponding key were down. Press ESC to quit. // // The strip under the grid is a typed-text line fed by jlInputGetChar: // whatever you type -- letters, digits, shifted punctuation -- appears // there, Backspace deletes, Return clears. It doubles as the // acceptance test for the typed-character queue ("192.168.1.10:6510" // must be typeable on every port). The bottom two scanlines // additionally encode verification data as raw pixel nibbles behind // sentinels: row 198 carries every received character (A5A5 sentinel, // 2 pixels = 1 byte) and row 199 the live mouse state (5A5A sentinel, // present/button flags plus position), so emulator harnesses can // verify the exact bytes from a framebuffer dump. // // The render loop only redraws cells whose target lit state changed // since last frame -- so on idle frames, the only work is the cursor // erase + redraw + a tiny rect-present pair. The cursor erase is // implemented by redrawing the cell that contained the *previous* // cursor position; in the gap regions between cells the cursor will // briefly leave a trail until that cell is touched again, which is an // acceptable demo-quality compromise. #include #include #include #define GRID_COLS 10 #define GRID_ROWS 6 #define CELL_W 28 #define CELL_H 26 #define GAP 4 #define MARGIN_X 2 #define MARGIN_Y 2 #define CURSOR_W 4 #define CURSOR_H 4 #define COLOR_BACKGROUND 0 #define COLOR_UNLIT 1 #define COLOR_LIT 2 #define COLOR_CURSOR 3 #define CELL_NONE ((int16_t)-1) // Typed-text line under the grid (the grid ends at y = 178 with the // 26-pixel cells above). #define FONT_W 5 #define FONT_H 7 #define TEXT_STRIP_Y 180 #define TEXT_X 4 #define TEXT_Y 182 #define TEXT_INPUT_X 68 #define MAX_LINE 40 // Verification scanline: sentinel nibbles, then a received-character // count byte, then every received character as (high nibble, low // nibble) pixel pairs. An emulator harness reads this row back from // the framebuffer to prove each typed byte arrived. #define VERIFY_Y 198 #define VERIFY_SENTINEL 0xA5A5 #define VERIFY_COUNT_X 4 #define VERIFY_CHARS_X 8 #define MAX_RECEIVED 150 // Every field on both verification rows is stamped high nibble first, // one nibble per pixel: a byte is two pixels wide, a 16-bit value four. #define VERIFY_BYTE_NIBBLES 2 #define VERIFY_WORD_NIBBLES 4 // Mouse verification scanline (the row below the typed-character one): // its own 5A5A sentinel, then present/left/right/middle as one nibble // each, then jlMouseX and jlMouseY as four nibbles each. // Re-stamped only when the reported state changes. #define VERIFY_MOUSE_Y 199 #define VERIFY_MOUSE_SENTINEL 0x5A5A #define VERIFY_MOUSE_FLAGS_X 4 #define VERIFY_MOUSE_XPOS_X 8 #define VERIFY_MOUSE_YPOS_X 12 // The flags field packed as one nibble per predicate, in the order the // row stamps them. This packing is the ONLY definition of that layout: // it is both what gets drawn and what the change check compares. #define MOUSE_FLAG_PRESENT 0x1000 #define MOUSE_FLAG_LEFT 0x0100 #define MOUSE_FLAG_RIGHT 0x0010 #define MOUSE_FLAG_MIDDLE 0x0001 static void buildPalette(jlSurfaceT *screen); static void cellAtPoint(int16_t px, int16_t py, int16_t *outCol, int16_t *outRow); static bool cellTargetLit(int16_t col, int16_t row, int16_t cursorCol, int16_t cursorRow); static void drawCell(jlSurfaceT *screen, int16_t col, int16_t row, bool lit); static void drawCursor(jlSurfaceT *screen, int16_t x, int16_t y); static void drawMouseVerifyRow(jlSurfaceT *screen); static void drawTextLine(jlSurfaceT *screen, int16_t x, int16_t y, const char *text, uint8_t color); static void drawVerifyRow(jlSurfaceT *screen); static int glyphIdx(char c); static void initialPaint(jlSurfaceT *screen); static void logTypedHistory(void); static uint16_t mouseFlags(void); static void presentChangedCells(jlSurfaceT *screen, int16_t cursorCol, int16_t cursorRow); static void processTypedChars(jlSurfaceT *screen); static void redrawTextStrip(jlSurfaceT *screen); static void stampNibbles(jlSurfaceT *screen, int16_t x, int16_t y, uint16_t value, int16_t nibbles); static void updateCursor(jlSurfaceT *screen, int16_t cursorCol, int16_t cursorRow); static void updateMouseVerifyRow(jlSurfaceT *screen); // Keys laid out row-by-row. KEY_NONE cells stay blank. Shape roughly // resembles a real keyboard (top number row, then QWERTY rows, then a // cluster of modifiers / arrows / function keys). static const jlKeyE gKeyGrid[GRID_ROWS][GRID_COLS] = { { KEY_1, KEY_2, KEY_3, KEY_4, KEY_5, KEY_6, KEY_7, KEY_8, KEY_9, KEY_0 }, { KEY_Q, KEY_W, KEY_E, KEY_R, KEY_T, KEY_Y, KEY_U, KEY_I, KEY_O, KEY_P }, { KEY_A, KEY_S, KEY_D, KEY_F, KEY_G, KEY_H, KEY_J, KEY_K, KEY_L, KEY_BACKSPACE }, { KEY_Z, KEY_X, KEY_C, KEY_V, KEY_B, KEY_N, KEY_M, KEY_LSHIFT, KEY_RSHIFT, KEY_TAB }, { KEY_SPACE, KEY_ESCAPE, KEY_RETURN, KEY_LCTRL, KEY_LALT, KEY_UP, KEY_DOWN, KEY_LEFT, KEY_RIGHT, KEY_NONE }, { KEY_F1, KEY_F2, KEY_F3, KEY_F4, KEY_F5, KEY_F6, KEY_F7, KEY_F8, KEY_F9, KEY_F10 } }; static bool gCellLit[GRID_ROWS][GRID_COLS]; static int16_t gLastCursorX = -100; static int16_t gLastCursorY = -100; static int16_t gLastCursorCol = CELL_NONE; static int16_t gLastCursorRow = CELL_NONE; // Last state stamped into the mouse verification row; updateMouseVerifyRow // only redraws when this tuple changes, so idle frames stay draw-free. static int16_t gLastMouseRowX = -1; static int16_t gLastMouseRowY = -1; static uint16_t gLastMouseRowFlags = 0xFFFF; // Typed-text line state plus the full received-character history for // the verification row. static char gLine[MAX_LINE + 1]; static int16_t gLineLen = 0; static uint8_t gReceived[MAX_RECEIVED]; static int16_t gReceivedCount = 0; // Tiny 5x7 font (high 5 bits of each row byte), borrowed from the // adventure example and extended with a few typed-punctuation glyphs. // Coverage: space, 0-9, A-Z (lowercase renders uppercase), and // . , : ! ? - ' " / ; = + _ -- everything else renders as the hollow // box at the end of the table. // // Row indices used by glyphIdx. The table below must keep this order; // the trailing row comments carry the same numbers. #define GLYPH_SPACE 0 #define GLYPH_DIGIT_BASE 1 #define GLYPH_ALPHA_BASE 11 #define GLYPH_PERIOD 37 #define GLYPH_COMMA 38 #define GLYPH_COLON 39 #define GLYPH_BANG 40 #define GLYPH_QUESTION 41 #define GLYPH_MINUS 42 #define GLYPH_APOSTROPHE 43 #define GLYPH_QUOTE 44 #define GLYPH_SLASH 45 #define GLYPH_SEMICOLON 46 #define GLYPH_EQUALS 47 #define GLYPH_PLUS 48 #define GLYPH_UNDERSCORE 49 #define GLYPH_UNKNOWN 50 static const uint8_t kFontGlyph[][FONT_H] = { { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // 0 space { 0x70, 0x88, 0x98, 0xA8, 0xC8, 0x88, 0x70 }, // 1 '0' { 0x20, 0x60, 0x20, 0x20, 0x20, 0x20, 0x70 }, // 2 '1' { 0x70, 0x88, 0x08, 0x10, 0x20, 0x40, 0xF8 }, // 3 '2' { 0x70, 0x88, 0x08, 0x30, 0x08, 0x88, 0x70 }, // 4 '3' { 0x10, 0x30, 0x50, 0x90, 0xF8, 0x10, 0x10 }, // 5 '4' { 0xF8, 0x80, 0xF0, 0x08, 0x08, 0x88, 0x70 }, // 6 '5' { 0x30, 0x40, 0x80, 0xF0, 0x88, 0x88, 0x70 }, // 7 '6' { 0xF8, 0x08, 0x10, 0x20, 0x40, 0x40, 0x40 }, // 8 '7' { 0x70, 0x88, 0x88, 0x70, 0x88, 0x88, 0x70 }, // 9 '8' { 0x70, 0x88, 0x88, 0x78, 0x08, 0x10, 0x60 }, // 10 '9' { 0x70, 0x88, 0x88, 0xF8, 0x88, 0x88, 0x88 }, // 11 'A' { 0xF0, 0x88, 0x88, 0xF0, 0x88, 0x88, 0xF0 }, // 12 'B' { 0x70, 0x88, 0x80, 0x80, 0x80, 0x88, 0x70 }, // 13 'C' { 0xF0, 0x88, 0x88, 0x88, 0x88, 0x88, 0xF0 }, // 14 'D' { 0xF8, 0x80, 0x80, 0xF0, 0x80, 0x80, 0xF8 }, // 15 'E' { 0xF8, 0x80, 0x80, 0xF0, 0x80, 0x80, 0x80 }, // 16 'F' { 0x70, 0x88, 0x80, 0xB8, 0x88, 0x88, 0x70 }, // 17 'G' { 0x88, 0x88, 0x88, 0xF8, 0x88, 0x88, 0x88 }, // 18 'H' { 0x70, 0x20, 0x20, 0x20, 0x20, 0x20, 0x70 }, // 19 'I' { 0x38, 0x10, 0x10, 0x10, 0x90, 0x90, 0x60 }, // 20 'J' { 0x88, 0x90, 0xA0, 0xC0, 0xA0, 0x90, 0x88 }, // 21 'K' { 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0xF8 }, // 22 'L' { 0x88, 0xD8, 0xA8, 0xA8, 0x88, 0x88, 0x88 }, // 23 'M' { 0x88, 0xC8, 0xA8, 0x98, 0x88, 0x88, 0x88 }, // 24 'N' { 0x70, 0x88, 0x88, 0x88, 0x88, 0x88, 0x70 }, // 25 'O' { 0xF0, 0x88, 0x88, 0xF0, 0x80, 0x80, 0x80 }, // 26 'P' { 0x70, 0x88, 0x88, 0x88, 0xA8, 0x90, 0x68 }, // 27 'Q' { 0xF0, 0x88, 0x88, 0xF0, 0xA0, 0x90, 0x88 }, // 28 'R' { 0x70, 0x88, 0x80, 0x70, 0x08, 0x88, 0x70 }, // 29 'S' { 0xF8, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20 }, // 30 'T' { 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x70 }, // 31 'U' { 0x88, 0x88, 0x88, 0x88, 0x88, 0x50, 0x20 }, // 32 'V' { 0x88, 0x88, 0x88, 0xA8, 0xA8, 0xD8, 0x88 }, // 33 'W' { 0x88, 0x88, 0x50, 0x20, 0x50, 0x88, 0x88 }, // 34 'X' { 0x88, 0x88, 0x50, 0x20, 0x20, 0x20, 0x20 }, // 35 'Y' { 0xF8, 0x08, 0x10, 0x20, 0x40, 0x80, 0xF8 }, // 36 'Z' { 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x60 }, // 37 '.' { 0x00, 0x00, 0x00, 0x00, 0x60, 0x60, 0x40 }, // 38 ',' { 0x00, 0x60, 0x60, 0x00, 0x60, 0x60, 0x00 }, // 39 ':' { 0x20, 0x20, 0x20, 0x20, 0x20, 0x00, 0x20 }, // 40 '!' { 0x70, 0x88, 0x08, 0x10, 0x20, 0x00, 0x20 }, // 41 '?' { 0x00, 0x00, 0x00, 0x78, 0x00, 0x00, 0x00 }, // 42 '-' { 0x20, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00 }, // 43 '\'' { 0x50, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00 }, // 44 '"' { 0x00, 0x08, 0x10, 0x20, 0x40, 0x80, 0x00 }, // 45 '/' { 0x00, 0x60, 0x60, 0x00, 0x60, 0x60, 0x40 }, // 46 ';' { 0x00, 0x00, 0xF8, 0x00, 0xF8, 0x00, 0x00 }, // 47 '=' { 0x00, 0x20, 0x20, 0xF8, 0x20, 0x20, 0x00 }, // 48 '+' { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF8 }, // 49 '_' { 0xF8, 0x88, 0x88, 0x88, 0x88, 0x88, 0xF8 }, // 50 unknown box }; static void buildPalette(jlSurfaceT *screen) { uint16_t colors[SURFACE_COLORS_PER_PALETTE]; uint16_t i; // Every entry gets a DISTINCT color (gray ramp for the unused // ones, red for 15 since 0x0FFF is taken by the cursor) so a // screenshot-based harness can decode the verification row's // nibble pixels back to values on ports without direct // framebuffer reads. for (i = 0; i < SURFACE_COLORS_PER_PALETTE; i++) { colors[i] = (uint16_t)((i << 8) | (i << 4) | i); } colors[COLOR_BACKGROUND] = 0x0000; // black colors[COLOR_UNLIT] = 0x0333; // dark gray colors[COLOR_LIT] = 0x00F0; // bright green colors[COLOR_CURSOR] = 0x0FFF; // white colors[15] = 0x0F00; // red (0x0FFF is the cursor's) jlPaletteSet(screen, 0, colors); } static void cellAtPoint(int16_t px, int16_t py, int16_t *outCol, int16_t *outRow) { int16_t col; int16_t row; int16_t cx; int16_t cy; *outCol = CELL_NONE; *outRow = CELL_NONE; for (row = 0; row < GRID_ROWS; row++) { for (col = 0; col < GRID_COLS; col++) { cx = (int16_t)(MARGIN_X + col * (CELL_W + GAP)); cy = (int16_t)(MARGIN_Y + row * (CELL_H + GAP)); if (px >= cx && px < (cx + CELL_W) && py >= cy && py < (cy + CELL_H)) { *outCol = col; *outRow = row; return; } } } } static bool cellTargetLit(int16_t col, int16_t row, int16_t cursorCol, int16_t cursorRow) { jlKeyE key; key = gKeyGrid[row][col]; if (key == KEY_NONE) { return false; } if (jlKeyDown(key)) { return true; } if (col == cursorCol && row == cursorRow && jlMouseDown(MOUSE_BUTTON_LEFT)) { return true; } return false; } static void drawCell(jlSurfaceT *screen, int16_t col, int16_t row, bool lit) { int16_t x; int16_t y; uint8_t color; x = (int16_t)(MARGIN_X + col * (CELL_W + GAP)); y = (int16_t)(MARGIN_Y + row * (CELL_H + GAP)); color = lit ? COLOR_LIT : COLOR_UNLIT; jlFillRect(screen, x, y, CELL_W, CELL_H, color); } // The block is clipped against the verification scanlines rather than // drawn over them: emulator harnesses sample those rows between // frames, so even a stamp-then-repair within one loop iteration is a // visible corruption window to them. static void drawCursor(jlSurfaceT *screen, int16_t x, int16_t y) { int16_t h; h = CURSOR_H; if ((int16_t)(y + h) > VERIFY_Y) { h = (int16_t)(VERIFY_Y - y); } if (h <= 0) { return; } jlFillRect(screen, x, y, CURSOR_W, h, COLOR_CURSOR); } // Stamp the mouse verification scanline: sentinel, present/button // flags, then the pointer position as nibble pixels. An emulator // harness reads this row back from the framebuffer to prove mouse // motion and button state crossed the HAL (see verify-x68000-mouse.sh). static void drawMouseVerifyRow(jlSurfaceT *screen) { stampNibbles(screen, 0, VERIFY_MOUSE_Y, VERIFY_MOUSE_SENTINEL, VERIFY_WORD_NIBBLES); stampNibbles(screen, VERIFY_MOUSE_FLAGS_X, VERIFY_MOUSE_Y, mouseFlags(), VERIFY_WORD_NIBBLES); stampNibbles(screen, VERIFY_MOUSE_XPOS_X, VERIFY_MOUSE_Y, (uint16_t)jlMouseX(), VERIFY_WORD_NIBBLES); stampNibbles(screen, VERIFY_MOUSE_YPOS_X, VERIFY_MOUSE_Y, (uint16_t)jlMouseY(), VERIFY_WORD_NIBBLES); } static void drawTextLine(jlSurfaceT *screen, int16_t x, int16_t y, const char *text, uint8_t color) { int16_t cx; int16_t i; int16_t row; int16_t col; int idx; uint8_t bits; cx = x; for (i = 0; text[i] != '\0'; i++) { idx = glyphIdx(text[i]); for (row = 0; row < FONT_H; row++) { bits = kFontGlyph[idx][row]; for (col = 0; col < FONT_W; col++) { if (bits & (0x80 >> col)) { jlDrawPixel(screen, (int16_t)(cx + col), (int16_t)(y + row), color); } } } cx = (int16_t)(cx + FONT_W + 1); if (cx > SURFACE_WIDTH - FONT_W) { return; } } } // Re-stamp the verification scanline: sentinel, received count, then // every received character as two nibble pixels. Only the count and // the newest characters actually change, but the row is cheap enough // to redraw whole. static void drawVerifyRow(jlSurfaceT *screen) { int16_t i; stampNibbles(screen, 0, VERIFY_Y, VERIFY_SENTINEL, VERIFY_WORD_NIBBLES); stampNibbles(screen, VERIFY_COUNT_X, VERIFY_Y, (uint16_t)gReceivedCount, VERIFY_BYTE_NIBBLES); for (i = 0; i < gReceivedCount; i++) { stampNibbles(screen, (int16_t)(VERIFY_CHARS_X + VERIFY_BYTE_NIBBLES * i), VERIFY_Y, gReceived[i], VERIFY_BYTE_NIBBLES); } // The strip repaint that called us blanked the mouse row too, so // restore it in the same pass. drawMouseVerifyRow(screen); } static int glyphIdx(char c) { if (c == ' ') { return GLYPH_SPACE; } if (c >= '0' && c <= '9') { return GLYPH_DIGIT_BASE + (c - '0'); } if (c >= 'A' && c <= 'Z') { return GLYPH_ALPHA_BASE + (c - 'A'); } if (c >= 'a' && c <= 'z') { return GLYPH_ALPHA_BASE + (c - 'a'); } // uppercase fallback if (c == '.') { return GLYPH_PERIOD; } if (c == ',') { return GLYPH_COMMA; } if (c == ':') { return GLYPH_COLON; } if (c == '!') { return GLYPH_BANG; } if (c == '?') { return GLYPH_QUESTION; } if (c == '-') { return GLYPH_MINUS; } if (c == '\'') { return GLYPH_APOSTROPHE; } if (c == '"') { return GLYPH_QUOTE; } if (c == '/') { return GLYPH_SLASH; } if (c == ';') { return GLYPH_SEMICOLON; } if (c == '=') { return GLYPH_EQUALS; } if (c == '+') { return GLYPH_PLUS; } if (c == '_') { return GLYPH_UNDERSCORE; } return GLYPH_UNKNOWN; } static void initialPaint(jlSurfaceT *screen) { int16_t col; int16_t row; jlKeyE key; jlSurfaceClear(screen, COLOR_BACKGROUND); for (row = 0; row < GRID_ROWS; row++) { for (col = 0; col < GRID_COLS; col++) { key = gKeyGrid[row][col]; if (key == KEY_NONE) { continue; } drawCell(screen, col, row, false); gCellLit[row][col] = false; } } redrawTextStrip(screen); jlStagePresent(); } // Emit the received-character history as hex on exit so an emulator // harness can verify the typed-character path from the boot volume // when it cannot read the framebuffer (see verify-amiga-input.sh). static void logTypedHistory(void) { static const char kHex[] = "0123456789ABCDEF"; char line[2 * MAX_RECEIVED + 8]; int16_t i; int16_t n; n = 0; for (i = 0; i < gReceivedCount; i++) { line[n] = kHex[(gReceived[i] >> 4) & 0x0F]; line[n + 1] = kHex[gReceived[i] & 0x0F]; n = (int16_t)(n + 2); } line[n] = '\0'; jlLogF("typed=%s", line); jlLogFlush(); } // The mouse predicates packed one nibble per flag, in the order // drawMouseVerifyRow stamps them. Sampled once per use so the drawn // row and updateMouseVerifyRow's change check can never disagree. static uint16_t mouseFlags(void) { return (uint16_t)((jlMousePresent() ? MOUSE_FLAG_PRESENT : 0) | (jlMouseDown(MOUSE_BUTTON_LEFT) ? MOUSE_FLAG_LEFT : 0) | (jlMouseDown(MOUSE_BUTTON_RIGHT) ? MOUSE_FLAG_RIGHT : 0) | (jlMouseDown(MOUSE_BUTTON_MIDDLE) ? MOUSE_FLAG_MIDDLE : 0)); } static void presentChangedCells(jlSurfaceT *screen, int16_t cursorCol, int16_t cursorRow) { int16_t col; int16_t row; jlKeyE key; bool lit; for (row = 0; row < GRID_ROWS; row++) { for (col = 0; col < GRID_COLS; col++) { key = gKeyGrid[row][col]; if (key == KEY_NONE) { continue; } lit = cellTargetLit(col, row, cursorCol, cursorRow); if (lit == gCellLit[row][col]) { continue; } // drawCell marks the cell's rect dirty; jlStagePresent // flushes that one band. drawCell(screen, col, row, lit); jlStagePresent(); gCellLit[row][col] = lit; } } } // Pop everything the user typed since last frame, apply it to the // text line (Backspace deletes, Return clears, Escape is the quit key // and types nothing), and refresh the strip when anything changed. static void processTypedChars(jlSurfaceT *screen) { int ch; bool changed; changed = false; while ((ch = jlInputGetChar()) != -1) { if (gReceivedCount < MAX_RECEIVED) { gReceived[gReceivedCount] = (uint8_t)ch; gReceivedCount = (int16_t)(gReceivedCount + 1); } changed = true; if (ch == JL_CHAR_BACKSPACE) { if (gLineLen > 0) { gLineLen = (int16_t)(gLineLen - 1); } } else if (ch == JL_CHAR_RETURN) { gLineLen = 0; } else if (ch == JL_CHAR_ESCAPE || ch == JL_CHAR_TAB) { // Nothing to render for these. } else if (gLineLen < MAX_LINE) { gLine[gLineLen] = (char)ch; gLineLen = (int16_t)(gLineLen + 1); } } if (!changed) { return; } redrawTextStrip(screen); jlStagePresent(); } // Repaint everything below the key grid: the prompt, the current text // line, and the verification scanline. Shared by the typed-character // refresh and the cursor-erase path (which may stamp background over // this region). static void redrawTextStrip(jlSurfaceT *screen) { gLine[gLineLen] = '\0'; jlFillRect(screen, 0, TEXT_STRIP_Y, SURFACE_WIDTH, SURFACE_HEIGHT - TEXT_STRIP_Y, COLOR_BACKGROUND); drawTextLine(screen, TEXT_X, TEXT_Y, "TYPE HERE:", COLOR_UNLIT); drawTextLine(screen, TEXT_INPUT_X, TEXT_Y, gLine, COLOR_CURSOR); drawVerifyRow(screen); } // Stamp a value into consecutive pixels as nibbles, high nibble // first -- the encoding both verification rows and every emulator // harness that decodes them share. static void stampNibbles(jlSurfaceT *screen, int16_t x, int16_t y, uint16_t value, int16_t nibbles) { int16_t i; int16_t shift; for (i = 0; i < nibbles; i++) { shift = (int16_t)(4 * (nibbles - 1 - i)); jlDrawPixel(screen, (int16_t)(x + i), y, (uint8_t)((value >> shift) & 0x0F)); } } // Erase the previous cursor (by redrawing the cell that held it) and // stamp the new cursor at the current mouse position. Both rects are // presented; if the cursor stayed inside the same cell only one rect // pair is touched, so steady-state cost is small. static void updateCursor(jlSurfaceT *screen, int16_t cursorCol, int16_t cursorRow) { int16_t mouseX; int16_t mouseY; mouseX = jlMouseX(); mouseY = jlMouseY(); if (gLastCursorX != mouseX || gLastCursorY != mouseY) { if (gLastCursorCol != CELL_NONE) { drawCell(screen, gLastCursorCol, gLastCursorRow, gCellLit[gLastCursorRow][gLastCursorCol]); } else if (gLastCursorX >= 0 && gLastCursorY >= 0) { // Old cursor was in a gap region. Stamp background over it, // then repair the text strip / verify row if the stamp // reached into them. jlFillRect(screen, gLastCursorX, gLastCursorY, CURSOR_W, CURSOR_H, COLOR_BACKGROUND); if ((int16_t)(gLastCursorY + CURSOR_H) > TEXT_STRIP_Y) { redrawTextStrip(screen); } } } drawCursor(screen, mouseX, mouseY); // All draw calls above marked their rects dirty; one jlStagePresent // flushes the union (cursor erase + cursor draw). jlStagePresent(); gLastCursorX = mouseX; gLastCursorY = mouseY; gLastCursorCol = cursorCol; gLastCursorRow = cursorRow; } // Re-stamp the mouse verification row only when the reported state // changed since the last stamp, so idle frames stay draw-free. static void updateMouseVerifyRow(jlSurfaceT *screen) { int16_t x; int16_t y; uint16_t flags; x = jlMouseX(); y = jlMouseY(); flags = mouseFlags(); if (x == gLastMouseRowX && y == gLastMouseRowY && flags == gLastMouseRowFlags) { return; } drawMouseVerifyRow(screen); jlStagePresent(); gLastMouseRowX = x; gLastMouseRowY = y; gLastMouseRowFlags = flags; } int main(void) { jlConfigT config; jlSurfaceT *screen; int16_t cursorCol; int16_t cursorRow; config.codegenBytes = 8 * 1024; config.audioBytes = 64UL * 1024; if (!jlInit(&config)) { fprintf(stderr, "jlInit failed: %s\n", jlLastError()); return 1; } screen = jlStageGet(); if (screen == NULL) { fprintf(stderr, "jlStageGet returned NULL\n"); jlShutdown(); return 1; } buildPalette(screen); jlScbSetRange(screen, 0, SURFACE_HEIGHT - 1, 0); initialPaint(screen); jlInputPoll(); for (;;) { jlInputPoll(); if (jlKeyPressed(KEY_ESCAPE)) { break; } cellAtPoint(jlMouseX(), jlMouseY(), &cursorCol, &cursorRow); presentChangedCells(screen, cursorCol, cursorRow); updateCursor(screen, cursorCol, cursorRow); updateMouseVerifyRow(screen); processTypedChars(screen); } logTypedHistory(); jlShutdown(); return 0; }