168 lines
6.8 KiB
C
168 lines
6.8 KiB
C
|
|
/**
|
||
|
|
* @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>
|
||
|
|
|
||
|
|
#include <akbasic/audio.h>
|
||
|
|
#include <akbasic/graphics.h>
|
||
|
|
#include <akbasic/input.h>
|
||
|
|
#include <akbasic/sink.h>
|
||
|
|
|
||
|
|
/** @brief How many saved SSHAPE regions the graphics backend will hold at once. */
|
||
|
|
#define AKBASIC_AKGL_MAX_SHAPES 16
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @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;
|
||
|
|
|
||
|
|
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];
|
||
|
|
} 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);
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @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_
|