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