Wire the sink and the three devices to libakgl
src/sink_akgl.c, src/graphics_akgl.c, src/audio_akgl.c and src/input_akgl.c, in
the akbasic_akgl target, which is the only thing here that links SDL.
-DAKBASIC_WITH_AKGL=ON had never been configured in this repository before, and
it now builds and passes.
The sink is what section 3 has been waiting on. Its character grid comes from
akgl_text_measure(font, "A", &w, &h), the direct equivalent of the reference's
font.SizeUTF8("A") and the call that did not exist until 42b60f7. Wrapping is
done on the character grid rather than by handing SDL_ttf a wraplength, because
the cursor has to land somewhere definite: a program that PRINTs a long string
and then PRINTs again expects the second to start on the row after the first
ended, and only the code that placed the characters knows which row that is.
tests/akgl_backends.c draws into a 128x128 software renderer under the dummy
video driver and reads the pixels back -- the pattern deps/libakgl/tests/draw.c
established, which needs no display and no offscreen harness. It asserts the
seam rather than libakgl's own behaviour: a BASIC line in, a lit pixel of the
right colour out.
Four things in libakgl had to be worked around to get here. All four are
commented at their site with "filed upstream" and recorded in TODO.md section 3:
- An embedded libakgl requires SDL, SDL_image, SDL_mixer, SDL_ttf and jansson to
be *installed*. It builds its own vendored copies only when it is top-level,
and they are sitting right there in deps/libakgl/deps. Every lookup is guarded
with if(NOT TARGET ...), so this adds those five subdirectories before
add_subdirectory(deps/libakgl) -- the same trick and the same ordering
requirement akerror::akerror and akstdlib::akstdlib already need.
- akgl/controller.h does not compile on its own: it declares handlers taking an
akgl_Actor * and includes nothing that declares the type.
- There is no way to attach a 2D backend to a renderer you already have.
akgl_render_init2d() installs the vtable but also creates its own window and
writes the camera global, so it belongs to the akgl_game_init() path -- which
is exactly the path an embedding host is not on. The test assigns the six
pointers by hand.
- akgl_text_rendertextat() segfaults on a backend whose vtable is empty; it
reaches through renderer->draw_texture without checking it. Same class of
defect 42b60f7's own commit added a draw test for.
The sink's readline reports end of input rather than reading: a drawn text layer
is not a source of lines, and INPUT through one wants a line editor built on the
keystroke ring. EOF rather than an error is the contract sink.h states, so INPUT
already handles it. That editor is the next piece of work there.
70/70 core ctest with no SDL on the include path, 71/71 with the akgl suite,
clean under -Wall -Wextra, doxygen clean.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 08:46:53 -04:00
|
|
|
/**
|
|
|
|
|
* @file akgl.h
|
|
|
|
|
* @brief The libakgl-backed implementations of the sink and the three devices.
|
|
|
|
|
*
|
|
|
|
|
* Everything declared here lives in the separate `akbasic_akgl` target, which is
|
|
|
|
|
* the only part of this project that links SDL. The core library builds and its
|
|
|
|
|
* whole test suite runs on a machine with no SDL on it; that is why the records
|
|
|
|
|
* these initializers populate are plain function-pointer structs and why this
|
|
|
|
|
* header is the only one that includes a libakgl header.
|
|
|
|
|
*
|
|
|
|
|
* **The interpreter owns no window, no renderer and no event loop.** Every one
|
|
|
|
|
* of these takes something the host already created and draws or plays through
|
|
|
|
|
* it. None of them creates a device, and none of them pumps events.
|
|
|
|
|
*
|
|
|
|
|
* Each initializer calls akgl_error_init() first. It reserves libakgl's 256-260
|
|
|
|
|
* status band and names every AKGL_ERR_* code; akgl_game_init() calls it as its
|
|
|
|
|
* first statement, but a program driving subsystems directly -- which is exactly
|
|
|
|
|
* what an embedded interpreter does -- never goes through akgl_game_init() and
|
|
|
|
|
* has to call it itself. Skip it and every AKGL_ERR_* that reaches a stack trace
|
|
|
|
|
* prints "Unknown Error". It is idempotent, so a host that already called it
|
|
|
|
|
* loses nothing.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
#ifndef _AKBASIC_AKGL_H_
|
|
|
|
|
#define _AKBASIC_AKGL_H_
|
|
|
|
|
|
|
|
|
|
#include <SDL3/SDL.h>
|
|
|
|
|
#include <SDL3_ttf/SDL_ttf.h>
|
|
|
|
|
|
|
|
|
|
#include <akerror.h>
|
|
|
|
|
|
|
|
|
|
#include <akgl/renderer.h>
|
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>
2026-07-31 13:25:14 -04:00
|
|
|
#include <akgl/version.h>
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Everything in this target now depends on APIs that arrived in libakgl 0.3.0:
|
|
|
|
|
* akgl_render_bind2d(), akgl_audio_sweep(), and akgl_Keystroke with
|
|
|
|
|
* akgl_controller_poll_keystroke(). Refused here rather than at link time,
|
|
|
|
|
* because a missing symbol names a function and this names the release.
|
|
|
|
|
*
|
|
|
|
|
* The soname carries MAJOR.MINOR while the major is 0, so 0.2 and 0.3 are
|
|
|
|
|
* different ABIs and a mismatch is normally caught at load. This catches the
|
|
|
|
|
* case the soname cannot: a build against 0.2 headers that happens to find a
|
|
|
|
|
* 0.3 library, or the reverse.
|
|
|
|
|
*/
|
|
|
|
|
#if !AKGL_VERSION_AT_LEAST(0, 3, 0)
|
|
|
|
|
#error "akbasic's libakgl adaptors require libakgl 0.3.0 or later"
|
|
|
|
|
#endif
|
Wire the sink and the three devices to libakgl
src/sink_akgl.c, src/graphics_akgl.c, src/audio_akgl.c and src/input_akgl.c, in
the akbasic_akgl target, which is the only thing here that links SDL.
-DAKBASIC_WITH_AKGL=ON had never been configured in this repository before, and
it now builds and passes.
The sink is what section 3 has been waiting on. Its character grid comes from
akgl_text_measure(font, "A", &w, &h), the direct equivalent of the reference's
font.SizeUTF8("A") and the call that did not exist until 42b60f7. Wrapping is
done on the character grid rather than by handing SDL_ttf a wraplength, because
the cursor has to land somewhere definite: a program that PRINTs a long string
and then PRINTs again expects the second to start on the row after the first
ended, and only the code that placed the characters knows which row that is.
tests/akgl_backends.c draws into a 128x128 software renderer under the dummy
video driver and reads the pixels back -- the pattern deps/libakgl/tests/draw.c
established, which needs no display and no offscreen harness. It asserts the
seam rather than libakgl's own behaviour: a BASIC line in, a lit pixel of the
right colour out.
Four things in libakgl had to be worked around to get here. All four are
commented at their site with "filed upstream" and recorded in TODO.md section 3:
- An embedded libakgl requires SDL, SDL_image, SDL_mixer, SDL_ttf and jansson to
be *installed*. It builds its own vendored copies only when it is top-level,
and they are sitting right there in deps/libakgl/deps. Every lookup is guarded
with if(NOT TARGET ...), so this adds those five subdirectories before
add_subdirectory(deps/libakgl) -- the same trick and the same ordering
requirement akerror::akerror and akstdlib::akstdlib already need.
- akgl/controller.h does not compile on its own: it declares handlers taking an
akgl_Actor * and includes nothing that declares the type.
- There is no way to attach a 2D backend to a renderer you already have.
akgl_render_init2d() installs the vtable but also creates its own window and
writes the camera global, so it belongs to the akgl_game_init() path -- which
is exactly the path an embedding host is not on. The test assigns the six
pointers by hand.
- akgl_text_rendertextat() segfaults on a backend whose vtable is empty; it
reaches through renderer->draw_texture without checking it. Same class of
defect 42b60f7's own commit added a draw test for.
The sink's readline reports end of input rather than reading: a drawn text layer
is not a source of lines, and INPUT through one wants a line editor built on the
keystroke ring. EOF rather than an error is the contract sink.h states, so INPUT
already handles it. That editor is the next piece of work there.
70/70 core ctest with no SDL on the include path, 71/71 with the akgl suite,
clean under -Wall -Wextra, doxygen clean.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 08:46:53 -04:00
|
|
|
|
|
|
|
|
#include <akbasic/audio.h>
|
|
|
|
|
#include <akbasic/graphics.h>
|
|
|
|
|
#include <akbasic/input.h>
|
|
|
|
|
#include <akbasic/sink.h>
|
|
|
|
|
|
Own every pixel each frame, and bring the cursor back as a blinking block
The blinking backspace was not the backspace. The sink skipped rows it
believed were already clear, tracked in a drawn[] array, which assumes a frame
inherits the frame before it. SDL_RenderPresent swaps buffers, so a frame
inherits the one two back: a row erased once is clean in one buffer and dirty
in the other, and presenting alternates between them. The first character of a
backspaced wrap flickered forever.
It reproduces on X11 and never under the dummy driver or a software renderer,
which is why the suite stayed green through two rounds of fixing it. The sink
now repaints every row it owns every frame -- a frame either owns every pixel
it presents or inherits pixels it cannot reason about.
Confirmed on real hardware with the reported input: the wrapped tail's row
reads zero across five successive captures.
The cursor returns as a blinking block, half a second per cycle, off SDL's
clock. It sits in the cell after the text rather than under it, which is what
made the underscore unreadable, and it wraps to the next row when a line
exactly fills one -- where the next character actually lands.
The new regression test paints stale pixels by hand, which is what a swapped-in
buffer hands back, so the case is covered without real hardware. Every new
assertion was checked by reverting the fix and watching it fail.
Recorded honestly in TODO.md section 5: the same buffer swap means the
graphics verbs were never reliable across frames either. A one-shot DRAW lands
in one buffer and the next present shows the other. Making that work needs a
persistent surface, which is its own commit.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 15:23:41 -04:00
|
|
|
/**
|
|
|
|
|
* @brief One full cursor blink in milliseconds, half on and half off.
|
|
|
|
|
*
|
|
|
|
|
* Half a second, which is about what a Commodore does and is slow enough to read
|
|
|
|
|
* under.
|
|
|
|
|
*/
|
|
|
|
|
#define AKBASIC_SINK_CURSOR_BLINK_MS 500
|
|
|
|
|
|
Wire the sink and the three devices to libakgl
src/sink_akgl.c, src/graphics_akgl.c, src/audio_akgl.c and src/input_akgl.c, in
the akbasic_akgl target, which is the only thing here that links SDL.
-DAKBASIC_WITH_AKGL=ON had never been configured in this repository before, and
it now builds and passes.
The sink is what section 3 has been waiting on. Its character grid comes from
akgl_text_measure(font, "A", &w, &h), the direct equivalent of the reference's
font.SizeUTF8("A") and the call that did not exist until 42b60f7. Wrapping is
done on the character grid rather than by handing SDL_ttf a wraplength, because
the cursor has to land somewhere definite: a program that PRINTs a long string
and then PRINTs again expects the second to start on the row after the first
ended, and only the code that placed the characters knows which row that is.
tests/akgl_backends.c draws into a 128x128 software renderer under the dummy
video driver and reads the pixels back -- the pattern deps/libakgl/tests/draw.c
established, which needs no display and no offscreen harness. It asserts the
seam rather than libakgl's own behaviour: a BASIC line in, a lit pixel of the
right colour out.
Four things in libakgl had to be worked around to get here. All four are
commented at their site with "filed upstream" and recorded in TODO.md section 3:
- An embedded libakgl requires SDL, SDL_image, SDL_mixer, SDL_ttf and jansson to
be *installed*. It builds its own vendored copies only when it is top-level,
and they are sitting right there in deps/libakgl/deps. Every lookup is guarded
with if(NOT TARGET ...), so this adds those five subdirectories before
add_subdirectory(deps/libakgl) -- the same trick and the same ordering
requirement akerror::akerror and akstdlib::akstdlib already need.
- akgl/controller.h does not compile on its own: it declares handlers taking an
akgl_Actor * and includes nothing that declares the type.
- There is no way to attach a 2D backend to a renderer you already have.
akgl_render_init2d() installs the vtable but also creates its own window and
writes the camera global, so it belongs to the akgl_game_init() path -- which
is exactly the path an embedding host is not on. The test assigns the six
pointers by hand.
- akgl_text_rendertextat() segfaults on a backend whose vtable is empty; it
reaches through renderer->draw_texture without checking it. Same class of
defect 42b60f7's own commit added a draw test for.
The sink's readline reports end of input rather than reading: a drawn text layer
is not a source of lines, and INPUT through one wants a line editor built on the
keystroke ring. EOF rather than an error is the contract sink.h states, so INPUT
already handles it. That editor is the next piece of work there.
70/70 core ctest with no SDL on the include path, 71/71 with the akgl suite,
clean under -Wall -Wextra, doxygen clean.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 08:46:53 -04:00
|
|
|
/** @brief How many saved SSHAPE regions the graphics backend will hold at once. */
|
|
|
|
|
#define AKBASIC_AKGL_MAX_SHAPES 16
|
|
|
|
|
|
2026-07-31 11:21:39 -04:00
|
|
|
/**
|
|
|
|
|
* @brief One frame of the host's own loop, borrowed by the sink's line editor.
|
|
|
|
|
*
|
|
|
|
|
* The sink has to be able to wait for a typed line without owning an event loop,
|
|
|
|
|
* and those two requirements only meet in one place: the host hands over a
|
|
|
|
|
* callback that does exactly one frame -- pump events, redraw, present -- and
|
|
|
|
|
* the editor calls it between keystrokes. The loop is still the host's; the
|
|
|
|
|
* editor just borrows it a frame at a time.
|
|
|
|
|
*
|
|
|
|
|
* Setting @p running false is how the host says the window closed. The editor
|
|
|
|
|
* reports end of input rather than raising, because that is what running out of
|
|
|
|
|
* input means everywhere else in sink.h.
|
|
|
|
|
*
|
|
|
|
|
* @param self Whatever the host passed to akbasic_sink_akgl_set_pump().
|
|
|
|
|
* @param running Set false to stop waiting; the editor then reports EOF.
|
|
|
|
|
* @return `NULL` on success, otherwise an error context owned by the caller.
|
|
|
|
|
*/
|
|
|
|
|
typedef akerr_ErrorContext AKERR_NOIGNORE *(*akbasic_AkglPump)(void *self, bool *running);
|
|
|
|
|
|
Wire the sink and the three devices to libakgl
src/sink_akgl.c, src/graphics_akgl.c, src/audio_akgl.c and src/input_akgl.c, in
the akbasic_akgl target, which is the only thing here that links SDL.
-DAKBASIC_WITH_AKGL=ON had never been configured in this repository before, and
it now builds and passes.
The sink is what section 3 has been waiting on. Its character grid comes from
akgl_text_measure(font, "A", &w, &h), the direct equivalent of the reference's
font.SizeUTF8("A") and the call that did not exist until 42b60f7. Wrapping is
done on the character grid rather than by handing SDL_ttf a wraplength, because
the cursor has to land somewhere definite: a program that PRINTs a long string
and then PRINTs again expects the second to start on the row after the first
ended, and only the code that placed the characters knows which row that is.
tests/akgl_backends.c draws into a 128x128 software renderer under the dummy
video driver and reads the pixels back -- the pattern deps/libakgl/tests/draw.c
established, which needs no display and no offscreen harness. It asserts the
seam rather than libakgl's own behaviour: a BASIC line in, a lit pixel of the
right colour out.
Four things in libakgl had to be worked around to get here. All four are
commented at their site with "filed upstream" and recorded in TODO.md section 3:
- An embedded libakgl requires SDL, SDL_image, SDL_mixer, SDL_ttf and jansson to
be *installed*. It builds its own vendored copies only when it is top-level,
and they are sitting right there in deps/libakgl/deps. Every lookup is guarded
with if(NOT TARGET ...), so this adds those five subdirectories before
add_subdirectory(deps/libakgl) -- the same trick and the same ordering
requirement akerror::akerror and akstdlib::akstdlib already need.
- akgl/controller.h does not compile on its own: it declares handlers taking an
akgl_Actor * and includes nothing that declares the type.
- There is no way to attach a 2D backend to a renderer you already have.
akgl_render_init2d() installs the vtable but also creates its own window and
writes the camera global, so it belongs to the akgl_game_init() path -- which
is exactly the path an embedding host is not on. The test assigns the six
pointers by hand.
- akgl_text_rendertextat() segfaults on a backend whose vtable is empty; it
reaches through renderer->draw_texture without checking it. Same class of
defect 42b60f7's own commit added a draw test for.
The sink's readline reports end of input rather than reading: a drawn text layer
is not a source of lines, and INPUT through one wants a line editor built on the
keystroke ring. EOF rather than an error is the contract sink.h states, so INPUT
already handles it. That editor is the next piece of work there.
70/70 core ctest with no SDL on the include path, 71/71 with the akgl suite,
clean under -Wall -Wextra, doxygen clean.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 08:46:53 -04:00
|
|
|
/**
|
|
|
|
|
* @brief State for the libakgl-backed text sink.
|
|
|
|
|
*
|
|
|
|
|
* The cursor, the wrap and the scroll live here rather than in the interpreter:
|
|
|
|
|
* everything in the reference's basicruntime_graphics.go except Write and
|
|
|
|
|
* Println, which are the sink interface itself.
|
|
|
|
|
*/
|
|
|
|
|
typedef struct
|
|
|
|
|
{
|
|
|
|
|
akgl_RenderBackend *renderer;
|
|
|
|
|
TTF_Font *font;
|
|
|
|
|
SDL_Color color;
|
Erase what the text layer drew: three GUI bugs, one cause
The sink painted glyphs on top of whatever was already on the renderer and
never erased anything, and the host deliberately does not clear the frame so a
DRAW survives. The grid was always correct; the screen kept the previous
frame. That surfaced as three separate-looking faults:
- a backspaced character stayed on screen, including across a line wrap
- a scroll left the old rows behind, "papered over with garbage"
- the editing cursor smeared an underscore along every column it passed
through, which read as an underline under the text being typed
render() now fills each row it is about to draw, and each row that has emptied
since it last drew, with the background first. Row by row rather than one
clear over the whole area: the text layer is authoritative over the rows it
occupies and leaves every other pixel alone, so a picture behind the text
loses only the strips the text uses instead of all of it.
echo_line() also truncates rows that hold nothing but the spaces its own erase
pass wrote, which is what backspacing back across a wrap leaves behind. A row
of spaces is text as far as render() is concerned, so it would have been
repainted forever and kept erasing whatever was under it.
The cursor glyph is removed outright, as asked. The smearing was the erase bug
rather than the cursor, so one could come back and behave -- a block would
read better than an underscore.
Four pixel-level tests, because grid assertions could never have caught any of
this. Each was checked by reverting the fix and watching it fail; two of them
were vacuous when first written and are noted as such where they are fixed.
The real-keyboard test also now rubs out a character and scrolls forty lines.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 15:03:37 -04:00
|
|
|
/**
|
|
|
|
|
* What a text cell is erased to before its glyphs are drawn. Opaque black
|
|
|
|
|
* by default, which is a Commodore console and is what makes the text layer
|
|
|
|
|
* *authoritative* over the rows it occupies rather than transparent over
|
|
|
|
|
* them -- see akbasic_sink_akgl_render().
|
|
|
|
|
*/
|
|
|
|
|
SDL_Color background;
|
Wire the sink and the three devices to libakgl
src/sink_akgl.c, src/graphics_akgl.c, src/audio_akgl.c and src/input_akgl.c, in
the akbasic_akgl target, which is the only thing here that links SDL.
-DAKBASIC_WITH_AKGL=ON had never been configured in this repository before, and
it now builds and passes.
The sink is what section 3 has been waiting on. Its character grid comes from
akgl_text_measure(font, "A", &w, &h), the direct equivalent of the reference's
font.SizeUTF8("A") and the call that did not exist until 42b60f7. Wrapping is
done on the character grid rather than by handing SDL_ttf a wraplength, because
the cursor has to land somewhere definite: a program that PRINTs a long string
and then PRINTs again expects the second to start on the row after the first
ended, and only the code that placed the characters knows which row that is.
tests/akgl_backends.c draws into a 128x128 software renderer under the dummy
video driver and reads the pixels back -- the pattern deps/libakgl/tests/draw.c
established, which needs no display and no offscreen harness. It asserts the
seam rather than libakgl's own behaviour: a BASIC line in, a lit pixel of the
right colour out.
Four things in libakgl had to be worked around to get here. All four are
commented at their site with "filed upstream" and recorded in TODO.md section 3:
- An embedded libakgl requires SDL, SDL_image, SDL_mixer, SDL_ttf and jansson to
be *installed*. It builds its own vendored copies only when it is top-level,
and they are sitting right there in deps/libakgl/deps. Every lookup is guarded
with if(NOT TARGET ...), so this adds those five subdirectories before
add_subdirectory(deps/libakgl) -- the same trick and the same ordering
requirement akerror::akerror and akstdlib::akstdlib already need.
- akgl/controller.h does not compile on its own: it declares handlers taking an
akgl_Actor * and includes nothing that declares the type.
- There is no way to attach a 2D backend to a renderer you already have.
akgl_render_init2d() installs the vtable but also creates its own window and
writes the camera global, so it belongs to the akgl_game_init() path -- which
is exactly the path an embedding host is not on. The test assigns the six
pointers by hand.
- akgl_text_rendertextat() segfaults on a backend whose vtable is empty; it
reaches through renderer->draw_texture without checking it. Same class of
defect 42b60f7's own commit added a draw test for.
The sink's readline reports end of input rather than reading: a drawn text layer
is not a source of lines, and INPUT through one wants a line editor built on the
keystroke ring. EOF rather than an error is the contract sink.h states, so INPUT
already handles it. That editor is the next piece of work there.
70/70 core ctest with no SDL on the include path, 71/71 with the akgl suite,
clean under -Wall -Wextra, doxygen clean.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 08:46:53 -04:00
|
|
|
|
|
|
|
|
int x; /* pixel origin of the text area */
|
|
|
|
|
int y;
|
|
|
|
|
int width; /* pixel size of the text area */
|
|
|
|
|
int height;
|
|
|
|
|
int cellw; /* one character cell, measured from the font */
|
|
|
|
|
int cellh;
|
|
|
|
|
int columns; /* the character grid the cell size works out to */
|
|
|
|
|
int rows;
|
|
|
|
|
|
|
|
|
|
int cursorcol;
|
|
|
|
|
int cursorrow;
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* The scrollback the sink redraws every frame. A fixed grid rather than a
|
|
|
|
|
* list of lines: the interpreter allocates nothing, and neither does this.
|
|
|
|
|
*/
|
|
|
|
|
char text[64][256];
|
2026-07-31 11:21:39 -04:00
|
|
|
|
Erase what the text layer drew: three GUI bugs, one cause
The sink painted glyphs on top of whatever was already on the renderer and
never erased anything, and the host deliberately does not clear the frame so a
DRAW survives. The grid was always correct; the screen kept the previous
frame. That surfaced as three separate-looking faults:
- a backspaced character stayed on screen, including across a line wrap
- a scroll left the old rows behind, "papered over with garbage"
- the editing cursor smeared an underscore along every column it passed
through, which read as an underline under the text being typed
render() now fills each row it is about to draw, and each row that has emptied
since it last drew, with the background first. Row by row rather than one
clear over the whole area: the text layer is authoritative over the rows it
occupies and leaves every other pixel alone, so a picture behind the text
loses only the strips the text uses instead of all of it.
echo_line() also truncates rows that hold nothing but the spaces its own erase
pass wrote, which is what backspacing back across a wrap leaves behind. A row
of spaces is text as far as render() is concerned, so it would have been
repainted forever and kept erasing whatever was under it.
The cursor glyph is removed outright, as asked. The smearing was the erase bug
rather than the cursor, so one could come back and behave -- a block would
read better than an underscore.
Four pixel-level tests, because grid assertions could never have caught any of
this. Each was checked by reverting the fix and watching it fail; two of them
were vacuous when first written and are noted as such where they are fixed.
The real-keyboard test also now rubs out a character and scrolls forty lines.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 15:03:37 -04:00
|
|
|
/**
|
Own every pixel each frame, and bring the cursor back as a blinking block
The blinking backspace was not the backspace. The sink skipped rows it
believed were already clear, tracked in a drawn[] array, which assumes a frame
inherits the frame before it. SDL_RenderPresent swaps buffers, so a frame
inherits the one two back: a row erased once is clean in one buffer and dirty
in the other, and presenting alternates between them. The first character of a
backspaced wrap flickered forever.
It reproduces on X11 and never under the dummy driver or a software renderer,
which is why the suite stayed green through two rounds of fixing it. The sink
now repaints every row it owns every frame -- a frame either owns every pixel
it presents or inherits pixels it cannot reason about.
Confirmed on real hardware with the reported input: the wrapped tail's row
reads zero across five successive captures.
The cursor returns as a blinking block, half a second per cycle, off SDL's
clock. It sits in the cell after the text rather than under it, which is what
made the underscore unreadable, and it wraps to the next row when a line
exactly fills one -- where the next character actually lands.
The new regression test paints stale pixels by hand, which is what a swapped-in
buffer hands back, so the case is covered without real hardware. Every new
assertion was checked by reverting the fix and watching it fail.
Recorded honestly in TODO.md section 5: the same buffer swap means the
graphics verbs were never reliable across frames either. A one-shot DRAW lands
in one buffer and the next present shows the other. Making that work needs a
persistent surface, which is its own commit.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 15:23:41 -04:00
|
|
|
* Milliseconds for one full cursor blink, half on and half off. Zero holds
|
|
|
|
|
* the cursor solid, which is what a test wanting a deterministic frame sets.
|
Erase what the text layer drew: three GUI bugs, one cause
The sink painted glyphs on top of whatever was already on the renderer and
never erased anything, and the host deliberately does not clear the frame so a
DRAW survives. The grid was always correct; the screen kept the previous
frame. That surfaced as three separate-looking faults:
- a backspaced character stayed on screen, including across a line wrap
- a scroll left the old rows behind, "papered over with garbage"
- the editing cursor smeared an underscore along every column it passed
through, which read as an underline under the text being typed
render() now fills each row it is about to draw, and each row that has emptied
since it last drew, with the background first. Row by row rather than one
clear over the whole area: the text layer is authoritative over the rows it
occupies and leaves every other pixel alone, so a picture behind the text
loses only the strips the text uses instead of all of it.
echo_line() also truncates rows that hold nothing but the spaces its own erase
pass wrote, which is what backspacing back across a wrap leaves behind. A row
of spaces is text as far as render() is concerned, so it would have been
repainted forever and kept erasing whatever was under it.
The cursor glyph is removed outright, as asked. The smearing was the erase bug
rather than the cursor, so one could come back and behave -- a block would
read better than an underscore.
Four pixel-level tests, because grid assertions could never have caught any of
this. Each was checked by reverting the fix and watching it fail; two of them
were vacuous when first written and are noted as such where they are fixed.
The real-keyboard test also now rubs out a character and scrolls forty lines.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 15:03:37 -04:00
|
|
|
*/
|
Own every pixel each frame, and bring the cursor back as a blinking block
The blinking backspace was not the backspace. The sink skipped rows it
believed were already clear, tracked in a drawn[] array, which assumes a frame
inherits the frame before it. SDL_RenderPresent swaps buffers, so a frame
inherits the one two back: a row erased once is clean in one buffer and dirty
in the other, and presenting alternates between them. The first character of a
backspaced wrap flickered forever.
It reproduces on X11 and never under the dummy driver or a software renderer,
which is why the suite stayed green through two rounds of fixing it. The sink
now repaints every row it owns every frame -- a frame either owns every pixel
it presents or inherits pixels it cannot reason about.
Confirmed on real hardware with the reported input: the wrapped tail's row
reads zero across five successive captures.
The cursor returns as a blinking block, half a second per cycle, off SDL's
clock. It sits in the cell after the text rather than under it, which is what
made the underscore unreadable, and it wraps to the next row when a line
exactly fills one -- where the next character actually lands.
The new regression test paints stale pixels by hand, which is what a swapped-in
buffer hands back, so the case is covered without real hardware. Every new
assertion was checked by reverting the fix and watching it fail.
Recorded honestly in TODO.md section 5: the same buffer swap means the
graphics verbs were never reliable across frames either. A one-shot DRAW lands
in one buffer and the next present shows the other. Making that work needs a
persistent surface, which is its own commit.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 15:23:41 -04:00
|
|
|
uint64_t cursorperiodms;
|
Erase what the text layer drew: three GUI bugs, one cause
The sink painted glyphs on top of whatever was already on the renderer and
never erased anything, and the host deliberately does not clear the frame so a
DRAW survives. The grid was always correct; the screen kept the previous
frame. That surfaced as three separate-looking faults:
- a backspaced character stayed on screen, including across a line wrap
- a scroll left the old rows behind, "papered over with garbage"
- the editing cursor smeared an underscore along every column it passed
through, which read as an underline under the text being typed
render() now fills each row it is about to draw, and each row that has emptied
since it last drew, with the background first. Row by row rather than one
clear over the whole area: the text layer is authoritative over the rows it
occupies and leaves every other pixel alone, so a picture behind the text
loses only the strips the text uses instead of all of it.
echo_line() also truncates rows that hold nothing but the spaces its own erase
pass wrote, which is what backspacing back across a wrap leaves behind. A row
of spaces is text as far as render() is concerned, so it would have been
repainted forever and kept erasing whatever was under it.
The cursor glyph is removed outright, as asked. The smearing was the erase bug
rather than the cursor, so one could come back and behave -- a block would
read better than an underscore.
Four pixel-level tests, because grid assertions could never have caught any of
this. Each was checked by reverting the fix and watching it fail; two of them
were vacuous when first written and are noted as such where they are fixed.
The real-keyboard test also now rubs out a character and scrolls forty lines.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 15:03:37 -04:00
|
|
|
|
2026-07-31 11:21:39 -04:00
|
|
|
/*
|
|
|
|
|
* The line editor. Nothing here is live except while readline is running,
|
|
|
|
|
* and `editing` is what says so -- render() draws a cursor only then, and
|
|
|
|
|
* the scroll adjusts `editrow` only then.
|
|
|
|
|
*/
|
|
|
|
|
akbasic_AkglPump pump;
|
|
|
|
|
void *pumpself;
|
|
|
|
|
bool editing;
|
|
|
|
|
int editrow; /* where the line being typed starts */
|
|
|
|
|
int editcol;
|
|
|
|
|
int editlen; /* characters accepted so far */
|
|
|
|
|
int echolen; /* characters currently drawn, so a backspace */
|
|
|
|
|
/* knows how much to erase */
|
|
|
|
|
char editline[256];
|
Wire the sink and the three devices to libakgl
src/sink_akgl.c, src/graphics_akgl.c, src/audio_akgl.c and src/input_akgl.c, in
the akbasic_akgl target, which is the only thing here that links SDL.
-DAKBASIC_WITH_AKGL=ON had never been configured in this repository before, and
it now builds and passes.
The sink is what section 3 has been waiting on. Its character grid comes from
akgl_text_measure(font, "A", &w, &h), the direct equivalent of the reference's
font.SizeUTF8("A") and the call that did not exist until 42b60f7. Wrapping is
done on the character grid rather than by handing SDL_ttf a wraplength, because
the cursor has to land somewhere definite: a program that PRINTs a long string
and then PRINTs again expects the second to start on the row after the first
ended, and only the code that placed the characters knows which row that is.
tests/akgl_backends.c draws into a 128x128 software renderer under the dummy
video driver and reads the pixels back -- the pattern deps/libakgl/tests/draw.c
established, which needs no display and no offscreen harness. It asserts the
seam rather than libakgl's own behaviour: a BASIC line in, a lit pixel of the
right colour out.
Four things in libakgl had to be worked around to get here. All four are
commented at their site with "filed upstream" and recorded in TODO.md section 3:
- An embedded libakgl requires SDL, SDL_image, SDL_mixer, SDL_ttf and jansson to
be *installed*. It builds its own vendored copies only when it is top-level,
and they are sitting right there in deps/libakgl/deps. Every lookup is guarded
with if(NOT TARGET ...), so this adds those five subdirectories before
add_subdirectory(deps/libakgl) -- the same trick and the same ordering
requirement akerror::akerror and akstdlib::akstdlib already need.
- akgl/controller.h does not compile on its own: it declares handlers taking an
akgl_Actor * and includes nothing that declares the type.
- There is no way to attach a 2D backend to a renderer you already have.
akgl_render_init2d() installs the vtable but also creates its own window and
writes the camera global, so it belongs to the akgl_game_init() path -- which
is exactly the path an embedding host is not on. The test assigns the six
pointers by hand.
- akgl_text_rendertextat() segfaults on a backend whose vtable is empty; it
reaches through renderer->draw_texture without checking it. Same class of
defect 42b60f7's own commit added a draw test for.
The sink's readline reports end of input rather than reading: a drawn text layer
is not a source of lines, and INPUT through one wants a line editor built on the
keystroke ring. EOF rather than an error is the contract sink.h states, so INPUT
already handles it. That editor is the next piece of work there.
70/70 core ctest with no SDL on the include path, 71/71 with the akgl suite,
clean under -Wall -Wextra, doxygen clean.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 08:46:53 -04:00
|
|
|
} akbasic_AkglSink;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @brief State for the libakgl-backed graphics backend.
|
|
|
|
|
*
|
|
|
|
|
* The shape pool is why this exists at all. SSHAPE hands the BASIC program a
|
|
|
|
|
* handle rather than the pixels -- see TODO.md section 5 -- and these are the
|
|
|
|
|
* surfaces those handles refer to.
|
|
|
|
|
*/
|
|
|
|
|
typedef struct
|
|
|
|
|
{
|
|
|
|
|
akgl_RenderBackend *renderer;
|
|
|
|
|
SDL_Surface *shapes[AKBASIC_AKGL_MAX_SHAPES];
|
|
|
|
|
int shapecount;
|
|
|
|
|
} akbasic_AkglGraphics;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @brief Point a text sink at a renderer and a font the host already has.
|
|
|
|
|
*
|
|
|
|
|
* The character grid is derived by measuring one glyph with akgl_text_measure(),
|
|
|
|
|
* which is the direct equivalent of the reference's font.SizeUTF8("A") -- and
|
|
|
|
|
* which did not exist in libakgl until 42b60f7. A font that is not monospaced
|
|
|
|
|
* still works; the grid is then sized by whatever "A" happens to measure, and
|
|
|
|
|
* proportional glyphs simply do not line up in columns.
|
|
|
|
|
*
|
|
|
|
|
* @param obj Object to initialize, inspect, or modify.
|
|
|
|
|
* @param state Storage for the sink's own state; must outlive the sink.
|
|
|
|
|
* @param renderer The renderer the host already initialized; not created here.
|
|
|
|
|
* @param font An open font; not opened or closed here.
|
|
|
|
|
* @param w Width in pixels of the text area.
|
|
|
|
|
* @param h Height in pixels of the text area.
|
|
|
|
|
* @return `NULL` on success, otherwise an error context owned by the caller.
|
|
|
|
|
* @throws AKERR_NULLPOINTER When any pointer argument is NULL.
|
|
|
|
|
* @throws AKBASIC_ERR_VALUE When the font measures a zero-width cell, which would divide by zero.
|
|
|
|
|
* @throws AKBASIC_ERR_BOUNDS When the area is too small for even one character.
|
|
|
|
|
*/
|
|
|
|
|
akerr_ErrorContext AKERR_NOIGNORE *akbasic_sink_init_akgl(akbasic_TextSink *obj, akbasic_AkglSink *state, akgl_RenderBackend *renderer, TTF_Font *font, int w, int h);
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @brief Draw whatever the sink currently holds.
|
|
|
|
|
*
|
|
|
|
|
* Separate from the sink's write path because the interpreter does not own the
|
|
|
|
|
* frame: a host calls this when it is drawing, not when the script happens to
|
|
|
|
|
* PRINT. A driver that only wants stdout never calls it at all.
|
|
|
|
|
*
|
|
|
|
|
* @param obj The sink to draw.
|
|
|
|
|
* @return `NULL` on success, otherwise an error context owned by the caller.
|
|
|
|
|
* @throws AKERR_NULLPOINTER When `obj` is NULL.
|
|
|
|
|
* @throws AKGL_ERR_SDL When the renderer refuses the text.
|
|
|
|
|
*/
|
|
|
|
|
akerr_ErrorContext AKERR_NOIGNORE *akbasic_sink_akgl_render(akbasic_TextSink *obj);
|
|
|
|
|
|
2026-07-31 11:21:39 -04:00
|
|
|
/**
|
|
|
|
|
* @brief Give the sink a line editor by lending it one frame of the host's loop.
|
|
|
|
|
*
|
|
|
|
|
* Without this, readline reports end of input immediately: a drawn text layer is
|
|
|
|
|
* not a source of lines, and a sink that blocked on the keyboard with no way to
|
|
|
|
|
* pump events would deadlock the process on its first INPUT. With it, readline
|
|
|
|
|
* collects keystrokes from libakgl's ring -- the one the host's own event pump
|
|
|
|
|
* fills -- echoes them into the grid, and returns the line on Return.
|
|
|
|
|
*
|
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>
2026-07-31 13:25:14 -04:00
|
|
|
* **It is a real keyboard as of libakgl 0.3.0.** The ring carries the composed
|
|
|
|
|
* UTF-8 text SDL worked out from the keystroke, so shifted characters, a
|
|
|
|
|
* keyboard layout, a compose key and a dead key all do what the person typing
|
|
|
|
|
* expects -- including the double quote, without which a BASIC string literal
|
|
|
|
|
* cannot be typed at all. Until 0.3.0 the ring reported a keycode and nothing
|
|
|
|
|
* else, so none of that was reachable and letters were folded to upper case;
|
|
|
|
|
* that was libakgl API-gap item 10, filed from here.
|
|
|
|
|
*
|
|
|
|
|
* Composed text is taken over the keycode wherever there is any, which is what
|
|
|
|
|
* makes the layout work: on an AZERTY keyboard the key SDL calls `SDLK_Q`
|
|
|
|
|
* composes to "a", and "a" is what the program should see.
|
2026-07-31 11:21:39 -04:00
|
|
|
*
|
|
|
|
|
* Editing is Backspace to erase one character and Escape to abandon the line.
|
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>
2026-07-31 13:25:14 -04:00
|
|
|
* There is no cursor movement within the line: the ring reports the arrow keys,
|
|
|
|
|
* but a script's own GET loop wants them.
|
2026-07-31 11:21:39 -04:00
|
|
|
*
|
|
|
|
|
* @param obj The sink to give an editor to.
|
|
|
|
|
* @param pump One frame of the host's loop, or NULL to take the editor away.
|
|
|
|
|
* @param self Passed back to @p pump untouched.
|
|
|
|
|
* @return `NULL` on success, otherwise an error context owned by the caller.
|
|
|
|
|
* @throws AKERR_NULLPOINTER When `obj` is NULL or carries no sink state.
|
|
|
|
|
*/
|
|
|
|
|
akerr_ErrorContext AKERR_NOIGNORE *akbasic_sink_akgl_set_pump(akbasic_TextSink *obj, akbasic_AkglPump pump, void *self);
|
|
|
|
|
|
Wire the sink and the three devices to libakgl
src/sink_akgl.c, src/graphics_akgl.c, src/audio_akgl.c and src/input_akgl.c, in
the akbasic_akgl target, which is the only thing here that links SDL.
-DAKBASIC_WITH_AKGL=ON had never been configured in this repository before, and
it now builds and passes.
The sink is what section 3 has been waiting on. Its character grid comes from
akgl_text_measure(font, "A", &w, &h), the direct equivalent of the reference's
font.SizeUTF8("A") and the call that did not exist until 42b60f7. Wrapping is
done on the character grid rather than by handing SDL_ttf a wraplength, because
the cursor has to land somewhere definite: a program that PRINTs a long string
and then PRINTs again expects the second to start on the row after the first
ended, and only the code that placed the characters knows which row that is.
tests/akgl_backends.c draws into a 128x128 software renderer under the dummy
video driver and reads the pixels back -- the pattern deps/libakgl/tests/draw.c
established, which needs no display and no offscreen harness. It asserts the
seam rather than libakgl's own behaviour: a BASIC line in, a lit pixel of the
right colour out.
Four things in libakgl had to be worked around to get here. All four are
commented at their site with "filed upstream" and recorded in TODO.md section 3:
- An embedded libakgl requires SDL, SDL_image, SDL_mixer, SDL_ttf and jansson to
be *installed*. It builds its own vendored copies only when it is top-level,
and they are sitting right there in deps/libakgl/deps. Every lookup is guarded
with if(NOT TARGET ...), so this adds those five subdirectories before
add_subdirectory(deps/libakgl) -- the same trick and the same ordering
requirement akerror::akerror and akstdlib::akstdlib already need.
- akgl/controller.h does not compile on its own: it declares handlers taking an
akgl_Actor * and includes nothing that declares the type.
- There is no way to attach a 2D backend to a renderer you already have.
akgl_render_init2d() installs the vtable but also creates its own window and
writes the camera global, so it belongs to the akgl_game_init() path -- which
is exactly the path an embedding host is not on. The test assigns the six
pointers by hand.
- akgl_text_rendertextat() segfaults on a backend whose vtable is empty; it
reaches through renderer->draw_texture without checking it. Same class of
defect 42b60f7's own commit added a draw test for.
The sink's readline reports end of input rather than reading: a drawn text layer
is not a source of lines, and INPUT through one wants a line editor built on the
keystroke ring. EOF rather than an error is the contract sink.h states, so INPUT
already handles it. That editor is the next piece of work there.
70/70 core ctest with no SDL on the include path, 71/71 with the akgl suite,
clean under -Wall -Wextra, doxygen clean.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 08:46:53 -04:00
|
|
|
/**
|
|
|
|
|
* @brief Point a graphics backend at a renderer the host already has.
|
|
|
|
|
* @param obj Object to initialize, inspect, or modify.
|
|
|
|
|
* @param state Storage for the shape pool; must outlive the backend.
|
|
|
|
|
* @param renderer The renderer the host already initialized; not created here.
|
|
|
|
|
* @return `NULL` on success, otherwise an error context owned by the caller.
|
|
|
|
|
* @throws AKERR_NULLPOINTER When any argument is NULL.
|
|
|
|
|
*/
|
|
|
|
|
akerr_ErrorContext AKERR_NOIGNORE *akbasic_graphics_init_akgl(akbasic_GraphicsBackend *obj, akbasic_AkglGraphics *state, akgl_RenderBackend *renderer);
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @brief Wire an audio backend to libakgl's tone generator.
|
|
|
|
|
*
|
|
|
|
|
* Calls akgl_audio_init(), which opens an SDL audio device. A host that owns its
|
|
|
|
|
* own audio pipeline can call akgl_audio_init() itself beforehand -- it is
|
|
|
|
|
* idempotent in the sense that mattered upstream: the voice table works whether
|
|
|
|
|
* or not a device is open.
|
|
|
|
|
*
|
|
|
|
|
* @param obj Object to initialize, inspect, or modify.
|
|
|
|
|
* @return `NULL` on success, otherwise an error context owned by the caller.
|
|
|
|
|
* @throws AKERR_NULLPOINTER When `obj` is NULL.
|
|
|
|
|
* @throws AKGL_ERR_SDL When no audio device can be opened.
|
|
|
|
|
*/
|
|
|
|
|
akerr_ErrorContext AKERR_NOIGNORE *akbasic_audio_init_akgl(akbasic_AudioBackend *obj);
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @brief Wire an input backend to libakgl's keystroke ring.
|
|
|
|
|
*
|
|
|
|
|
* Reads only. The ring is filled by akgl_controller_handle_event(), which the
|
|
|
|
|
* *host* calls as it pumps SDL events -- so a script gets keystrokes without the
|
|
|
|
|
* interpreter owning the event loop.
|
|
|
|
|
*
|
|
|
|
|
* Worth knowing before lending this to a script: that ring is process-global and
|
|
|
|
|
* the host's own control maps read the same events. A script sitting in a GET
|
|
|
|
|
* loop drains keystrokes the game will then never see. A host that cares either
|
|
|
|
|
* withholds the input backend or supplies a filtered one of its own.
|
|
|
|
|
*
|
|
|
|
|
* @param obj Object to initialize, inspect, or modify.
|
|
|
|
|
* @return `NULL` on success, otherwise an error context owned by the caller.
|
|
|
|
|
* @throws AKERR_NULLPOINTER When `obj` is NULL.
|
|
|
|
|
*/
|
|
|
|
|
akerr_ErrorContext AKERR_NOIGNORE *akbasic_input_init_akgl(akbasic_InputBackend *obj);
|
|
|
|
|
|
|
|
|
|
#endif // _AKBASIC_AKGL_H_
|