]> git.lizzy.rs Git - nothing.git/blob - src/ui/console.c
Merge pull request #1197 from tsoding/cleanup
[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         game_switch_state(console->game, GAME_STATE_LEVEL_PICKER);
176     } else {
177         console_log_push_line(console->console_log, "Unknown command", NULL, CONSOLE_ERROR);
178     }
179
180     edit_field_clean(console->edit_field);
181
182     return 0;
183 }
184
185 int console_handle_event(Console *console,
186                          const SDL_Event *event)
187 {
188     switch(event->type) {
189     case SDL_KEYDOWN: {
190         switch(event->key.keysym.sym) {
191         case SDLK_RETURN:
192             return console_eval_input(console);
193
194         case SDLK_UP:
195             edit_field_replace(
196                 console->edit_field,
197                 history_current(console->history));
198             history_prev(console->history);
199             return 0;
200
201         case SDLK_p: {
202             if (event->key.keysym.mod & KMOD_CTRL) {
203                 edit_field_replace(
204                     console->edit_field, history_current(console->history));
205                 history_prev(console->history);
206                 return 0;
207             }
208         } break;
209
210         case SDLK_DOWN:
211             edit_field_replace(
212                 console->edit_field,
213                 history_current(console->history));
214             history_next(console->history);
215             return 0;
216
217         case SDLK_n: {
218             if (event->key.keysym.mod & KMOD_CTRL) {
219                 edit_field_replace(
220                     console->edit_field, history_current(console->history));
221                 history_next(console->history);
222                 return 0;
223             }
224         } break;
225         }
226     } break;
227     }
228
229     return edit_field_event(console->edit_field, event);
230 }
231
232 int console_render(const Console *console,
233                    const Camera *camera)
234 {
235     /* TODO(#364): console doesn't have any padding around the edit fields */
236     SDL_Rect view_port;
237     SDL_RenderGetViewport(camera->renderer, &view_port);
238
239     const float e = console->a * (2 - console->a);
240     const float y = -(1.0f - e) * CONSOLE_HEIGHT;
241
242     if (camera_fill_rect_screen(
243             camera,
244             rect(0.0f, y,
245                  (float) view_port.w,
246                  CONSOLE_HEIGHT),
247             CONSOLE_BACKGROUND) < 0) {
248         return -1;
249     }
250
251     console_log_render(console->console_log,
252                        camera,
253                        vec(0.0f, y));
254
255     if (edit_field_render_screen(console->edit_field,
256                                  camera,
257                                  vec(0.0f, y + CONSOLE_LOG_HEIGHT)) < 0) {
258         return -1;
259     }
260
261     return 0;
262 }
263
264 int console_update(Console *console, float delta_time)
265 {
266     trace_assert(console);
267
268     if (console->a < 1.0f) {
269         console->a += 1.0f / SLIDE_DOWN_TIME * delta_time;
270
271         if (console->a > 1.0f) {
272             console->a = 1.0f;
273         }
274     }
275
276     return 0;
277 }
278
279 void console_slide_down(Console *console)
280 {
281     trace_assert(console);
282     console->a = 0.0f;
283 }