]> git.lizzy.rs Git - nothing.git/blob - src/ui/console.c
(#1193) Use 'menu' console command instead of 'Esc' key
[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 /* TODO(#358): Console does not support copy, cut, paste operations */
97
98 Console *create_console(Game *game)
99 {
100     Lt *lt = create_lt();
101
102     Console *console = PUSH_LT(lt, nth_calloc(1, sizeof(Console)), free);
103     if (console == NULL) {
104         RETURN_LT(lt, NULL);
105     }
106     console->lt = lt;
107
108     console->edit_field = PUSH_LT(
109         lt,
110         create_edit_field(
111             vec(FONT_WIDTH_SCALE, FONT_HEIGHT_SCALE),
112             CONSOLE_FOREGROUND),
113         destroy_edit_field);
114     if (console->edit_field == NULL) {
115         RETURN_LT(lt, NULL);
116     }
117
118     console->console_log = PUSH_LT(
119         lt,
120         create_console_log(
121             vec(FONT_WIDTH_SCALE, FONT_HEIGHT_SCALE),
122             CONSOLE_LOG_CAPACITY),
123         destroy_console_log);
124
125     console->a = 0;
126
127     console->history = PUSH_LT(
128         lt,
129         create_history(HISTORY_CAPACITY),
130         destroy_history);
131     if (console->history == NULL) {
132         RETURN_LT(lt, NULL);
133     }
134
135     console->game = game;
136
137     return console;
138 }
139
140 void destroy_console(Console *console)
141 {
142     trace_assert(console);
143     RETURN_LT0(console->lt);
144 }
145
146 static int console_eval_input(Console *console)
147 {
148     const char *source_code = edit_field_as_text(console->edit_field);
149
150     /* TODO(#387): console pushes empty strings to the history */
151     if (history_push(console->history, source_code) < 0) {
152         return -1;
153     }
154
155     if (console_log_push_line(console->console_log, source_code, NULL, CONSOLE_FOREGROUND) < 0) {
156         return -1;
157     }
158
159     Token input = token_nt(source_code);
160
161     Token command = chop_word(&input);
162     if (token_equals_str(command, "load")) {
163         Token level = chop_word(&input);
164         console_log_push_line(console->console_log, "Loading level:", NULL, CONSOLE_FOREGROUND);
165         console_log_push_line(console->console_log, level.begin, level.end, CONSOLE_FOREGROUND);
166         char level_name[256];
167         memset(level_name, 0, 256);
168         memcpy(level_name, level.begin, min_size_t((size_t)(level.end - level.begin), 255));
169
170         if (game_load_level(console->game, level_name) < 0) {
171             console_log_push_line(console->console_log, "Could not load level", NULL, CONSOLE_ERROR);
172         }
173     } else if (token_equals_str(command, "menu")) {
174         console_log_push_line(console->console_log, "Loading menu", NULL, CONSOLE_FOREGROUND);
175         level_picker_clean_selection(console->game->level_picker);
176         game_switch_state(console->game, GAME_STATE_LEVEL_PICKER);
177     } else {
178         console_log_push_line(console->console_log, "Unknown command", NULL, CONSOLE_ERROR);
179     }
180
181     edit_field_clean(console->edit_field);
182
183     return 0;
184 }
185
186 int console_handle_event(Console *console,
187                          const SDL_Event *event)
188 {
189     switch(event->type) {
190     case SDL_KEYDOWN: {
191         switch(event->key.keysym.sym) {
192         case SDLK_RETURN:
193             return console_eval_input(console);
194
195         case SDLK_UP:
196             edit_field_replace(
197                 console->edit_field,
198                 history_current(console->history));
199             history_prev(console->history);
200             return 0;
201
202         case SDLK_p: {
203             if (event->key.keysym.mod & KMOD_CTRL) {
204                 edit_field_replace(
205                     console->edit_field, history_current(console->history));
206                 history_prev(console->history);
207                 return 0;
208             }
209         } break;
210
211         case SDLK_DOWN:
212             edit_field_replace(
213                 console->edit_field,
214                 history_current(console->history));
215             history_next(console->history);
216             return 0;
217
218         case SDLK_n: {
219             if (event->key.keysym.mod & KMOD_CTRL) {
220                 edit_field_replace(
221                     console->edit_field, history_current(console->history));
222                 history_next(console->history);
223                 return 0;
224             }
225         } break;
226         }
227     } break;
228     }
229
230     return edit_field_event(console->edit_field, event);
231 }
232
233 int console_render(const Console *console,
234                    const Camera *camera)
235 {
236     /* TODO(#364): console doesn't have any padding around the edit fields */
237     SDL_Rect view_port;
238     SDL_RenderGetViewport(camera->renderer, &view_port);
239
240     const float e = console->a * (2 - console->a);
241     const float y = -(1.0f - e) * CONSOLE_HEIGHT;
242
243     if (camera_fill_rect_screen(
244             camera,
245             rect(0.0f, y,
246                  (float) view_port.w,
247                  CONSOLE_HEIGHT),
248             CONSOLE_BACKGROUND) < 0) {
249         return -1;
250     }
251
252     if (console_log_render(console->console_log,
253                            camera,
254                            vec(0.0f, y)) < 0) {
255         return -1;
256     }
257
258     if (edit_field_render_screen(console->edit_field,
259                                  camera,
260                                  vec(0.0f, y + CONSOLE_LOG_HEIGHT)) < 0) {
261         return -1;
262     }
263
264     return 0;
265 }
266
267 int console_update(Console *console, float delta_time)
268 {
269     trace_assert(console);
270
271     if (console->a < 1.0f) {
272         console->a += 1.0f / SLIDE_DOWN_TIME * delta_time;
273
274         if (console->a > 1.0f) {
275             console->a = 1.0f;
276         }
277     }
278
279     return 0;
280 }
281
282 void console_slide_down(Console *console)
283 {
284     trace_assert(console);
285     console->a = 0.0f;
286 }