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>
This commit is contained in:
2026-07-31 13:25:14 -04:00
parent 583c0abbd2
commit fca9ad4a89
16 changed files with 495 additions and 265 deletions

View File

@@ -13,19 +13,11 @@
* equivalent until then.
*/
#include <ctype.h>
#include <stdio.h>
#include <string.h>
#include <akerror.h>
/*
* akgl/actor.h before akgl/controller.h, and it is not optional: controller.h
* declares two handler function pointers taking an akgl_Actor * and includes
* nothing that declares the type. Filed upstream; see the same note in
* src/input_akgl.c, which is where it was found first.
*/
#include <akgl/actor.h>
#include <akgl/controller.h>
#include <akgl/error.h>
#include <akgl/text.h>
@@ -152,47 +144,83 @@ static void echo_line(akbasic_AkglSink *state)
state->echolen = state->editlen;
}
/**
* @brief Fold one keycode into the line being typed.
*
* The keycodes are SDL's, and an unshifted printable key carries its own ASCII
* value, which is why the range test below is all the translation there is.
* Shifted characters are unreachable: the ring carries no modifier state. See
* akbasic_sink_akgl_set_pump() in akgl.h.
*/
static void edit_key(akbasic_AkglSink *state, int keycode, bool *submitted)
/** @brief Append one byte to the line being typed, if there is room for it. */
static void edit_append(akbasic_AkglSink *state, char c)
{
if ( keycode == '\r' || keycode == '\n' ) {
if ( state->editlen >= (int)sizeof(state->editline) - 1 ) {
/* Full. Dropped silently, exactly as a C128's 80-character limit does. */
return;
}
state->editline[state->editlen] = c;
state->editlen += 1;
state->editline[state->editlen] = '\0';
}
/**
* @brief Fold one keystroke into the line being typed.
*
* **The composed text is preferred over the keycode wherever there is any**, and
* that is the whole reason libakgl 0.3.0's akgl_Keystroke exists. A keycode
* cannot express a shifted character, a keyboard layout, a compose key or a dead
* key; SDL has already worked all of that out by the time the ring sees it, and
* `text` is the answer. Before 0.3.0 this took a bare keycode, folded letters to
* upper case, and could not type a double quote -- which meant a BASIC string
* literal could not be typed at the window at all.
*
* The keycode is still what identifies the editing keys, because Return,
* Backspace and Escape are keys rather than characters and several of them
* compose to text SDL would otherwise hand straight through.
*/
static void edit_key(akbasic_AkglSink *state, const akgl_Keystroke *key, bool *submitted)
{
size_t i = 0;
if ( key->key == SDLK_RETURN || key->key == SDLK_KP_ENTER ||
key->key == '\r' || key->key == '\n' ) {
*submitted = true;
return;
}
if ( keycode == '\b' || keycode == 0x7f ) {
if ( key->key == SDLK_BACKSPACE || key->key == '\b' || key->key == 0x7f ) {
if ( state->editlen > 0 ) {
/*
* One *byte* at a time, which is wrong for a multi-byte character and
* is left that way deliberately: every character a BASIC program can
* hold is one byte (section 1.2's inline string), so a multi-byte
* character cannot survive being submitted anyway. Erasing what was
* accepted is consistent with that.
*/
state->editlen -= 1;
state->editline[state->editlen] = '\0';
echo_line(state);
}
return;
}
if ( keycode == 0x1b ) {
if ( key->key == SDLK_ESCAPE || key->key == 0x1b ) {
state->editlen = 0;
state->editline[0] = '\0';
echo_line(state);
return;
}
if ( keycode < 0x20 || keycode > 0x7e ) {
/* A cursor or function key. Not an editing command here; a script's own
* GET loop is what wants those. */
if ( key->text[0] != '\0' ) {
for ( i = 0; key->text[i] != '\0' && i < sizeof(key->text); i++ ) {
/*
* Printable ASCII only. The grid is a byte per cell and a value's
* string is a fixed 256 bytes, so a multi-byte character has nowhere
* to go -- dropping it is honest where storing half of it is not.
*/
if ( (unsigned char)key->text[i] >= 0x20 && (unsigned char)key->text[i] < 0x7f ) {
edit_append(state, key->text[i]);
}
}
echo_line(state);
return;
}
if ( state->editlen >= (int)sizeof(state->editline) - 1 ) {
/* Full. Dropped silently, exactly as a C128's 80-character limit does. */
return;
}
state->editline[state->editlen] = (char)toupper(keycode);
state->editlen += 1;
state->editline[state->editlen] = '\0';
echo_line(state);
/*
* No composed text: a cursor key, a function key or a bare modifier. Not an
* editing command here -- a script's own GET loop is what wants those.
*/
}
/**
@@ -206,15 +234,21 @@ static void edit_key(akbasic_AkglSink *state, int keycode, bool *submitted)
static akerr_ErrorContext AKERR_NOIGNORE *edit_loop(akbasic_AkglSink *state, bool *eof)
{
PREPARE_ERROR(errctx);
akgl_Keystroke key;
bool submitted = false;
bool available = false;
bool running = true;
int keycode = 0;
while ( !submitted ) {
PASS(errctx, akgl_controller_poll_key(&keycode, &available));
/*
* poll_keystroke rather than poll_key: the same ring, with the modifier
* state and the composed text still attached. poll_key is the reduced
* view, and it is what GET and GETKEY still use -- a script asking "was
* the up arrow pressed" wants a keycode, not the empty string.
*/
PASS(errctx, akgl_controller_poll_keystroke(&key, &available));
if ( available ) {
edit_key(state, keycode, &submitted);
edit_key(state, &key, &submitted);
continue;
}
/*