]> git.lizzy.rs Git - nothing.git/blob - src/ui/console.c
(#550) Introduce log_script unit
[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 "game/level_script.h"
10 #include "sdl/renderer.h"
11 #include "system/log.h"
12 #include "system/log_script.h"
13 #include "system/lt.h"
14 #include "system/nth_alloc.h"
15 #include "ui/console.h"
16 #include "ui/console_log.h"
17 #include "ui/edit_field.h"
18 #include "ui/history.h"
19
20 #define FONT_WIDTH_SCALE 3.0f
21 #define FONT_HEIGHT_SCALE 3.0f
22
23 #define CONSOLE_LOG_CAPACITY 10
24 #define HISTORY_CAPACITY 20
25 #define PROMPT_HEIGHT (FONT_HEIGHT_SCALE * FONT_CHAR_HEIGHT)
26 #define CONSOLE_LOG_HEIGHT (FONT_HEIGHT_SCALE * FONT_CHAR_HEIGHT * CONSOLE_LOG_CAPACITY)
27
28 #define CONSOLE_HEIGHT (CONSOLE_LOG_HEIGHT + PROMPT_HEIGHT)
29
30 #define SLIDE_DOWN_TIME 0.4f
31
32 #define CONSOLE_ALPHA (0.80f)
33 #define CONSOLE_BACKGROUND (rgba(0.20f, 0.20f, 0.20f, CONSOLE_ALPHA))
34 #define CONSOLE_FOREGROUND (rgba(0.80f, 0.80f, 0.80f, CONSOLE_ALPHA))
35 #define CONSOLE_ERROR (rgba(0.80f, 0.50f, 0.50f, CONSOLE_ALPHA))
36
37 #define CONSOLE_EVAL_RESULT_SIZE 256
38
39 struct Console
40 {
41     Lt *lt;
42     Gc *gc;
43     struct Scope scope;
44     Edit_field *edit_field;
45     Console_Log *console_log;
46     Level *level;
47     History *history;
48     float a;
49     char *eval_result;
50 };
51
52 /* TODO(#355): Console does not support Emacs keybindings */
53 /* TODO(#356): Console does not support autocompletion */
54 /* TODO(#357): Console does not show the state of the GC of the script */
55 /* TODO(#358): Console does not support copy, cut, paste operations */
56
57 Console *create_console(Level *level,
58                         const Sprite_font *font)
59 {
60     Lt *lt = create_lt();
61
62     if (lt == NULL) {
63         return NULL;
64     }
65
66     Console *console = PUSH_LT(lt, nth_alloc(sizeof(Console)), free);
67     if (console == NULL) {
68         RETURN_LT(lt, NULL);
69     }
70     console->lt = lt;
71
72     console->gc = PUSH_LT(lt, create_gc(), destroy_gc);
73     if (console->gc == NULL) {
74         RETURN_LT(lt, NULL);
75     }
76
77     console->scope.expr = CONS(console->gc,
78                                NIL(console->gc),
79                                NIL(console->gc));
80
81     load_std_library(console->gc, &console->scope);
82     load_log_library(console->gc, &console->scope);
83     load_level_library(console->gc, &console->scope, level);
84
85     console->edit_field = PUSH_LT(
86         lt,
87         create_edit_field(
88             font,
89             vec(FONT_WIDTH_SCALE, FONT_HEIGHT_SCALE),
90             CONSOLE_FOREGROUND),
91         destroy_edit_field);
92     if (console->edit_field == NULL) {
93         RETURN_LT(lt, NULL);
94     }
95
96     console->console_log = PUSH_LT(
97         lt,
98         create_console_log(
99             font,
100             vec(FONT_WIDTH_SCALE, FONT_HEIGHT_SCALE),
101             CONSOLE_LOG_CAPACITY),
102         destroy_console_log);
103
104     console->level = level;
105     console->a = 0;
106
107     console->eval_result = PUSH_LT(
108         lt,
109         nth_alloc(sizeof(char) * CONSOLE_EVAL_RESULT_SIZE),
110         free);
111     if (console->eval_result == NULL) {
112         RETURN_LT(lt, NULL);
113     }
114     memset(console->eval_result, 0, sizeof(char) * CONSOLE_EVAL_RESULT_SIZE);
115
116     console->history = PUSH_LT(
117         lt,
118         create_history(HISTORY_CAPACITY),
119         destroy_history);
120     if (console->history == NULL) {
121         RETURN_LT(lt, NULL);
122     }
123
124     return console;
125 }
126
127 void destroy_console(Console *console)
128 {
129     assert(console);
130     RETURN_LT0(console->lt);
131 }
132
133 static int console_eval_input(Console *console)
134 {
135     const char *source_code = edit_field_as_text(console->edit_field);
136
137     /* TODO(#387): console pushes empty strings to the history */
138     if (history_push(console->history, source_code) < 0) {
139         return -1;
140     }
141
142     if (console_log_push_line(console->console_log, source_code, CONSOLE_FOREGROUND) < 0) {
143         return -1;
144     }
145
146     while (*source_code != 0) {
147         struct ParseResult parse_result = read_expr_from_string(console->gc,
148                                                                 source_code);
149
150         if (parse_result.is_error) {
151             if (console_log_push_line(console->console_log, parse_result.error_message, CONSOLE_ERROR)) {
152                 return -1;
153             }
154
155             edit_field_clean(console->edit_field);
156
157             return 0;
158         }
159
160         struct EvalResult eval_result = eval(
161             console->gc,
162             &console->scope,
163             parse_result.expr);
164
165         if (expr_as_sexpr(
166                 eval_result.expr,
167                 console->eval_result,
168                 CONSOLE_EVAL_RESULT_SIZE) < 0) {
169             return -1;
170         }
171
172         if (console_log_push_line(console->console_log,
173                           console->eval_result,
174                           eval_result.is_error ?
175                           CONSOLE_ERROR :
176                           CONSOLE_FOREGROUND)) {
177             return -1;
178         }
179
180         source_code = next_token(parse_result.end).begin;
181     }
182
183     gc_collect(console->gc, console->scope.expr);
184     edit_field_clean(console->edit_field);
185
186     return 0;
187 }
188
189 int console_handle_event(Console *console,
190                          const SDL_Event *event)
191 {
192     switch(event->type) {
193     case SDL_KEYDOWN:
194         switch(event->key.keysym.sym) {
195         case SDLK_RETURN:
196             return console_eval_input(console);
197
198         case SDLK_UP:
199             edit_field_replace(
200                 console->edit_field,
201                 history_current(console->history));
202             history_prev(console->history);
203             return 0;
204
205         case SDLK_DOWN:
206             edit_field_replace(
207                 console->edit_field,
208                 history_current(console->history));
209             history_next(console->history);
210             return 0;
211         }
212         break;
213     }
214
215     return edit_field_handle_event(console->edit_field, event);
216 }
217
218 int console_render(const Console *console,
219                    SDL_Renderer *renderer)
220 {
221     /* TODO(#364): console doesn't have any padding around the edit fields */
222     SDL_Rect view_port;
223     SDL_RenderGetViewport(renderer, &view_port);
224
225     const float e = console->a * (2 - console->a);
226     const float y = -(1.0f - e) * CONSOLE_HEIGHT;
227
228     if (fill_rect(renderer,
229                   rect(0.0f, y,
230                        (float) view_port.w,
231                        CONSOLE_HEIGHT),
232                   CONSOLE_BACKGROUND) < 0) {
233         return -1;
234     }
235
236     if (console_log_render(console->console_log,
237                    renderer,
238                    vec(0.0f, y)) < 0) {
239         return -1;
240     }
241
242     if (edit_field_render(console->edit_field,
243                           renderer,
244                           vec(0.0f, y + CONSOLE_LOG_HEIGHT)) < 0) {
245         return -1;
246     }
247
248     return 0;
249 }
250
251 int console_update(Console *console, float delta_time)
252 {
253     assert(console);
254
255     if (console->a < 1.0f) {
256         console->a += 1.0f / SLIDE_DOWN_TIME * delta_time;
257
258         if (console->a > 1.0f) {
259             console->a = 1.0f;
260         }
261     }
262
263     return 0;
264 }
265
266 void console_slide_down(Console *console)
267 {
268     assert(console);
269     console->a = 0.0f;
270 }