Make GRAPHIC select the text plane
Some checks failed
akbasic CI Build / cmake_build (push) Failing after 3m22s
akbasic CI Build / sanitizers (push) Failing after 4m43s
akbasic CI Build / coverage (push) Failing after 3m43s
akbasic CI Build / akgl_build (push) Failing after 4m48s
akbasic CI Build / mutation_test (push) Failing after 3m31s

Co-authored-by: Andrew Kesterson <andrew@aklabs.net>
This commit is contained in:
2026-08-03 08:08:34 -04:00
parent c5d13f00f6
commit 3342f2b569
13 changed files with 345 additions and 79 deletions

View File

@@ -1173,6 +1173,78 @@ static akerr_ErrorContext AKERR_NOIGNORE *test_drawing_layer_persists(void)
SUCCEED_RETURN(errctx);
}
/**
* @brief GRAPHIC selects whether the AKGL text plane covers the drawing layer.
*
* `PRINT` always writes into the retained grid. In a full bitmap mode the grid
* is simply not composited; returning to mode zero reveals it. Split mode moves
* the text region below the requested row, leaving the drawing visible above.
*/
static akerr_ErrorContext AKERR_NOIGNORE *test_graphic_text_composition(void)
{
PREPARE_ERROR(errctx);
SDL_Surface *shot = NULL;
int fullrows = 0;
PASS(errctx, akbasic_sink_init_akgl(&AKGLSINK, &AKGLSINKSTATE, akgl_renderer, font,
TARGET_SIZE, TARGET_SIZE));
PASS(errctx, akbasic_runtime_init(&RUNTIME, &AKGLSINK));
PASS(errctx, akbasic_graphics_init_akgl(&GRAPHICS, &GRAPHICSSTATE, akgl_renderer));
PASS(errctx, akbasic_runtime_set_devices(&RUNTIME, &GRAPHICS, NULL, NULL, NULL));
PASS(errctx, akbasic_runtime_load(&RUNTIME,
"10 GRAPHIC 1, 1\n"
"20 COLOR 1, 3\n"
"30 DRAW 1, 40, 40\n"
"40 PRINT \"HIDDEN\"\n"));
PASS(errctx, akbasic_runtime_start(&RUNTIME, AKBASIC_MODE_RUN));
PASS(errctx, akbasic_graphics_akgl_begin(&GRAPHICS));
PASS(errctx, akbasic_runtime_run(&RUNTIME, 0));
PASS(errctx, akbasic_graphics_akgl_end(&GRAPHICS));
TEST_REQUIRE_INT(AKGLSINKSTATE.graphicmode, 1);
TEST_REQUIRE_STR(AKGLSINKSTATE.text[0], "HIDDEN");
PASS(errctx, clear_target());
PASS(errctx, akbasic_graphics_akgl_render(&GRAPHICS));
PASS(errctx, akbasic_sink_akgl_render(&AKGLSINK));
shot = SDL_RenderReadPixels(akgl_renderer->sdl_renderer, NULL);
TEST_REQUIRE(shot != NULL, "could not read the full-bitmap frame back");
TEST_REQUIRE(pixel_is(shot, 40, 40, 0x88, 0x39, 0x32),
"GRAPHIC 1 must show DRAW even after PRINT wrote the hidden text grid");
SDL_DestroySurface(shot);
PASS(errctx, AKGLSINK.graphic(&AKGLSINK, 0, -1));
PASS(errctx, clear_target());
PASS(errctx, akbasic_graphics_akgl_render(&GRAPHICS));
PASS(errctx, akbasic_sink_akgl_render(&AKGLSINK));
shot = SDL_RenderReadPixels(akgl_renderer->sdl_renderer, NULL);
TEST_REQUIRE(shot != NULL, "could not read the text frame back");
TEST_REQUIRE(!pixel_is(shot, 40, 40, 0x88, 0x39, 0x32),
"GRAPHIC 0 must put the retained text plane back over the drawing");
SDL_DestroySurface(shot);
PASS(errctx, AKGLSINK.clear(&AKGLSINK));
fullrows = TARGET_SIZE / AKGLSINKSTATE.cellh;
PASS(errctx, AKGLSINK.graphic(&AKGLSINK, 2, 3));
TEST_REQUIRE_INT(AKGLSINKSTATE.y, 3 * AKGLSINKSTATE.cellh);
TEST_REQUIRE_INT(AKGLSINKSTATE.rows, fullrows - 3);
PASS(errctx, AKGLSINK.writeln(&AKGLSINK, "BOTTOM"));
PASS(errctx, clear_target());
PASS(errctx, akbasic_graphics_akgl_render(&GRAPHICS));
PASS(errctx, akbasic_sink_akgl_render(&AKGLSINK));
shot = SDL_RenderReadPixels(akgl_renderer->sdl_renderer, NULL);
TEST_REQUIRE(shot != NULL, "could not read the split-screen frame back");
TEST_REQUIRE(pixel_is(shot, 40, 40, 0x88, 0x39, 0x32),
"GRAPHIC 2 must leave the graphics region above its text rows visible");
SDL_DestroySurface(shot);
PASS(errctx, AKGLSINK.graphic(&AKGLSINK, 0, -1));
TEST_REQUIRE_INT(AKGLSINKSTATE.texttop, 0);
TEST_REQUIRE_INT(AKGLSINKSTATE.rows, fullrows);
akbasic_graphics_akgl_shutdown(&GRAPHICS);
SUCCEED_RETURN(errctx);
}
/**
* @brief The contact says which way to push out and how far.
*
@@ -1339,6 +1411,7 @@ int main(void)
CATCH(errctx, test_static_geometry());
CATCH(errctx, test_contact_geometry());
CATCH(errctx, test_drawing_layer_persists());
CATCH(errctx, test_graphic_text_composition());
} CLEANUP {
if ( font != NULL ) {
TTF_CloseFont(font);

View File

@@ -34,6 +34,22 @@ static akerr_ErrorContext AKERR_NOIGNORE *run_program(const char *source)
SUCCEED_RETURN(errctx);
}
/** @brief The values delivered through GRAPHIC's optional text-plane hook. */
static int GRAPHIC_CALLS = 0;
static int GRAPHIC_MODE = 0;
static int GRAPHIC_SPLIT = 0;
static akerr_ErrorContext AKERR_NOIGNORE *record_graphic(akbasic_TextSink *self, int mode, int split)
{
PREPARE_ERROR(errctx);
(void)self;
GRAPHIC_CALLS += 1;
GRAPHIC_MODE = mode;
GRAPHIC_SPLIT = split;
SUCCEED_RETURN(errctx);
}
/** @brief The white of palette index 2, which most of these draw with. */
#define WHITE "#ffffff"
/** @brief The red of palette index 3. */
@@ -323,6 +339,21 @@ static void test_graphic(void)
TEST_REQUIRE_STR(MOCK.log, "");
harness_stop();
/* The third argument reaches a graphical text sink as the first text row. */
TEST_REQUIRE_OK(harness_start(NULL));
mock_devices_init();
GRAPHIC_CALLS = 0;
HARNESS_SINK.graphic = record_graphic;
TEST_REQUIRE_OK(akbasic_runtime_set_devices(&HARNESS_RUNTIME, &MOCK_GRAPHICS,
&MOCK_AUDIO, &MOCK_INPUT, NULL));
TEST_REQUIRE_OK(akbasic_runtime_load(&HARNESS_RUNTIME, "10 GRAPHIC 2, 1, 19\n"));
TEST_REQUIRE_OK(akbasic_runtime_start(&HARNESS_RUNTIME, AKBASIC_MODE_RUN));
TEST_REQUIRE_OK(akbasic_runtime_run(&HARNESS_RUNTIME, 0));
TEST_REQUIRE_INT(GRAPHIC_CALLS, 1);
TEST_REQUIRE_INT(GRAPHIC_MODE, 2);
TEST_REQUIRE_INT(GRAPHIC_SPLIT, 19);
harness_stop();
/* GRAPHIC CLR drops the saved shapes and resets the whole state. */
TEST_REQUIRE_OK(run_program("10 COLOR 1, 3\n20 GRAPHIC 5\n"));
TEST_REQUIRE_STR(MOCK.log, "freeshapes\n");

View File

@@ -47,6 +47,9 @@ static akerr_ErrorContext AKERR_NOIGNORE *broken_clear(akbasic_TextSink *self)
static int MOVETOS = 0;
static int WINDOWS = 0;
static int WINDOW_ARGS[4] = { 0, 0, 0, 0 };
static int GRAPHICS = 0;
static int GRAPHIC_MODE = 0;
static int GRAPHIC_SPLIT = 0;
static akerr_ErrorContext AKERR_NOIGNORE *counting_moveto(akbasic_TextSink *self, int col, int row)
{
@@ -70,6 +73,17 @@ static akerr_ErrorContext AKERR_NOIGNORE *counting_window(akbasic_TextSink *self
SUCCEED_RETURN(errctx);
}
static akerr_ErrorContext AKERR_NOIGNORE *counting_graphic(akbasic_TextSink *self, int mode, int split)
{
PREPARE_ERROR(errctx);
(void)self;
GRAPHICS += 1;
GRAPHIC_MODE = mode;
GRAPHIC_SPLIT = split;
SUCCEED_RETURN(errctx);
}
int main(void)
{
akbasic_TextSink primary;
@@ -163,11 +177,15 @@ int main(void)
gridded.clear = broken_clear;
gridded.moveto = counting_moveto;
gridded.window = counting_window;
gridded.grid = NULL;
gridded.graphic = counting_graphic;
MOVETOS = 0;
WINDOWS = 0;
GRAPHICS = 0;
TEST_REQUIRE_OK(akbasic_sink_init_tee(&tee, &teestate, &primary, &gridded, NULL));
TEST_REQUIRE(tee.moveto != NULL, "a tee with a gridded half must offer moveto");
TEST_REQUIRE(tee.window != NULL, "a tee with a gridded half must offer window");
TEST_REQUIRE(tee.graphic != NULL, "a tee with a graphical half must offer graphic");
TEST_REQUIRE_OK(tee.moveto(&tee, 3, 4));
TEST_REQUIRE_OK(tee.window(&tee, 0, 20, 39, 24));
TEST_REQUIRE_INT(MOVETOS, 1);
@@ -176,6 +194,10 @@ int main(void)
TEST_REQUIRE_INT(WINDOW_ARGS[1], 20);
TEST_REQUIRE_INT(WINDOW_ARGS[2], 39);
TEST_REQUIRE_INT(WINDOW_ARGS[3], 24);
TEST_REQUIRE_OK(tee.graphic(&tee, 2, 19));
TEST_REQUIRE_INT(GRAPHICS, 1);
TEST_REQUIRE_INT(GRAPHIC_MODE, 2);
TEST_REQUIRE_INT(GRAPHIC_SPLIT, 19);
/* The gridded half being first is wired the same way round. */
WINDOWS = 0;
@@ -193,6 +215,10 @@ int main(void)
broken.writeln = broken_write;
broken.readline = NULL;
broken.clear = broken_clear;
broken.moveto = NULL;
broken.window = NULL;
broken.grid = NULL;
broken.graphic = NULL;
TEST_REQUIRE_OK(akbasic_sink_init_tee(&tee, &teestate, &primary, &broken, NULL));
TEST_REQUIRE_STATUS(tee.write(&tee, "X"), AKERR_IO);
TEST_REQUIRE_STATUS(tee.writeln(&tee, "X"), AKERR_IO);