Consume libakgl 0.3.0: every workaround deleted, two capabilities gained
0.3.0 closed all ten API gaps this port had filed. The four workarounds go with them: the CMake block that declared libakgl's vendored dependencies by hand, the akgl/actor.h include in three files, and the six vtable pointers assigned by hand in two more, now akgl_render_bind2d(). Two gaps were capabilities rather than inconveniences, and both are now real: The line editor takes the composed UTF-8 text the ring carries in preference to the keycode, so shifted characters, keyboard layouts, compose keys and dead keys all work. A double quote can be typed, which means a BASIC string literal can be typed -- the sharp end of the old limitation. Letters are no longer folded to upper case. SOUND's dir/min/step reach akgl_audio_sweep instead of being refused. dir 3 sweeps once rather than oscillating and TODO.md section 5 says so. A backend with no sweep still refuses the swept note and plays the held one. The adaptors now carry an AKGL_VERSION_AT_LEAST(0, 3, 0) floor, verified by temporarily demanding 0.4.0 and watching it fire. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Co-Authored-By: Andrew Kesterson <andrew@aklabs.net>
This commit is contained in:
@@ -28,7 +28,6 @@
|
||||
|
||||
#include <akerror.h>
|
||||
|
||||
#include <akgl/actor.h>
|
||||
#include <akgl/controller.h>
|
||||
#include <akgl/error.h>
|
||||
|
||||
@@ -82,6 +81,9 @@ static bool anything_drawn(SDL_Surface *shot, int x0, int y0, int w, int h)
|
||||
* Straight into SDL's own queue rather than into libakgl's ring, so the
|
||||
* frontend's event pump is the thing under test: nothing reaches the
|
||||
* interpreter unless pump_events() drained this and handed it over.
|
||||
*
|
||||
* Key-down only, with no composed text -- which is what a cursor key, a function
|
||||
* key or a bare modifier produces.
|
||||
*/
|
||||
static void push_key(int keycode)
|
||||
{
|
||||
@@ -93,13 +95,62 @@ static void push_key(int keycode)
|
||||
SDL_PushEvent(&event);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Queue a printable keystroke: the key-down *and* the text it composed to.
|
||||
*
|
||||
* Two events, because that is what a real keyboard produces and what libakgl
|
||||
* assembles a keystroke from -- SDL_EVENT_KEY_DOWN records the press and
|
||||
* SDL_EVENT_TEXT_INPUT delivers the character the layout, the shift state, a
|
||||
* compose key and a dead key between them worked out. A keycode alone cannot
|
||||
* express any of that, which is why the ring carries both.
|
||||
*
|
||||
* `text` is deliberately allowed to differ from `keycode`: that is how a shifted
|
||||
* character and a non-US layout are tested at all.
|
||||
*/
|
||||
static void push_text_key(int keycode, const char *text)
|
||||
{
|
||||
SDL_Event event;
|
||||
|
||||
push_key(keycode);
|
||||
memset(&event, 0, sizeof(event));
|
||||
event.type = SDL_EVENT_TEXT_INPUT;
|
||||
event.text.text = text;
|
||||
SDL_PushEvent(&event);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief A one-character string with static storage, for one printable byte.
|
||||
*
|
||||
* SDL_PushEvent copies the SDL_Event but *not* the string an
|
||||
* SDL_EVENT_TEXT_INPUT points at, so the text has to outlive the call that
|
||||
* queued it and must not be reused before the queue is drained. A local buffer
|
||||
* is neither: the first attempt at push_line() used one and every event in the
|
||||
* line ended up pointing at the same overwritten two bytes, which showed up as
|
||||
* a REPL that never received a line and a test that hung rather than failed.
|
||||
*/
|
||||
static const char *one_char(unsigned char c)
|
||||
{
|
||||
static char table[128][2];
|
||||
static bool built = false;
|
||||
int i = 0;
|
||||
|
||||
if ( !built ) {
|
||||
for ( i = 0; i < 128; i++ ) {
|
||||
table[i][0] = (char)i;
|
||||
table[i][1] = '\0';
|
||||
}
|
||||
built = true;
|
||||
}
|
||||
return table[c & 0x7f];
|
||||
}
|
||||
|
||||
/** @brief Queue a whole typed line, terminator included. */
|
||||
static void push_line(const char *text)
|
||||
{
|
||||
size_t i = 0;
|
||||
|
||||
for ( i = 0; text[i] != '\0'; i++ ) {
|
||||
push_key((int)(unsigned char)text[i]);
|
||||
push_text_key((int)(unsigned char)text[i], one_char((unsigned char)text[i]));
|
||||
}
|
||||
push_key('\r');
|
||||
}
|
||||
@@ -269,9 +320,9 @@ static akerr_ErrorContext AKERR_NOIGNORE *test_window_close_stops(void)
|
||||
* @brief The line editor turns keystrokes into a line, echoed as it is typed.
|
||||
*
|
||||
* Read through the sink directly rather than through the REPL, because what is
|
||||
* being asserted here is the editing itself: the fold to upper case that the
|
||||
* ring's missing modifier state forces, the backspace, and the echo landing in
|
||||
* the grid where the cursor says it is.
|
||||
* being asserted here is the editing itself: that the *composed text* is what
|
||||
* gets typed rather than the keycode, the backspace, the escape, and the echo
|
||||
* landing in the grid where the cursor says it is.
|
||||
*/
|
||||
static akerr_ErrorContext AKERR_NOIGNORE *test_line_editor(void)
|
||||
{
|
||||
@@ -282,40 +333,65 @@ static akerr_ErrorContext AKERR_NOIGNORE *test_line_editor(void)
|
||||
PASS(errctx, start_frontend(NULL));
|
||||
PASS(errctx, FRONTEND.input.flush_keys(&FRONTEND.input));
|
||||
|
||||
/* "prXnt" with the X backspaced away, typed in lower case. */
|
||||
push_key('p');
|
||||
push_key('r');
|
||||
push_key('x');
|
||||
push_key('\b');
|
||||
push_key('i');
|
||||
push_key('n');
|
||||
push_key('t');
|
||||
/* "prXnt" with the X backspaced away. Lower case survives, which it did not
|
||||
* before libakgl 0.3.0 -- the editor folded everything to upper case because
|
||||
* a keycode was all it had. */
|
||||
push_text_key('p', "p");
|
||||
push_text_key('r', "r");
|
||||
push_text_key('x', "x");
|
||||
push_key(SDLK_BACKSPACE);
|
||||
push_text_key('i', "i");
|
||||
push_text_key('n', "n");
|
||||
push_text_key('t', "t");
|
||||
push_key('\r');
|
||||
|
||||
PASS(errctx, FRONTEND.akglsink.readline(&FRONTEND.akglsink, line, sizeof(line), &eof));
|
||||
TEST_REQUIRE(!eof, "a submitted line is not end of input");
|
||||
/*
|
||||
* Upper case because akgl_controller_poll_key() reports a keycode and no
|
||||
* modifier state, so Shift is invisible and lower case is unreachable. A
|
||||
* C128 is upper case too, which is why this is the right resolution rather
|
||||
* than merely the available one. Filed upstream.
|
||||
*/
|
||||
TEST_REQUIRE_STR(line, "PRINT");
|
||||
TEST_REQUIRE_STR(line, "print");
|
||||
/* The echo is in the grid, and the backspace really removed a character. */
|
||||
TEST_REQUIRE_STR(FRONTEND.akglstate.text[0], "PRINT");
|
||||
TEST_REQUIRE_STR(FRONTEND.akglstate.text[0], "print");
|
||||
TEST_REQUIRE_INT(FRONTEND.akglstate.cursorrow, 1);
|
||||
TEST_REQUIRE_INT(FRONTEND.akglstate.cursorcol, 0);
|
||||
TEST_REQUIRE(!FRONTEND.akglstate.editing, "the editor should be idle once the line is in");
|
||||
|
||||
/* Escape abandons a line rather than submitting it. */
|
||||
push_key('z');
|
||||
push_key(0x1b);
|
||||
push_key('o');
|
||||
push_key('k');
|
||||
/*
|
||||
* A shifted character, which is the whole point of the 0.3.0 keystroke API:
|
||||
* the key is still SDLK_2 and the character is '"'. Without the composed
|
||||
* text there is no way to tell those apart, and a BASIC string literal could
|
||||
* not be typed at the window at all.
|
||||
*/
|
||||
push_text_key(SDLK_2, "\"");
|
||||
push_text_key('h', "H");
|
||||
push_text_key(SDLK_2, "\"");
|
||||
push_key('\r');
|
||||
PASS(errctx, FRONTEND.akglsink.readline(&FRONTEND.akglsink, line, sizeof(line), &eof));
|
||||
TEST_REQUIRE_STR(line, "OK");
|
||||
TEST_REQUIRE_STR(FRONTEND.akglstate.text[1], "OK");
|
||||
TEST_REQUIRE_STR(line, "\"H\"");
|
||||
|
||||
/*
|
||||
* And a key whose composed character has nothing to do with its keycode,
|
||||
* which is what a non-US layout looks like: AZERTY's SDLK_Q types "a".
|
||||
*/
|
||||
push_text_key(SDLK_Q, "a");
|
||||
push_key('\r');
|
||||
PASS(errctx, FRONTEND.akglsink.readline(&FRONTEND.akglsink, line, sizeof(line), &eof));
|
||||
TEST_REQUIRE_STR(line, "a");
|
||||
|
||||
/* A key that composes to nothing is not typed: an arrow is not a character. */
|
||||
push_key(SDLK_UP);
|
||||
push_key(SDLK_LEFT);
|
||||
push_text_key('z', "z");
|
||||
push_key('\r');
|
||||
PASS(errctx, FRONTEND.akglsink.readline(&FRONTEND.akglsink, line, sizeof(line), &eof));
|
||||
TEST_REQUIRE_STR(line, "z");
|
||||
|
||||
/* Escape abandons a line rather than submitting it. */
|
||||
push_text_key('z', "z");
|
||||
push_key(SDLK_ESCAPE);
|
||||
push_text_key('o', "o");
|
||||
push_text_key('k', "k");
|
||||
push_key('\r');
|
||||
PASS(errctx, FRONTEND.akglsink.readline(&FRONTEND.akglsink, line, sizeof(line), &eof));
|
||||
TEST_REQUIRE_STR(line, "ok");
|
||||
|
||||
/* A closed window ends the wait, reported as end of input rather than raised. */
|
||||
push_quit();
|
||||
|
||||
Reference in New Issue
Block a user