530 lines
19 KiB
C
530 lines
19 KiB
C
// 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 scanline additionally
|
|
// encodes every received character as raw pixel nibbles (2 pixels =
|
|
// 1 byte) behind an A5A5 sentinel 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 <stdio.h>
|
|
|
|
#include <joey/joey.h>
|
|
#include <joey/debug.h>
|
|
|
|
#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_0 0xA
|
|
#define VERIFY_SENTINEL_1 0x5
|
|
#define VERIFY_COUNT_X 4
|
|
#define VERIFY_CHARS_X 8
|
|
#define MAX_RECEIVED 150
|
|
|
|
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 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 void presentChangedCells(jlSurfaceT *screen, int16_t cursorCol, int16_t cursorRow);
|
|
static void processTypedChars(jlSurfaceT *screen);
|
|
static void redrawTextStrip(jlSurfaceT *screen);
|
|
static void updateCursor(jlSurfaceT *screen, int16_t cursorCol, int16_t cursorRow);
|
|
|
|
// 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;
|
|
|
|
// 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);
|
|
}
|
|
|
|
|
|
static void drawCursor(jlSurfaceT *screen, int16_t x, int16_t y) {
|
|
jlFillRect(screen, x, y, CURSOR_W, CURSOR_H, COLOR_CURSOR);
|
|
}
|
|
|
|
|
|
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;
|
|
uint8_t ch;
|
|
|
|
jlDrawPixel(screen, 0, VERIFY_Y, VERIFY_SENTINEL_0);
|
|
jlDrawPixel(screen, 1, VERIFY_Y, VERIFY_SENTINEL_1);
|
|
jlDrawPixel(screen, 2, VERIFY_Y, VERIFY_SENTINEL_0);
|
|
jlDrawPixel(screen, 3, VERIFY_Y, VERIFY_SENTINEL_1);
|
|
jlDrawPixel(screen, VERIFY_COUNT_X, VERIFY_Y, (uint8_t)((gReceivedCount >> 4) & 0x0F));
|
|
jlDrawPixel(screen, VERIFY_COUNT_X + 1, VERIFY_Y, (uint8_t)(gReceivedCount & 0x0F));
|
|
for (i = 0; i < gReceivedCount; i++) {
|
|
ch = gReceived[i];
|
|
jlDrawPixel(screen, (int16_t)(VERIFY_CHARS_X + 2 * i), VERIFY_Y, (uint8_t)((ch >> 4) & 0x0F));
|
|
jlDrawPixel(screen, (int16_t)(VERIFY_CHARS_X + 2 * i + 1), VERIFY_Y, (uint8_t)(ch & 0x0F));
|
|
}
|
|
}
|
|
|
|
|
|
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();
|
|
}
|
|
|
|
|
|
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);
|
|
}
|
|
|
|
|
|
// 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;
|
|
}
|
|
|
|
|
|
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);
|
|
processTypedChars(screen);
|
|
}
|
|
|
|
logTypedHistory();
|
|
jlShutdown();
|
|
return 0;
|
|
}
|