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:
102
src/sink_akgl.c
102
src/sink_akgl.c
@@ -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;
|
||||
}
|
||||
/*
|
||||
|
||||
Reference in New Issue
Block a user