]> git.lizzy.rs Git - nothing.git/blob - src/ui/console.c
(#477) Replace all instances of malloc with nth_alloc
[nothing.git] / src / ui / console.c
1 #include <assert.h>
2
3 #include "ebisp/gc.h"
4 #include "ebisp/interpreter.h"
5 #include "ebisp/parser.h"
6 #include "ebisp/scope.h"
7 #include "game/level.h"
8 #include "game/level/player/rigid_rect.h"
9 #include "sdl/renderer.h"
10 #include "system/error.h"
11 #include "system/lt.h"
12 #include "system/nth_alloc.h"
13 #include "ui/console.h"
14 #include "ui/edit_field.h"
15 #include "ui/history.h"
16 #include "ui/log.h"
17
18 #define FONT_WIDTH_SCALE 3.0f
19 #define FONT_HEIGHT_SCALE 3.0f
20
21 #define LOG_CAPACITY 10
22 #define HISTORY_CAPACITY 20
23 #define PROMPT_HEIGHT (FONT_HEIGHT_SCALE * FONT_CHAR_HEIGHT)
24 #define LOG_HEIGHT (FONT_HEIGHT_SCALE * FONT_CHAR_HEIGHT * LOG_CAPACITY)
25
26 #define CONSOLE_HEIGHT (LOG_HEIGHT + PROMPT_HEIGHT)
27
28 #define SLIDE_DOWN_TIME 0.4f
29
30 #define CONSOLE_ALPHA (0.80f)
31 #define CONSOLE_BACKGROUND (color(0.20f, 0.20f, 0.20f, CONSOLE_ALPHA))
32 #define CONSOLE_FOREGROUND (color(0.80f, 0.80f, 0.80f, CONSOLE_ALPHA))
33 #define CONSOLE_ERROR (color(0.80f, 0.50f, 0.50f, CONSOLE_ALPHA))
34
35 #define CONSOLE_EVAL_RESULT_SIZE 256
36
37 struct Console
38 {
39     Lt *lt;
40     Gc *gc;
41     struct Scope scope;
42     Edit_field *edit_field;
43     Log *log;
44     Level *level;
45     History *history;
46     float a;
47     char *eval_result;
48 };
49
50 /* TODO(#355): Console does not support Emacs keybindings */
51 /* TODO(#356): Console does not support autocompletion */
52 /* TODO(#357): Console does not show the state of the GC of the script */
53 /* TODO(#358): Console does not support copy, cut, paste operations */
54
55 struct EvalResult rect_apply_force(void *param, Gc *gc, struct Scope *scope, struct Expr args)
56 {
57     assert(gc);
58     assert(scope);
59     assert(param);
60
61     /* TODO(#401): rect_apply_force doesn't sanitize it's input */
62
63     Level *level = (Level*) param;
64     const char *rect_id = CAR(args).atom->str;
65     struct Expr vector_force_expr = CAR(CDR(args));
66     const float force_x = (float) CAR(vector_force_expr).atom->num;
67     const float force_y = (float) CDR(vector_force_expr).atom->num;
68
69     print_expr_as_sexpr(stdout, args); printf("\n");
70
71     Rigid_rect *rigid_rect = level_rigid_rect(level, rect_id);
72     if (rigid_rect != NULL) {
73         printf("Found rect `%s`\n", rect_id);
74         printf("Applying force (%f, %f)\n", force_x, force_y);
75         rigid_rect_apply_force(rigid_rect, vec(force_x, force_y));
76     } else {
77         fprintf(stderr, "Couldn't find rigid_rect `%s`", rect_id);
78     }
79
80     return eval_success(NIL(gc));
81 }
82
83 Console *create_console(Level *level,
84                         const Sprite_font *font)
85 {
86     Lt *lt = create_lt();
87
88     if (lt == NULL) {
89         return NULL;
90     }
91
92     Console *console = PUSH_LT(lt, nth_alloc(sizeof(Console)), free);
93     if (console == NULL) {
94         throw_error(ERROR_TYPE_LIBC);
95         RETURN_LT(lt, NULL);
96     }
97     console->lt = lt;
98
99     console->gc = PUSH_LT(lt, create_gc(), destroy_gc);
100     if (console->gc == NULL) {
101         RETURN_LT(lt, NULL);
102     }
103
104     console->scope.expr = CONS(console->gc,
105                                NIL(console->gc),
106                                NIL(console->gc));
107     set_scope_value(
108         console->gc,
109         &console->scope,
110         SYMBOL(console->gc, "rect-apply-force"),
111         NATIVE(console->gc, rect_apply_force, level));
112
113     console->edit_field = PUSH_LT(
114         lt,
115         create_edit_field(
116             font,
117             vec(FONT_WIDTH_SCALE, FONT_HEIGHT_SCALE),
118             CONSOLE_FOREGROUND),
119         destroy_edit_field);
120     if (console->edit_field == NULL) {
121         RETURN_LT(lt, NULL);
122     }
123
124     console->log = PUSH_LT(
125         lt,
126         create_log(
127             font,
128             vec(FONT_WIDTH_SCALE, FONT_HEIGHT_SCALE),
129             LOG_CAPACITY),
130         destroy_log);
131
132     console->level = level;
133     console->a = 0;
134
135     console->eval_result = PUSH_LT(
136         lt,
137         nth_alloc(sizeof(char) * CONSOLE_EVAL_RESULT_SIZE),
138         free);
139     if (console->eval_result == NULL) {
140         RETURN_LT(lt, NULL);
141     }
142     memset(console->eval_result, 0, sizeof(char) * CONSOLE_EVAL_RESULT_SIZE);
143
144     console->history = PUSH_LT(
145         lt,
146         create_history(HISTORY_CAPACITY),
147         destroy_history);
148     if (console->history == NULL) {
149         RETURN_LT(lt, NULL);
150     }
151
152     return console;
153 }
154
155 void destroy_console(Console *console)
156 {
157     assert(console);
158     RETURN_LT0(console->lt);
159 }
160
161 static int console_eval_input(Console *console)
162 {
163     const char *source_code = edit_field_as_text(console->edit_field);
164
165     /* TODO(#387): console pushes empty strings to the history */
166     if (history_push(console->history, source_code) < 0) {
167         return -1;
168     }
169
170     if (log_push_line(console->log, source_code, CONSOLE_FOREGROUND) < 0) {
171         return -1;
172     }
173
174     while (*source_code != 0) {
175         struct ParseResult parse_result = read_expr_from_string(console->gc,
176                                                                 source_code);
177
178         if (parse_result.is_error) {
179             if (log_push_line(console->log, parse_result.error_message, CONSOLE_ERROR)) {
180                 return -1;
181             }
182
183             edit_field_clean(console->edit_field);
184
185             return 0;
186         }
187
188         struct EvalResult eval_result = eval(
189             console->gc,
190             &console->scope,
191             parse_result.expr);
192
193         if (expr_as_sexpr(
194                 eval_result.expr,
195                 console->eval_result,
196                 CONSOLE_EVAL_RESULT_SIZE) < 0) {
197             return -1;
198         }
199
200         if (log_push_line(console->log,
201                           console->eval_result,
202                           eval_result.is_error ?
203                           CONSOLE_ERROR :
204                           CONSOLE_FOREGROUND)) {
205             return -1;
206         }
207
208         source_code = next_token(parse_result.end).begin;
209     }
210
211     gc_collect(console->gc, console->scope.expr);
212     edit_field_clean(console->edit_field);
213
214     return 0;
215 }
216
217 int console_handle_event(Console *console,
218                          const SDL_Event *event)
219 {
220     switch(event->type) {
221     case SDL_KEYDOWN:
222         switch(event->key.keysym.sym) {
223         case SDLK_RETURN:
224             return console_eval_input(console);
225
226         case SDLK_UP:
227             edit_field_replace(
228                 console->edit_field,
229                 history_current(console->history));
230             history_prev(console->history);
231             return 0;
232
233         case SDLK_DOWN:
234             edit_field_replace(
235                 console->edit_field,
236                 history_current(console->history));
237             history_next(console->history);
238             return 0;
239         }
240         break;
241     }
242
243     return edit_field_handle_event(console->edit_field, event);
244 }
245
246 int console_render(const Console *console,
247                    SDL_Renderer *renderer)
248 {
249     /* TODO(#364): console doesn't have any padding around the edit fields */
250     SDL_Rect view_port;
251     SDL_RenderGetViewport(renderer, &view_port);
252
253     const float e = console->a * (2 - console->a);
254     const float y = -(1.0f - e) * CONSOLE_HEIGHT;
255
256     if (fill_rect(renderer,
257                   rect(0.0f, y,
258                        (float) view_port.w,
259                        CONSOLE_HEIGHT),
260                   CONSOLE_BACKGROUND) < 0) {
261         return -1;
262     }
263
264     if (log_render(console->log,
265                    renderer,
266                    vec(0.0f, y)) < 0) {
267         return -1;
268     }
269
270     if (edit_field_render(console->edit_field,
271                           renderer,
272                           vec(0.0f, y + LOG_HEIGHT)) < 0) {
273         return -1;
274     }
275
276     return 0;
277 }
278
279 int console_update(Console *console, float delta_time)
280 {
281     assert(console);
282
283     /* TODO(#366): console slide down animation doesn't have any easing */
284     if (console->a < 1.0f) {
285         console->a += 1.0f / SLIDE_DOWN_TIME * delta_time;
286
287         if (console->a > 1.0f) {
288             console->a = 1.0f;
289         }
290     }
291
292     return 0;
293 }
294
295 void console_slide_down(Console *console)
296 {
297     assert(console);
298     console->a = 0.0f;
299 }