summaryrefslogtreecommitdiff
path: root/examples/dialog.c
diff options
context:
space:
mode:
authorKonAki <konaki@anokonaki.org>2026-10-12 13:45:27 +0800
committerKonAki <konaki@anokonaki.org>2026-10-12 13:45:27 +0800
commit47262912ac4b644806700906f3258e8a32a54938 (patch)
tree1f358cb2cb0c8dbec0a97571e0e29d7bde61e749 /examples/dialog.c
parentac6f4a20bb4d5782d61967c61c9d47d52d2c869e (diff)
add dialog example, wire the new modules into the build and docs
Diffstat (limited to 'examples/dialog.c')
-rw-r--r--examples/dialog.c260
1 files changed, 260 insertions, 0 deletions
diff --git a/examples/dialog.c b/examples/dialog.c
new file mode 100644
index 0000000..51fe369
--- /dev/null
+++ b/examples/dialog.c
@@ -0,0 +1,260 @@
+/*
+ * dialog: a little scene with sprites on a stage, a text box in front of them and a menu of buttons in the middle
+ *
+ * build: ./build.sh -d -m 2d,stage,textbox,font obj/dialog
+ * run: obj/dialog
+ *
+ * space, enter or a click goes on, up and down or the mouse pick in the menu, l puts Bo in front of the text box and back again
+ */
+
+#include <stdio.h>
+
+#include "konprogram.h"
+#include "kon2d.h"
+#include "konstage.h"
+#include "kontextbox.h"
+
+#if !defined(KON_MODULE_2D) || !defined(KON_MODULE_STAGE) || !defined(KON_MODULE_TEXTBOX) || !defined(KON_MODULE_FONT)
+#error "build with: ./build.sh -d -m 2d,stage,textbox,font obj/dialog"
+#endif
+
+#define VIEW_WIDTH 320
+#define VIEW_HEIGHT 180
+
+#define COL_BACKGROUND 0xFF1B1826u
+#define COL_PANEL 0xFF26223Au
+#define COL_PINK 0xFFF2A7C3u
+#define COL_PINK_DARK 0xFFB5738Fu
+#define COL_TEAL 0xFF8FD3C8u
+#define COL_TEAL_DARK 0xFF5A9A90u
+#define COL_SAND 0xFFF3DDA0u
+
+enum { ADA, BO, SPRITES };
+
+typedef struct state {
+ kon_stage_t stage;
+ kon_stageSprite_t sprites[4];
+ kon_textbox_t box, menu;
+ kon_image *back[2], *block[SPRITES];
+ int who[SPRITES];
+ int beat, background;
+} state_t;
+
+/*** pictures ***/
+
+static kon_image *makeBlock(int width, int height, uint32_t fill, uint32_t edge)
+{
+ kon_image *image = kon_createFramebuffer(width, height);
+
+ if (!image) return NULL;
+ kon_clearFramebuffer(image, edge);
+ kon_fillRectangle(image, 3, 3, width - 6, height - 6, fill);
+ return image;
+}
+
+static kon_image *makeGradient(uint32_t top, uint32_t bottom)
+{
+ kon_image *image = kon_createFramebuffer(2, VIEW_HEIGHT);
+ int x, y;
+
+ if (!image) return NULL;
+ for (y = 0; y < VIEW_HEIGHT; y++) {
+ uint32_t c = 0xFF000000u;
+ int shift;
+
+ for (shift = 0; shift < 24; shift += 8) {
+ int a = (int)((top >> shift) & 0xFF), b = (int)((bottom >> shift) & 0xFF);
+
+ c |= (uint32_t)(a + (b - a) * y / (VIEW_HEIGHT - 1)) << shift;
+ }
+ for (x = 0; x < 2; x++) image->data[y * 2 + x] = c;
+ }
+ return image;
+}
+
+/*** the scene ***/
+
+static void say(state_t *state, int who, const char *text)
+{
+ kon_textboxSay(&state->box, who == ADA ? "Ada" : "Bo", text);
+ kon_stageTween(&state->stage, state->who[ADA], KON_PROP_BRIGHTNESS, who == ADA ? 1.0f : 0.55f, 0.2f, KON_EASE_LINEAR);
+ if (state->who[BO] >= 0) kon_stageTween(&state->stage, state->who[BO], KON_PROP_BRIGHTNESS, who == BO ? 1.0f : 0.55f, 0.2f, KON_EASE_LINEAR);
+}
+
+static void enter(state_t *state, int who, kon_slot_t slot, float fromX, kon_layer_t layer)
+{
+ int i = kon_stageAdd(&state->stage, state->block[who], NULL, layer);
+
+ if (i < 0) return;
+ state->who[who] = i;
+ kon_stageMove(&state->stage, i, fromX, VIEW_HEIGHT, 0.0f, KON_EASE_LINEAR);
+ kon_stageFade(&state->stage, i, 0.0f, 0.0f, KON_EASE_LINEAR);
+ kon_stageFade(&state->stage, i, 1.0f, 0.5f, KON_EASE_OUT);
+ kon_stagePlace(&state->stage, i, slot, 0.6f, KON_EASE_OUT);
+}
+
+static void beat(state_t *state)
+{
+ static const char *labels[3] = {"swap the background", "move Ada to the middle", "leave"};
+
+ switch (state->beat++) {
+ case 0:
+ kon_stageBackground(&state->stage, state->back[0], KON_FIT_STRETCH, 0.0f);
+ state->background = 0;
+ enter(state, ADA, KON_SLOT_LEFT, -30.0f, KON_LAYER_BACK);
+ state->who[BO] = -1;
+ say(state, ADA, "Hello. The box is see through, and we both stand behind it.");
+ break;
+ case 1:
+ enter(state, BO, KON_SLOT_FAR_RIGHT, VIEW_WIDTH + 30.0f, KON_LAYER_BACK);
+ say(state, BO, "Press l and I come out in front of it. Press it again to go back.");
+ break;
+ case 2:
+ say(state, ADA, "The letters arrive one at a time, and a message that does not fit goes on a second page. Press the action key to hurry a page up.");
+ break;
+ case 3:
+ kon_textboxChoices(&state->menu, labels, 3);
+ kon_textboxFitChoices(&state->menu, 170);
+ kon_textboxCenter(&state->menu, VIEW_WIDTH, 0, 1, 0);
+ kon_textboxMove(&state->menu, kon_tweenValue(&state->menu.x), (float)((int)kon_tweenValue(&state->box.y) - state->menu.height) / 2.0f, 0.0f, KON_EASE_LINEAR);
+ break;
+ case 4:
+ say(state, BO, "Goodbye.");
+ break;
+ case 5:
+ kon_stageHide(&state->stage, state->who[ADA], 0.6f);
+ kon_stageHide(&state->stage, state->who[BO], 0.6f);
+ break;
+ default:
+ state->beat = 0;
+ break;
+ }
+}
+
+static void choose(state_t *state, int index)
+{
+ state->beat = 3;
+ if (index == 0) {
+ state->background = !state->background;
+ kon_stageBackground(&state->stage, state->back[state->background], KON_FIT_STRETCH, 1.0f);
+ say(state, ADA, "A one second cross fade.");
+ } else if (index == 1) {
+ kon_stagePlace(&state->stage, state->who[ADA], KON_SLOT_CENTER, 0.8f, KON_EASE_INOUT);
+ say(state, ADA, "Eased at both ends.");
+ } else {
+ state->beat = 4;
+ say(state, BO, "Already?");
+ }
+}
+
+static void action(state_t *state)
+{
+ if (state->menu.state == KON_TEXTBOX_CHOOSING) choose(state, kon_textboxConfirm(&state->menu));
+ else kon_textboxAdvance(&state->box);
+}
+
+/*** program ***/
+
+static int event(kon_program_t *program, const kon_event_t *ev, void *user)
+{
+ state_t *state = user;
+
+ if (ev->type == KON_EVENT_KEY_DOWN) {
+ if (ev->key == KON_KEY_SPACE || ev->key == KON_KEY_ENTER) action(state);
+ else if (ev->key == KON_KEY_L && state->who[BO] >= 0) kon_stageLayer(&state->stage, state->who[BO], state->sprites[state->who[BO]].layer == KON_LAYER_BACK ? KON_LAYER_FRONT : KON_LAYER_BACK);
+ else if (ev->key == KON_KEY_UP) kon_textboxSelect(&state->menu, -1);
+ else if (ev->key == KON_KEY_DOWN) kon_textboxSelect(&state->menu, 1);
+ else return 0;
+ } else if (ev->type == KON_EVENT_MOUSE_MOVE) {
+ int x, y;
+
+ if (kon_windowToGame(program->display, ev->mouseX, ev->mouseY, &x, &y)) kon_textboxHighlight(&state->menu, kon_textboxChoiceAt(&state->menu, x, y));
+ } else if (ev->type == KON_EVENT_MOUSE_DOWN && ev->mouseButton == KON_MOUSE_LEFT) {
+ int x, y;
+
+ if (state->menu.state == KON_TEXTBOX_CHOOSING) {
+ if (kon_windowToGame(program->display, ev->mouseX, ev->mouseY, &x, &y) && kon_textboxChoiceAt(&state->menu, x, y) >= 0) action(state);
+ } else {
+ action(state);
+ }
+ } else {
+ return 0;
+ }
+ return 1;
+}
+
+static void update(kon_program_t *program, double dt, void *user)
+{
+ state_t *state = user;
+
+ (void)program;
+ kon_stageUpdate(&state->stage, (float)dt);
+ kon_textboxUpdate(&state->box, (float)dt);
+ kon_textboxUpdate(&state->menu, (float)dt);
+ if (state->box.state == KON_TEXTBOX_EMPTY && state->menu.state == KON_TEXTBOX_EMPTY && !kon_stageBusy(&state->stage)) beat(state);
+}
+
+static void draw(kon_program_t *program, kon_framebuffer_t *game, void *user)
+{
+ state_t *state = user;
+
+ (void)program;
+ kon_clearFramebuffer(game, COL_BACKGROUND);
+ kon_stageDrawBackground(&state->stage, game);
+ kon_stageDraw(&state->stage, game, KON_LAYER_BACK);
+ kon_textboxDraw(&state->box, game);
+ kon_stageDraw(&state->stage, game, KON_LAYER_FRONT);
+ kon_textboxDraw(&state->menu, game);
+}
+
+static int init(kon_program_t *program, void *user)
+{
+ state_t *state = user;
+
+ state->back[0] = makeGradient(0xFF2E2848u, COL_BACKGROUND);
+ state->back[1] = makeGradient(0xFF2A3A44u, 0xFF1B1826u);
+ state->block[ADA] = makeBlock(64, 120, COL_PINK, COL_PINK_DARK);
+ state->block[BO] = makeBlock(64, 132, COL_TEAL, COL_TEAL_DARK);
+ if (!state->back[0] || !state->back[1] || !state->block[ADA] || !state->block[BO]) return 0;
+
+ kon_stageInit(&state->stage, state->sprites, 4, VIEW_WIDTH, VIEW_HEIGHT);
+ state->who[ADA] = state->who[BO] = -1;
+
+ kon_textboxInit(&state->box, program->font, 12, 118, VIEW_WIDTH - 24, 52);
+ state->box.panel.fill = 0xA026223Au;
+ state->box.plate.fill = 0xE0332E4Eu;
+ kon_textboxInit(&state->menu, program->font, 0, 0, 170, 40);
+ state->menu.choiceMode = KON_CHOICES_BUTTONS;
+ state->menu.choiceAlign = KON_ALIGN_CENTER;
+ state->menu.buttonSelected.fill = COL_SAND;
+ return 1;
+}
+
+static void shutdown(kon_program_t *program, void *user)
+{
+ state_t *state = user;
+ int i;
+
+ (void)program;
+ for (i = 0; i < 2; i++) kon_freeFramebuffer(state->back[i]);
+ for (i = 0; i < SPRITES; i++) kon_freeFramebuffer(state->block[i]);
+}
+
+int main(void)
+{
+ state_t state = {0};
+ kon_programConfig_t config = {0};
+
+ config.title = "dialog";
+ config.gameWidth = VIEW_WIDTH;
+ config.gameHeight = VIEW_HEIGHT;
+ config.windowWidth = 1280;
+ config.windowHeight = 720;
+ config.user = &state;
+ config.init = init;
+ config.event = event;
+ config.update = update;
+ config.draw = draw;
+ config.shutdown = shutdown;
+ return kon_programRun(&config);
+}