Skip to content

Commit b4c00f2

Browse files
committed
Adds text input support
Pending review. Commited for saving.
1 parent c308bbe commit b4c00f2

File tree

5 files changed

+234
-3
lines changed

5 files changed

+234
-3
lines changed

src/input.c

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ input_init(Input *input)
3131
input->lastMouseY = 0;
3232
input->modKeyState = 0;
3333
input->lastModKeyState = 0;
34+
input->textInput[0] = '\0';
3435
}
3536

3637
void
@@ -44,6 +45,7 @@ input_reset(Input *input)
4445
input->modKeyState = 0;
4546
input->lastMouseX = input->mouseX;
4647
input->lastMouseY = input->mouseY;
48+
input->textInput[0] = '\0';
4749
}
4850

4951
static Uint64
@@ -101,6 +103,9 @@ get_event_key(SDL_Event *event)
101103
case SDLK_TAB:
102104
key = KEY_TAB;
103105
break;
106+
case SDLK_BACKSPACE:
107+
key = KEY_BACKSPACE;
108+
break;
104109
default:
105110
key = 0;
106111
break;
@@ -195,6 +200,9 @@ get_event_modkey(SDL_Event *event)
195200
case SDLK_F:
196201
key = KEY_CTRL_F;
197202
break;
203+
case SDLK_V:
204+
key = KEY_CTRL_V;
205+
break;
198206
}
199207
} else if (event->key.mod & (SDL_KMOD_LSHIFT | SDL_KMOD_RSHIFT)) {
200208
switch (event->key.key) {
@@ -278,6 +286,10 @@ input_handle_event(Input *input, SDL_Event *event, InputDeviceType *device_type)
278286
} else {
279287
input->keyState &= ~get_axis_motion(event);
280288
}
289+
} else if (event->type == SDL_EVENT_TEXT_INPUT) {
290+
// Copy the string and ensure null-termination
291+
strncpy(input->textInput, event->text.text, TEXT_INPUT_MAX_LEN - 1);
292+
input->textInput[TEXT_INPUT_MAX_LEN - 1] = '\0';
281293
}
282294

283295
if (device_type != NULL) {

src/input.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
#define KEY_ENTER 0x8000
4141
#define KEY_SPACE 0x10000
4242
#define KEY_TAB 0x20000
43+
#define KEY_BACKSPACE 0x40000
4344

4445
#define KEY_CTRL_M 0x1
4546
#define KEY_CTRL_S 0x2
@@ -50,11 +51,14 @@
5051
#define KEY_SHIFT_NUM4 0x40
5152
#define KEY_SHIFT_NUM5 0x80
5253
#define KEY_CTRL_F 0x100
54+
#define KEY_CTRL_V 0x200
5355

5456
#define MBUTTON_LEFT 0x1
5557
#define MBUTTON_MIDDLE 0x2
5658
#define MBUTTON_RIGHT 0x4
5759

60+
#define TEXT_INPUT_MAX_LEN 16
61+
5862
typedef enum InputDeviceType { DeviceType_Unknown, DeviceType_Keyboard, DeviceType_Gamepad } InputDeviceType;
5963

6064
typedef struct Input {
@@ -68,6 +72,7 @@ typedef struct Input {
6872
Uint32 lastMouseY;
6973
Uint32 mouseX;
7074
Uint32 mouseY;
75+
char textInput[TEXT_INPUT_MAX_LEN];
7176
} Input;
7277

7378
void input_init(Input *);

src/main.c

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@
5656
#include "event.h"
5757
#include "config.h"
5858
#include "save.h"
59+
#include "text_input.h"
5960

6061
#ifdef DEBUG
6162
#include "debug/debug.h"
@@ -104,6 +105,7 @@ static SDL_Rect statsGuiViewport;
104105
static SDL_Rect minimapViewport;
105106
static SDL_Rect menuViewport;
106107
static Input input;
108+
static unsigned int gCustomSeed = 0;
107109

108110
#ifdef DEBUG
109111
static Sprite *fpsSprite = NULL;
@@ -262,6 +264,8 @@ startGame(void)
262264

263265
if (weeklyGame)
264266
set_random_seed((unsigned int)time_get_weekly_seed());
267+
else if (gCustomSeed)
268+
set_random_seed(gCustomSeed);
265269
else
266270
set_random_seed(0);
267271

@@ -475,8 +479,8 @@ static void
475479
openSeedEntry(void *unused)
476480
{
477481
(void)unused;
482+
text_input_init(gWindow, gRenderer, "Enter game seed:");
478483
gGameState = SEED_ENTRY;
479-
// TODO: Create this view...
480484
}
481485

482486
static void
@@ -490,7 +494,7 @@ showHowToTooltip(void *unused)
490494
static void
491495
copySeedToClipboard(void *unused)
492496
{
493-
UNUSED(unused);
497+
(void)unused;
494498
char seed_str[16];
495499
SDL_snprintf(seed_str, sizeof(seed_str), "%u", get_random_seed());
496500
SDL_SetClipboardText(seed_str);
@@ -794,6 +798,10 @@ handle_main_input(void)
794798
charSelectMenu = NULL;
795799
gGameState = GAME_SELECT;
796800
break;
801+
case SEED_ENTRY:
802+
text_input_close(gWindow);
803+
gGameState = MENU;
804+
break;
797805
case MENU:
798806
gGameState = QUIT;
799807
break;
@@ -810,8 +818,18 @@ handle_main_input(void)
810818
}
811819
}
812820

821+
if (gGameState == SEED_ENTRY && text_input_is_confirmed()) {
822+
const char *val = text_input_get_value();
823+
if (SDL_strlen(val) > 0) {
824+
gCustomSeed = (unsigned int)SDL_atoi(val);
825+
debug("Custom seed set: %u", gCustomSeed);
826+
}
827+
text_input_close(gWindow);
828+
gGameState = MENU;
829+
}
830+
813831
handle_settings_input();
814-
if (input_key_is_pressed(&input, KEY_TAB)) {
832+
if ((gGameState == PLAYING || gGameState == GAME_OVER) && input_key_is_pressed(&input, KEY_TAB)) {
815833
gShowMap = !gShowMap;
816834
}
817835
}
@@ -1249,6 +1267,15 @@ run_menu(void)
12491267
SDL_RenderPresent(gRenderer);
12501268
}
12511269

1270+
static void
1271+
run_seed_entry(void)
1272+
{
1273+
SDL_SetRenderViewport(gRenderer, &mainViewport);
1274+
text_input_update(&input);
1275+
text_input_render(gCamera);
1276+
SDL_RenderPresent(gRenderer);
1277+
}
1278+
12521279
static void
12531280
run(void)
12541281
{
@@ -1294,6 +1321,9 @@ run(void)
12941321
case CHARACTER_MENU:
12951322
run_menu();
12961323
break;
1324+
case SEED_ENTRY:
1325+
run_seed_entry();
1326+
break;
12971327
case QUIT:
12981328
quit = true;
12991329
break;

src/text_input.c

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
/*
2+
* BreakHack - A dungeone crawler RPG
3+
* Copyright (C) 2025 Linus Probert <linus.probert@gmail.com>
4+
*
5+
* This program is free software: you can redistribute it and/or modify
6+
* it under the terms of the GNU General Public License as published by
7+
* the Free Software Foundation, either version 3 of the License, or
8+
* (at your option) any later version.
9+
*
10+
* This program is distributed in the hope that it will be useful,
11+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
* GNU General Public License for more details.
14+
*
15+
* You should have received a copy of the GNU General Public License
16+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
17+
*/
18+
19+
#include "text_input.h"
20+
#include "SDL3/SDL_clipboard.h"
21+
#include "sprite.h"
22+
#include "defines.h"
23+
24+
static char seedInputBuffer[16] = {0};
25+
static Sprite *seedInputSprite = NULL;
26+
static Sprite *seedHeadingSprite = NULL;
27+
static Sprite *seedHintSprite = NULL;
28+
static SDL_Renderer *gRenderer = NULL;
29+
static bool gInputConfirmed = false;
30+
31+
void
32+
text_input_init(SDL_Window *window, SDL_Renderer *renderer, const char *title)
33+
{
34+
gRenderer = renderer;
35+
gInputConfirmed = false;
36+
37+
// Clear the buffer
38+
memset(seedInputBuffer, '\0', sizeof(seedInputBuffer));
39+
SDL_StartTextInput(window);
40+
41+
if (seedHeadingSprite == NULL) {
42+
seedHeadingSprite = sprite_create();
43+
sprite_load_text_texture(seedHeadingSprite, "GUI/SDS_8x8.ttf", 0, 18, 1);
44+
texture_load_from_text(seedHeadingSprite->textures[0], title, C_BLUE, C_BLACK, renderer);
45+
seedHeadingSprite->dim = seedHeadingSprite->textures[0]->dim;
46+
seedHeadingSprite->pos =
47+
(Position){(SCREEN_WIDTH - seedHeadingSprite->dim.width) >> 1, SCREEN_HEIGHT / 2 - 40};
48+
seedHeadingSprite->fixed = true;
49+
}
50+
51+
if (seedInputSprite == NULL) {
52+
seedInputSprite = sprite_create();
53+
sprite_load_text_texture(seedInputSprite, "GUI/SDS_8x8.ttf", 0, 18, 1);
54+
seedInputSprite->fixed = true;
55+
}
56+
57+
if (seedHintSprite == NULL) {
58+
seedHintSprite = sprite_create();
59+
sprite_load_text_texture(seedHintSprite, "GUI/SDS_8x8.ttf", 0, 10, 1);
60+
texture_load_from_text(seedHintSprite->textures[0], "ENTER to confirm | ESC to cancel", C_WHITE,
61+
C_BLACK, renderer);
62+
seedHintSprite->dim = seedHintSprite->textures[0]->dim;
63+
seedHintSprite->pos = (Position){15, SCREEN_HEIGHT - 25};
64+
seedHintSprite->fixed = true;
65+
}
66+
}
67+
68+
static void
69+
parseBufferToInput(const char *buffer)
70+
{
71+
for (const char *c = buffer; *c; c++) {
72+
if (*c >= '0' && *c <= '9') {
73+
size_t len = SDL_strlen(seedInputBuffer);
74+
if (len < sizeof(seedInputBuffer) - 1) {
75+
seedInputBuffer[len] = *c;
76+
seedInputBuffer[len + 1] = '\0';
77+
}
78+
}
79+
}
80+
}
81+
82+
void
83+
text_input_update(Input *input)
84+
{
85+
if (input->textInput[0] != '\0') {
86+
parseBufferToInput(input->textInput);
87+
} else if (input_key_is_pressed(input, KEY_BACKSPACE)) {
88+
size_t len = SDL_strlen(seedInputBuffer);
89+
if (len > 0) {
90+
seedInputBuffer[len - 1] = '\0';
91+
}
92+
} else if (input_key_is_pressed(input, KEY_ENTER)) {
93+
gInputConfirmed = true;
94+
} else if (input_modkey_is_pressed(input, KEY_CTRL_V)) {
95+
char *cb_text = SDL_GetClipboardText();
96+
parseBufferToInput(cb_text);
97+
SDL_free(cb_text);
98+
}
99+
}
100+
101+
bool
102+
text_input_is_confirmed(void)
103+
{
104+
return gInputConfirmed;
105+
}
106+
107+
const char *
108+
text_input_get_value(void)
109+
{
110+
return seedInputBuffer;
111+
}
112+
113+
void
114+
text_input_render(Camera *cam)
115+
{
116+
SDL_FRect bg = {0, 0, SCREEN_WIDTH, SCREEN_HEIGHT};
117+
SDL_SetRenderDrawColor(cam->renderer, 0, 0, 0, 175);
118+
SDL_RenderFillRect(cam->renderer, &bg);
119+
120+
sprite_render(seedHeadingSprite, cam);
121+
122+
char displayBuf[20];
123+
SDL_snprintf(displayBuf, sizeof(displayBuf), "%s_", seedInputBuffer);
124+
texture_load_from_text(seedInputSprite->textures[0], displayBuf, C_YELLOW, C_BLACK, gRenderer);
125+
seedInputSprite->dim = seedInputSprite->textures[0]->dim;
126+
seedInputSprite->pos = (Position){(SCREEN_WIDTH - seedInputSprite->dim.width) >> 1, SCREEN_HEIGHT / 2};
127+
sprite_render(seedInputSprite, cam);
128+
129+
sprite_render(seedHintSprite, cam);
130+
}
131+
132+
void
133+
text_input_close(SDL_Window *window)
134+
{
135+
SDL_StopTextInput(window);
136+
137+
if (seedHeadingSprite) {
138+
sprite_destroy(seedHeadingSprite);
139+
seedHeadingSprite = NULL;
140+
}
141+
if (seedInputSprite) {
142+
sprite_destroy(seedInputSprite);
143+
seedInputSprite = NULL;
144+
}
145+
if (seedHintSprite) {
146+
sprite_destroy(seedHintSprite);
147+
seedHintSprite = NULL;
148+
}
149+
}

src/text_input.h

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/*
2+
* BreakHack - A dungeone crawler RPG
3+
* Copyright (C) 2026 Linus Probert <linus.probert@gmail.com>
4+
*
5+
* This program is free software: you can redistribute it and/or modify
6+
* it under the terms of the GNU General Public License as published by
7+
* the Free Software Foundation, either version 3 of the License, or
8+
* (at your option) any later version.
9+
*
10+
* This program is distributed in the hope that it will be useful,
11+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
* GNU General Public License for more details.
14+
*
15+
* You should have received a copy of the GNU General Public License
16+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
17+
*/
18+
19+
#pragma once
20+
21+
#include <SDL3/SDL.h>
22+
#include "camera.h"
23+
#include "input.h"
24+
25+
void text_input_init(SDL_Window *window, SDL_Renderer *renderer, const char *title);
26+
27+
void text_input_update(Input *input);
28+
29+
bool text_input_is_confirmed(void);
30+
31+
const char *text_input_get_value(void);
32+
33+
void text_input_render(Camera *cam);
34+
35+
void text_input_close(SDL_Window *window);

0 commit comments

Comments
 (0)