3 #include "./level_picker.h"
5 #include "game/sprite_font.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"
13 #define TITLE_MARGIN_TOP 100.0f
14 #define TITLE_MARGIN_BOTTOM 100.0f
16 #define LEVEL_PICKER_LIST_FONT_SCALE vec(5.0f, 5.0f)
17 #define LEVEL_PICKER_LIST_PADDING_BOTTOM 50.0f
19 #define ITEM_HEIGHT (FONT_CHAR_HEIGHT * LEVEL_PICKER_LIST_FONT_SCALE.y + LEVEL_PICKER_LIST_PADDING_BOTTOM)
21 #define SCROLLBAR_WIDTH 20
22 #define SCROLLING_SPEED_FRACTION 0.25f
24 void level_picker_populate(LevelPicker *level_picker,
27 trace_assert(level_picker);
28 trace_assert(dirpath);
30 level_picker->background.base_color = hexstr("073642");
31 level_picker->camera_position = vec(0.0f, 0.0f);
34 dynarray_clear(&level_picker->items);
35 level_picker->items = create_dynarray_malloc(METADATA_FILEPATH_MAX_SIZE);
37 DIR *level_dir = opendir(dirpath);
38 if (level_dir == NULL) {
39 log_fail("Can't open asset folder: %s\n", dirpath);
43 char filepath[METADATA_FILEPATH_MAX_SIZE];
44 for (struct dirent *d = readdir(level_dir);
46 d = readdir(level_dir)) {
47 if (*d->d_name == '.') continue;
49 snprintf(filepath, METADATA_FILEPATH_MAX_SIZE,
50 "%s/%s", dirpath, d->d_name);
51 dynarray_push(&level_picker->items, filepath);
56 level_picker->wiggly_text = (WigglyText) {
57 .text = "Select Level",
58 .scale = {10.0f, 10.0f},
63 int level_picker_render(const LevelPicker *level_picker,
66 trace_assert(level_picker);
68 const Rect viewport = camera_view_port_screen(camera);
70 if (background_render(&level_picker->background, camera) < 0) {
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;
78 &level_picker->wiggly_text,
80 vec(viewport.w * 0.5f - title_size.x * 0.5f, TITLE_MARGIN_TOP));
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);
86 if(level_picker->items.count > 0 && percent_of_visible_items < 1) {
87 SDL_Rect scrollbar = rect_for_sdl(
89 vec(level_picker->items_position.x + level_picker->items_size.x, level_picker->items_position.y),
90 vec(SCROLLBAR_WIDTH, scrolling_area_height)));
92 SDL_Rect scrollbar_thumb = rect_for_sdl(
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)));
97 if (SDL_SetRenderDrawColor(camera->renderer, 255, 255, 255, 255) < 0) {
101 if (SDL_RenderDrawRect(camera->renderer, &scrollbar) < 0) {
105 if (SDL_RenderFillRect(camera->renderer, &scrollbar_thumb) < 0) {
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));
115 if(current_position.y > level_picker->items_position.y + scrolling_area_height ||
116 current_position.y < level_picker->items_position.y) {
120 const char *item_text = dynarray_pointer_at(&level_picker->items, i);
122 sprite_font_render_text(
126 LEVEL_PICKER_LIST_FONT_SCALE,
127 rgba(1.0f, 1.0f, 1.0f, 1.0f),
130 if (i == level_picker->items_cursor) {
131 SDL_Rect boundary_box = rect_for_sdl(
132 sprite_font_boundary_box(
134 LEVEL_PICKER_LIST_FONT_SCALE,
136 if (SDL_SetRenderDrawColor(camera->renderer, 255, 255, 255, 255) < 0) {
140 if (SDL_RenderDrawRect(camera->renderer, &boundary_box) < 0) {
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);
153 camera_render_text_screen(
155 "Press 'N' to create new level",
158 vec(position.x + padding,
159 position.y - padding));
165 int level_picker_update(LevelPicker *level_picker,
169 trace_assert(level_picker);
171 const Rect viewport = camera_view_port_screen(camera);
172 const float scrolling_area_height = viewport.h - ITEM_HEIGHT - level_picker->items_position.y;
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;
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;
181 vec_add(&level_picker->camera_position,
182 vec(50.0f * delta_time, 0.0f));
184 if (wiggly_text_update(&level_picker->wiggly_text, delta_time) < 0) {
192 Vec2f level_picker_list_size(const LevelPicker *level_picker)
194 trace_assert(level_picker);
196 Vec2f result = vec(0.0f, 0.0f);
198 for (size_t i = 0; i < level_picker->items.count; ++i) {
199 const char *item_text = dynarray_pointer_at(
200 &level_picker->items,
203 Rect boundary_box = sprite_font_boundary_box(
205 LEVEL_PICKER_LIST_FONT_SCALE,
208 result.x = fmaxf(result.x, boundary_box.w);
209 result.y += boundary_box.h + LEVEL_PICKER_LIST_PADDING_BOTTOM;
216 int level_picker_event(LevelPicker *level_picker,
217 const SDL_Event *event)
219 trace_assert(level_picker);
222 switch (event->type) {
223 case SDL_WINDOWEVENT: {
224 switch (event->window.event) {
225 case SDL_WINDOWEVENT_SHOWN:
226 case SDL_WINDOWEVENT_SIZE_CHANGED: {
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);
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);
240 switch (event->key.keysym.sym) {
242 if (level_picker->items_cursor < level_picker->items.count) {
243 level_picker->selected_item = (int) level_picker->items_cursor;
249 case SDL_MOUSEMOTION: {
250 const Vec2f mouse_pos = vec((float) event->motion.x, (float) event->motion.y);
251 Vec2f position = vec_sum(
252 level_picker->items_position,
253 level_picker->items_scroll);
255 for (size_t i = 0; i < level_picker->items.count; ++i) {
256 const char *item_text = dynarray_pointer_at(
257 &level_picker->items,
260 Rect boundary_box = sprite_font_boundary_box(
262 LEVEL_PICKER_LIST_FONT_SCALE,
265 if (rect_contains_point(boundary_box, mouse_pos)) {
266 level_picker->items_cursor = i;
269 position.y += boundary_box.h + LEVEL_PICKER_LIST_PADDING_BOTTOM;
273 case SDL_MOUSEBUTTONDOWN: {
274 switch (event->button.button) {
275 case SDL_BUTTON_LEFT: {
276 // check if the click position was actually inside...
277 // note: make sure there's actually stuff in the list! tsoding likes
278 // to remove all levels and change title to "SMOL BREAK"...
279 if (level_picker->items.count == 0)
282 // note: this assumes that all list items are the same height!
283 // this is probably a valid assumption as long as we use a sprite font.
284 float single_item_height =
285 FONT_CHAR_HEIGHT * LEVEL_PICKER_LIST_FONT_SCALE.y + LEVEL_PICKER_LIST_PADDING_BOTTOM;
287 Vec2f position = vec_sum(
288 level_picker->items_position,
289 level_picker->items_scroll);
290 vec_add(&position, vec(0.0f, (float) level_picker->items_cursor * single_item_height));
292 const char *item_text =
294 &level_picker->items,
295 level_picker->items_cursor);
297 Rect boundary_box = sprite_font_boundary_box(
299 LEVEL_PICKER_LIST_FONT_SCALE,
302 const Vec2f mouse_pos = vec((float) event->motion.x, (float) event->motion.y);
303 if (rect_contains_point(boundary_box, mouse_pos)) {
304 level_picker->selected_item = (int) level_picker->items_cursor;
314 int level_picker_input(LevelPicker *level_picker,
315 const Uint8 *const keyboard_state,
316 SDL_Joystick *the_stick_of_joy)
318 trace_assert(level_picker);
319 trace_assert(keyboard_state);
320 (void) the_stick_of_joy;
324 const char *level_picker_selected_level(const LevelPicker *level_picker)
326 trace_assert(level_picker);
328 if (level_picker->selected_item < 0) {
332 return dynarray_pointer_at(
333 &level_picker->items,
334 (size_t)level_picker->selected_item);
337 void level_picker_clean_selection(LevelPicker *level_picker)
339 trace_assert(level_picker);
340 level_picker->selected_item = -1;
343 int level_picker_enter_camera_event(LevelPicker *level_picker,
346 camera_center_at(camera, level_picker->camera_position);
350 void level_picker_cursor_up(LevelPicker *level_picker)
352 trace_assert(level_picker);
353 if (level_picker->items_cursor == 0) {
354 level_picker->items_cursor = level_picker->items.count - 1;
356 level_picker->items_cursor--;
360 void level_picker_cursor_down(LevelPicker *level_picker)
362 trace_assert(level_picker);
363 level_picker->items_cursor++;
364 if (level_picker->items_cursor == level_picker->items.count) {
365 level_picker->items_cursor = 0;