]> git.lizzy.rs Git - nothing.git/blob - src/ui/console.c
909189561b6b792f4b5d9bb94e8264fd110ba882
[nothing.git] / src / ui / console.c
1 #include <ctype.h>
2 #include <math.h>
3
4 #include "system/stacktrace.h"
5
6 #include "game.h"
7 #include "game/level.h"
8 #include "sdl/renderer.h"
9 #include "system/log.h"
10 #include "system/lt.h"
11 #include "system/nth_alloc.h"
12 #include "ui/console.h"
13 #include "ui/console_log.h"
14 #include "ui/edit_field.h"
15 #include "ui/history.h"
16 #include "math/extrema.h"
17
18 #define FONT_WIDTH_SCALE 3.0f
19 #define FONT_HEIGHT_SCALE 3.0f
20
21 #define CONSOLE_LOG_CAPACITY 10
22 #define HISTORY_CAPACITY 20
23 #define PROMPT_HEIGHT (FONT_HEIGHT_SCALE * FONT_CHAR_HEIGHT)
24 #define CONSOLE_LOG_HEIGHT (FONT_HEIGHT_SCALE * FONT_CHAR_HEIGHT * CONSOLE_LOG_CAPACITY)
25
26 #define CONSOLE_HEIGHT (CONSOLE_LOG_HEIGHT + PROMPT_HEIGHT)
27
28 #define SLIDE_DOWN_TIME 0.4f
29
30 #define CONSOLE_ALPHA (0.80f)
31 #define CONSOLE_BACKGROUND (rgba(0.20f, 0.20f, 0.20f, CONSOLE_ALPHA))
32 #define CONSOLE_FOREGROUND (rgba(0.80f, 0.80f, 0.80f, CONSOLE_ALPHA))
33 #define CONSOLE_ERROR (rgba(0.80f, 0.50f, 0.50f, CONSOLE_ALPHA))
34
35 typedef struct {
36     const char *begin;
37     const char *end;
38 } Token;
39
40
41 static inline
42 Token token(const char *begin, const char *end)
43 {
44     Token t = {begin, end};
45     return t;
46 }
47
48 static inline
49 int token_equals_str(Token t, const char *s)
50 {
51     trace_assert(t.begin <= t.end);
52     size_t n1 = (size_t) (t.end - t.begin);
53     size_t n2 = strlen(s);
54     if (n1 != n2) return 0;
55     return memcmp(t.begin, s, n1) == 0;
56 }
57
58 static inline
59 Token token_nt(const char *s)
60 {
61     return token(s, s + strlen(s));
62 }
63
64 static inline
65 void ltrim(Token *t)
66 {
67     while (t->begin < t->end && isspace(*t->begin)) {
68         t->begin++;
69     }
70 }
71
72 static inline
73 Token chop_word(Token *t)
74 {
75     ltrim(t);
76     const char *end = t->begin;
77     while (end < t->end && !isspace(*end)) {
78         end++;
79     }
80     Token result = token(t->begin, end);
81     t->begin = end;
82     return result;
83 }
84
85 struct Console
86 {
87     Lt *lt;
88     Edit_field *edit_field;
89     Console_Log *console_log;
90     History *history;
91     Game *game;
92     float a;
93 };
94
95 /* TODO(#356): Console does not support autocompletion */
96
97 Console *create_console(Game *game)
98 {
99     Lt *lt = create_lt();
100
101     Console *console = PUSH_LT(lt, nth_calloc(1, sizeof(Console)), free);
102     if (console == NULL) {
103         RETURN_LT(lt, NULL);
104     }
105     console->lt = lt;
106
107     console->edit_field = PUSH_LT(
108         lt,
109         create_edit_field(
110             vec(FONT_WIDTH_SCALE, FONT_HEIGHT_SCALE),
111             CONSOLE_FOREGROUND),
112         destroy_edit_field);
113     if (console->edit_field == NULL) {
114         RETURN_LT(lt, NULL);
115     }
116
117     console->console_log = PUSH_LT(
118         lt,
119         create_console_log(
120             vec(FONT_WIDTH_SCALE, FONT_HEIGHT_SCALE),
121             CONSOLE_LOG_CAPACITY),
122         destroy_console_log);
123
124     console->a = 0;
125
126     console->history = PUSH_LT(
127         lt,
128         create_history(HISTORY_CAPACITY),
129         destroy_history);
130     if (console->history == NULL) {
131         RETURN_LT(lt, NULL);
132     }
133
134     console->game = game;
135
136     return console;
137 }
138
139 void destroy_console(Console *console)
140 {
141     trace_assert(console);
142     RETURN_LT0(console->lt);
143 }
144
145 static int console_eval_input(Console *console)
146 {
147     const char *input_text = edit_field_as_text(console->edit_field);
148
149     Token input = token_nt(input_text);
150     Token command = chop_word(&input);
151
152     if (token_equals_str(command, "")) {
153         edit_field_clean(console->edit_field);
154         return 0;
155     }
156
157     if (history_push(console->history, input_text) < 0) {
158         return -1;
159     }
160
161     if (console_log_push_line(console->console_log, input_text, NULL, CONSOLE_FOREGROUND) < 0) {
162         return -1;
163     }
164
165     if (token_equals_str(command, "load")) {
166         Token level = chop_word(&input);
167         console_log_push_line(console->console_log, "Loading level:", NULL, CONSOLE_FOREGROUND);
168         console_log_push_line(console->console_log, level.begin, level.end, CONSOLE_FOREGROUND);
169         char level_name[256];
170         memset(level_name, 0, 256);
171         memcpy(level_name, level.begin, min_size_t((size_t)(level.end - level.begin), 255));
172
173         if (game_load_level(console->game, level_name) < 0) {
174             console_log_push_line(console->console_log, "Could not load level", NULL, CONSOLE_ERROR);
175         }
176     } else if (token_equals_str(command, "menu")) {
177         console_log_push_line(console->console_log, "Loading menu", NULL, CONSOLE_FOREGROUND);
178         game_switch_state(console->game, GAME_STATE_LEVEL_PICKER);
179     } else {
180         console_log_push_line(console->console_log, "Unknown command", NULL, CONSOLE_ERROR);
181     }
182
183     edit_field_clean(console->edit_field);
184
185     return 0;
186 }
187
188 int console_handle_event(Console *console,
189                          const SDL_Event *event)
190 {
191     switch(event->type) {
192     case SDL_KEYDOWN: {
193         switch(event->key.keysym.sym) {
194         case SDLK_RETURN:
195             return console_eval_input(console);
196
197         case SDLK_UP:
198             edit_field_replace(
199                 console->edit_field,
200                 history_current(console->history));
201             history_prev(console->history);
202             return 0;
203
204         case SDLK_p: {
205             if (event->key.keysym.mod & KMOD_CTRL) {
206                 edit_field_replace(
207                     console->edit_field, history_current(console->history));
208                 history_prev(console->history);
209                 return 0;
210             }
211         } break;
212
213        case SDLK_l: {
214             if (event->key.keysym.mod & KMOD_CTRL) {
215                 console_log_clear(console->console_log);
216                 return 0;
217             }
218         } break;
219
220         case SDLK_DOWN:
221             edit_field_replace(
222                 console->edit_field,
223                 history_current(console->history));
224             history_next(console->history);
225             return 0;
226
227         case SDLK_n: {
228             if (event->key.keysym.mod & KMOD_CTRL) {
229                 edit_field_replace(
230                     console->edit_field, history_current(console->history));
231                 history_next(console->history);
232                 return 0;
233             }
234         } break;
235         }
236     } break;
237     }
238
239     return edit_field_event(console->edit_field, event);
240 }
241
242 int console_render(const Console *console,
243                    const Camera *camera)
244 {
245     /* TODO(#364): console doesn't have any padding around the edit fields */
246     SDL_Rect view_port;
247     SDL_RenderGetViewport(camera->renderer, &view_port);
248
249     const float e = console->a * (2 - console->a);
250     const float y = -(1.0f - e) * CONSOLE_HEIGHT;
251
252     if (camera_fill_rect_screen(
253             camera,
254             rect(0.0f, y,
255                  (float) view_port.w,
256                  CONSOLE_HEIGHT),
257             CONSOLE_BACKGROUND) < 0) {
258         return -1;
259     }
260
261     console_log_render(console->console_log,
262                        camera,
263                        vec(0.0f, y));
264
265     if (edit_field_render_screen(console->edit_field,
266                                  camera,
267                                  vec(0.0f, y + CONSOLE_LOG_HEIGHT)) < 0) {
268         return -1;
269     }
270
271     return 0;
272 }
273
274 int console_update(Console *console, float delta_time)
275 {
276     trace_assert(console);
277
278     if (console->a < 1.0f) {
279         console->a += 1.0f / SLIDE_DOWN_TIME * delta_time;
280
281         if (console->a > 1.0f) {
282             console->a = 1.0f;
283         }
284     }
285
286     return 0;
287 }
288
289 void console_slide_down(Console *console)
290 {
291     trace_assert(console);
292     console->a = 0.0f;
293 }