126 lines
3.9 KiB
C
126 lines
3.9 KiB
C
|
|
/**
|
||
|
|
* @file textbox.c
|
||
|
|
* @brief The dialogue panel: two rectangles and a string, drawn over the world.
|
||
|
|
*
|
||
|
|
* Text in libakgl is immediate mode. Every akgl_text_rendertextat call
|
||
|
|
* rasterizes the string through SDL_ttf, uploads it as a texture, blits it and
|
||
|
|
* destroys the texture again -- there is no cached glyph atlas and no text
|
||
|
|
* object to hold on to. That is the wrong shape for a page of static prose
|
||
|
|
* redrawn sixty times a second and exactly the right shape for this: one short
|
||
|
|
* line, on screen only while somebody is talking.
|
||
|
|
*/
|
||
|
|
|
||
|
|
#include <stdbool.h>
|
||
|
|
|
||
|
|
#include <SDL3/SDL.h>
|
||
|
|
#include <SDL3_ttf/SDL_ttf.h>
|
||
|
|
#include <akerror.h>
|
||
|
|
#include <akstdlib.h>
|
||
|
|
|
||
|
|
#include <akgl/draw.h>
|
||
|
|
#include <akgl/error.h>
|
||
|
|
#include <akgl/game.h>
|
||
|
|
#include <akgl/registry.h>
|
||
|
|
#include <akgl/text.h>
|
||
|
|
|
||
|
|
#include "jrpg.h"
|
||
|
|
|
||
|
|
/*
|
||
|
|
* Colour R G B A
|
||
|
|
*/
|
||
|
|
static const SDL_Color TEXTBOX_FILL = { 24, 20, 37, 235 };
|
||
|
|
static const SDL_Color TEXTBOX_EDGE = { 240, 236, 214, 255 };
|
||
|
|
static const SDL_Color TEXTBOX_INK = { 240, 236, 214, 255 };
|
||
|
|
|
||
|
|
/** @brief Pixels between the panel and the edges of the screen. */
|
||
|
|
#define TEXTBOX_MARGIN 8.0f
|
||
|
|
/** @brief Panel height in pixels. Two lines of 12pt monospace, plus padding. */
|
||
|
|
#define TEXTBOX_HEIGHT 56.0f
|
||
|
|
/** @brief Pixels between the panel's edge and the text inside it. */
|
||
|
|
#define TEXTBOX_PADDING 8.0f
|
||
|
|
|
||
|
|
static char textbox_text[JRPG_TEXTBOX_MAX_TEXT];
|
||
|
|
static bool textbox_visible = false;
|
||
|
|
|
||
|
|
bool jrpg_textbox_showing(void)
|
||
|
|
{
|
||
|
|
return textbox_visible;
|
||
|
|
}
|
||
|
|
|
||
|
|
void jrpg_textbox_close(void)
|
||
|
|
{
|
||
|
|
textbox_visible = false;
|
||
|
|
}
|
||
|
|
|
||
|
|
akerr_ErrorContext *jrpg_textbox_open(char *text)
|
||
|
|
{
|
||
|
|
PREPARE_ERROR(errctx);
|
||
|
|
|
||
|
|
FAIL_ZERO_RETURN(errctx, text, AKERR_NULLPOINTER, "text");
|
||
|
|
// aksl_strncpy, not strncpy: this is a fixed-width field, and strncpy at
|
||
|
|
// exactly the field width leaves it unterminated. Bounded at one less than
|
||
|
|
// the field so an over-long line truncates rather than being refused.
|
||
|
|
PASS(errctx,
|
||
|
|
aksl_strncpy(
|
||
|
|
textbox_text,
|
||
|
|
sizeof(textbox_text),
|
||
|
|
text,
|
||
|
|
sizeof(textbox_text) - 1
|
||
|
|
));
|
||
|
|
textbox_visible = true;
|
||
|
|
SUCCEED_RETURN(errctx);
|
||
|
|
}
|
||
|
|
|
||
|
|
akerr_ErrorContext *jrpg_textbox_draw(void)
|
||
|
|
{
|
||
|
|
PREPARE_ERROR(errctx);
|
||
|
|
TTF_Font *font = NULL;
|
||
|
|
SDL_FRect panel;
|
||
|
|
|
||
|
|
if ( textbox_visible == false ) {
|
||
|
|
SUCCEED_RETURN(errctx);
|
||
|
|
}
|
||
|
|
// akgl_text_rendertextat refuses the empty string -- SDL_ttf reports "Text
|
||
|
|
// has zero width" and the library passes that on as AKERR_NULLPOINTER,
|
||
|
|
// while akgl_text_measure accepts it. The two disagree, so anything drawing
|
||
|
|
// a line that might be empty checks for it. TODO.md, "Known and still
|
||
|
|
// open".
|
||
|
|
if ( textbox_text[0] == '\0' ) {
|
||
|
|
SUCCEED_RETURN(errctx);
|
||
|
|
}
|
||
|
|
|
||
|
|
// Fonts live in a registry under a name, not in an akgl type. There is no
|
||
|
|
// handle to keep, and no reference counting either: whoever calls
|
||
|
|
// akgl_text_unloadfont invalidates every pointer anybody else fetched.
|
||
|
|
// Fetching it per frame is the cheap way to stay honest about that.
|
||
|
|
font = SDL_GetPointerProperty(AKGL_REGISTRY_FONT, JRPG_FONT_NAME, NULL);
|
||
|
|
FAIL_ZERO_RETURN(
|
||
|
|
errctx,
|
||
|
|
font,
|
||
|
|
AKGL_ERR_REGISTRY,
|
||
|
|
"No font registered as \"%s\"",
|
||
|
|
JRPG_FONT_NAME
|
||
|
|
);
|
||
|
|
|
||
|
|
// Screen coordinates, from the camera's size rather than from a second copy
|
||
|
|
// of the window dimensions. akgl_render_2d_init put them there.
|
||
|
|
panel.x = TEXTBOX_MARGIN;
|
||
|
|
panel.w = akgl_camera->w - (2.0f * TEXTBOX_MARGIN);
|
||
|
|
panel.h = TEXTBOX_HEIGHT;
|
||
|
|
panel.y = akgl_camera->h - TEXTBOX_MARGIN - panel.h;
|
||
|
|
|
||
|
|
PASS(errctx, akgl_draw_filled_rect(akgl_renderer, &panel, TEXTBOX_FILL));
|
||
|
|
PASS(errctx, akgl_draw_rect(akgl_renderer, &panel, TEXTBOX_EDGE));
|
||
|
|
PASS(errctx,
|
||
|
|
akgl_text_rendertextat(
|
||
|
|
font,
|
||
|
|
textbox_text,
|
||
|
|
TEXTBOX_INK,
|
||
|
|
(int)(panel.w - (2.0f * TEXTBOX_PADDING)),
|
||
|
|
(int)(panel.x + TEXTBOX_PADDING),
|
||
|
|
(int)(panel.y + TEXTBOX_PADDING)
|
||
|
|
));
|
||
|
|
|
||
|
|
SUCCEED_RETURN(errctx);
|
||
|
|
}
|