]> git.lizzy.rs Git - nothing.git/blob - src/game/level_picker.c
Added additional check to scrollbar drawing.
[nothing.git] / src / game / level_picker.c
1 #include <stdio.h>
2
3 #include "./level_picker.h"
4
5 #include "game/sprite_font.h"
6 #include "system/lt.h"
7 #include "system/nth_alloc.h"
8 #include "system/stacktrace.h"
9 #include "system/str.h"
10 #include "system/log.h"
11 #include "system/file.h"
12
13 #define TITLE_MARGIN_TOP 100.0f
14 #define TITLE_MARGIN_BOTTOM 100.0f
15
16 #define LEVEL_PICKER_LIST_FONT_SCALE vec(5.0f, 5.0f)
17 #define LEVEL_PICKER_LIST_PADDING_BOTTOM 50.0f
18
19 #define ITEM_HEIGHT (FONT_CHAR_HEIGHT * LEVEL_PICKER_LIST_FONT_SCALE.y + LEVEL_PICKER_LIST_PADDING_BOTTOM)
20
21 #define SCROLLBAR_WIDTH 20
22 #define SCROLLING_SPEED_FRACTION 0.25f
23
24 void level_picker_populate(LevelPicker *level_picker,
25                            const char *dirpath)
26 {
27     trace_assert(level_picker);
28     trace_assert(dirpath);
29
30     level_picker->background.base_color = hexstr("073642");
31     level_picker->camera_position = vec(0.0f, 0.0f);
32
33     {
34         dynarray_clear(&level_picker->items);
35         level_picker->items = create_dynarray(METADATA_FILEPATH_MAX_SIZE);
36
37         DIR *level_dir = opendir(dirpath);
38         if (level_dir == NULL) {
39             log_fail("Can't open asset folder: %s\n", dirpath);
40             abort();
41         }
42
43         char filepath[METADATA_FILEPATH_MAX_SIZE];
44         for (struct dirent *d = readdir(level_dir);
45              d != NULL;
46              d = readdir(level_dir)) {
47             if (*d->d_name == '.') continue;
48
49             snprintf(filepath, METADATA_FILEPATH_MAX_SIZE,
50                      "%s/%s", dirpath, d->d_name);
51             dynarray_push(&level_picker->items, filepath);
52         }
53         closedir(level_dir);
54     }
55
56     level_picker->wiggly_text = (WigglyText) {
57         .text = "Select Level",
58         .scale = {10.0f, 10.0f},
59         .color = COLOR_WHITE,
60     };
61 }
62
63 int level_picker_render(const LevelPicker *level_picker,
64                         const Camera *camera)
65 {
66     trace_assert(level_picker);
67
68     const Rect viewport = camera_view_port_screen(camera);
69
70     if (background_render(&level_picker->background, camera) < 0) {
71         return -1;
72     }
73
74     const Vec2f title_size = wiggly_text_size(&level_picker->wiggly_text);
75     const float scrolling_area_height = viewport.h - ITEM_HEIGHT - level_picker->items_position.y;
76
77     wiggly_text_render(
78         &level_picker->wiggly_text,
79         camera,
80         vec(viewport.w * 0.5f - title_size.x * 0.5f, TITLE_MARGIN_TOP));
81
82     const float proportional_scroll = level_picker->items_scroll.y * scrolling_area_height / level_picker->items_size.y;
83     const float number_of_items_in_scrolling_area = scrolling_area_height / ITEM_HEIGHT;
84     const float percent_of_visible_items = number_of_items_in_scrolling_area / ((float) level_picker->items.count - 1);
85
86     if(level_picker->items.count > 0 && percent_of_visible_items < 1) {
87         SDL_Rect scrollbar = rect_for_sdl(
88             rect_from_vecs(
89                 vec(level_picker->items_position.x + level_picker->items_size.x, level_picker->items_position.y),
90                 vec(SCROLLBAR_WIDTH, scrolling_area_height)));
91
92         SDL_Rect scrollbar_thumb = rect_for_sdl(
93             rect_from_vecs(
94                 vec(level_picker->items_position.x + level_picker->items_size.x, level_picker->items_position.y - proportional_scroll),
95                 vec(SCROLLBAR_WIDTH, scrolling_area_height * percent_of_visible_items)));
96
97         if (SDL_SetRenderDrawColor(camera->renderer, 255, 255, 255, 255) < 0) {
98             return -1;
99         }
100
101         if (SDL_RenderDrawRect(camera->renderer, &scrollbar) < 0) {
102             return -1;
103         }
104
105         if (SDL_RenderFillRect(camera->renderer, &scrollbar_thumb) < 0) {
106             return -1;
107         }
108     }
109
110     for (size_t i = 0; i < level_picker->items.count; ++i) {
111         const Vec2f current_position = vec_sum(
112             level_picker->items_position,
113             vec(0.0f, (float) i * ITEM_HEIGHT + level_picker->items_scroll.y));
114
115         if(current_position.y > level_picker->items_position.y + scrolling_area_height ||
116             current_position.y < level_picker->items_position.y) {
117             continue;
118         }
119
120         const char *item_text = dynarray_pointer_at(&level_picker->items, i);
121
122         sprite_font_render_text(
123             &camera->font,
124             camera->renderer,
125             current_position,
126             LEVEL_PICKER_LIST_FONT_SCALE,
127             rgba(1.0f, 1.0f, 1.0f, 1.0f),
128             item_text);
129
130         if (i == level_picker->items_cursor) {
131             SDL_Rect boundary_box = rect_for_sdl(
132                 sprite_font_boundary_box(
133                     current_position,
134                     LEVEL_PICKER_LIST_FONT_SCALE,
135                     item_text));
136             if (SDL_SetRenderDrawColor(camera->renderer, 255, 255, 255, 255) < 0) {
137                 return -1;
138             }
139
140             if (SDL_RenderDrawRect(camera->renderer, &boundary_box) < 0) {
141                 return -1;
142             }
143         }
144     }
145
146     {
147         /* CSS */
148         const float padding = 20.0f;
149         const Vec2f size = vec(3.0f, 3.0f);
150         const Vec2f position = vec(0.0f, viewport.h - size.y * FONT_CHAR_HEIGHT);
151
152         /* HTML */
153         camera_render_text_screen(
154             camera,
155             "Press 'N' to create new level",
156             size,
157             COLOR_WHITE,
158             vec(position.x + padding,
159                 position.y - padding));
160     }
161
162     return 0;
163 }
164
165 int level_picker_update(LevelPicker *level_picker,
166                         Camera *camera,
167                         float delta_time)
168 {
169     trace_assert(level_picker);
170
171     const Rect viewport = camera_view_port_screen(camera);
172     const float scrolling_area_height = viewport.h - ITEM_HEIGHT - level_picker->items_position.y;
173
174     if ((float) level_picker->items_cursor * ITEM_HEIGHT + level_picker->items_scroll.y > scrolling_area_height) {
175         level_picker->items_scroll.y -= ITEM_HEIGHT * SCROLLING_SPEED_FRACTION;
176     }
177     if ((float) level_picker->items_cursor * ITEM_HEIGHT + level_picker->items_scroll.y < 0) {
178         level_picker->items_scroll.y += ITEM_HEIGHT * SCROLLING_SPEED_FRACTION;
179     }
180
181     vec_add(&level_picker->camera_position,
182             vec(50.0f * delta_time, 0.0f));
183
184     if (wiggly_text_update(&level_picker->wiggly_text, delta_time) < 0) {
185         return -1;
186     }
187
188     return 0;
189 }
190
191 static
192 Vec2f level_picker_list_size(const LevelPicker *level_picker)
193 {
194     trace_assert(level_picker);
195
196     Vec2f result = vec(0.0f, 0.0f);
197
198     for (size_t i = 0; i < level_picker->items.count; ++i) {
199         const char *item_text = dynarray_pointer_at(
200             &level_picker->items,
201             i);
202
203         Rect boundary_box = sprite_font_boundary_box(
204             vec(0.0f, 0.0f),
205             LEVEL_PICKER_LIST_FONT_SCALE,
206             item_text);
207
208         result.x = fmaxf(result.x, boundary_box.w);
209         result.y += boundary_box.h + LEVEL_PICKER_LIST_PADDING_BOTTOM;
210     }
211
212     return result;
213 }
214
215
216 int level_picker_event(LevelPicker *level_picker,
217                        const SDL_Event *event)
218 {
219     trace_assert(level_picker);
220     trace_assert(event);
221
222     switch (event->type) {
223     case SDL_WINDOWEVENT: {
224         switch (event->window.event) {
225         case SDL_WINDOWEVENT_SHOWN:
226         case SDL_WINDOWEVENT_SIZE_CHANGED: {
227             int width;
228             SDL_GetRendererOutputSize(SDL_GetRenderer(SDL_GetWindowFromID(event->window.windowID)), &width, NULL);
229             const Vec2f title_size = wiggly_text_size(&level_picker->wiggly_text);
230             level_picker->items_size = level_picker_list_size(level_picker);
231
232             level_picker->items_position =
233                 vec((float)width * 0.5f - level_picker->items_size.x * 0.5f,
234                     TITLE_MARGIN_TOP + title_size.y + TITLE_MARGIN_BOTTOM);
235         } break;
236         }
237     } break;
238
239     case SDL_KEYDOWN:
240         switch (event->key.keysym.sym) {
241         case SDLK_UP:
242             if (level_picker->items_cursor == 0) {
243                 level_picker->items_cursor = level_picker->items.count - 1;
244             } else {
245                 level_picker->items_cursor--;
246             }
247             break;
248         case SDLK_DOWN:
249             level_picker->items_cursor++;
250             if (level_picker->items_cursor == level_picker->items.count) {
251                 level_picker->items_cursor = 0;
252             }
253             break;
254         case SDLK_RETURN:
255             if (level_picker->items_cursor < level_picker->items.count) {
256                 level_picker->selected_item = (int) level_picker->items_cursor;
257             }
258             break;
259         }
260         break;
261
262     case SDL_MOUSEMOTION: {
263         const Vec2f mouse_pos = vec((float) event->motion.x, (float) event->motion.y);
264         Vec2f position = vec_sum(
265             level_picker->items_position,
266             level_picker->items_scroll);
267
268         for (size_t i = 0; i < level_picker->items.count; ++i) {
269             const char *item_text = dynarray_pointer_at(
270                 &level_picker->items,
271                 i);
272
273             Rect boundary_box = sprite_font_boundary_box(
274                 position,
275                 LEVEL_PICKER_LIST_FONT_SCALE,
276                 item_text);
277
278             if (rect_contains_point(boundary_box, mouse_pos)) {
279                 level_picker->items_cursor = i;
280             }
281
282             position.y += boundary_box.h + LEVEL_PICKER_LIST_PADDING_BOTTOM;
283         }
284     } break;
285
286     case SDL_MOUSEBUTTONDOWN: {
287         switch (event->button.button) {
288         case SDL_BUTTON_LEFT: {
289             // check if the click position was actually inside...
290             // note: make sure there's actually stuff in the list! tsoding likes
291             // to remove all levels and change title to "SMOL BREAK"...
292             if (level_picker->items.count == 0)
293                 break;
294
295             // note: this assumes that all list items are the same height!
296             // this is probably a valid assumption as long as we use a sprite font.
297             float single_item_height =
298                 FONT_CHAR_HEIGHT * LEVEL_PICKER_LIST_FONT_SCALE.y + LEVEL_PICKER_LIST_PADDING_BOTTOM;
299
300             Vec2f position = vec_sum(
301                 level_picker->items_position,
302                 level_picker->items_scroll);
303             vec_add(&position, vec(0.0f, (float) level_picker->items_cursor * single_item_height));
304
305             const char *item_text =
306                 dynarray_pointer_at(
307                     &level_picker->items,
308                     level_picker->items_cursor);
309
310             Rect boundary_box = sprite_font_boundary_box(
311                 position,
312                 LEVEL_PICKER_LIST_FONT_SCALE,
313                 item_text);
314
315             const Vec2f mouse_pos = vec((float) event->motion.x, (float) event->motion.y);
316             if (rect_contains_point(boundary_box, mouse_pos)) {
317                 level_picker->selected_item = (int) level_picker->items_cursor;
318             }
319         } break;
320         }
321     } break;
322     }
323
324     return 0;
325 }
326
327 int level_picker_input(LevelPicker *level_picker,
328                        const Uint8 *const keyboard_state,
329                        SDL_Joystick *the_stick_of_joy)
330 {
331     trace_assert(level_picker);
332     trace_assert(keyboard_state);
333     (void) the_stick_of_joy;
334     return 0;
335 }
336
337 const char *level_picker_selected_level(const LevelPicker *level_picker)
338 {
339     trace_assert(level_picker);
340
341     if (level_picker->selected_item < 0) {
342         return NULL;
343     }
344
345     return dynarray_pointer_at(
346         &level_picker->items,
347         (size_t)level_picker->selected_item);
348 }
349
350 void level_picker_clean_selection(LevelPicker *level_picker)
351 {
352     trace_assert(level_picker);
353     level_picker->selected_item = -1;
354 }
355
356 int level_picker_enter_camera_event(LevelPicker *level_picker,
357                                     Camera *camera)
358 {
359     camera_center_at(camera, level_picker->camera_position);
360     return 0;
361 }