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_MOUSEWHEEL: {
250 if (event->wheel.y < 0) {
251 level_picker_cursor_down(level_picker);
252 } else if (event->wheel.y > 0) {
253 level_picker_cursor_up(level_picker);
257 case SDL_MOUSEMOTION: {
258 const Vec2f mouse_pos = vec((float) event->motion.x, (float) event->motion.y);
259 Vec2f position = vec_sum(
260 level_picker->items_position,
261 level_picker->items_scroll);
263 for (size_t i = 0; i < level_picker->items.count; ++i) {
264 const char *item_text = dynarray_pointer_at(
265 &level_picker->items,
268 Rect boundary_box = sprite_font_boundary_box(
270 LEVEL_PICKER_LIST_FONT_SCALE,
273 if (rect_contains_point(boundary_box, mouse_pos)) {
274 level_picker->items_cursor = i;
277 position.y += boundary_box.h + LEVEL_PICKER_LIST_PADDING_BOTTOM;
281 case SDL_MOUSEBUTTONDOWN: {
282 switch (event->button.button) {
283 case SDL_BUTTON_LEFT: {
284 // check if the click position was actually inside...
285 // note: make sure there's actually stuff in the list! tsoding likes
286 // to remove all levels and change title to "SMOL BREAK"...
287 if (level_picker->items.count == 0)
290 // note: this assumes that all list items are the same height!
291 // this is probably a valid assumption as long as we use a sprite font.
292 float single_item_height =
293 FONT_CHAR_HEIGHT * LEVEL_PICKER_LIST_FONT_SCALE.y + LEVEL_PICKER_LIST_PADDING_BOTTOM;
295 Vec2f position = vec_sum(
296 level_picker->items_position,
297 level_picker->items_scroll);
298 vec_add(&position, vec(0.0f, (float) level_picker->items_cursor * single_item_height));
300 const char *item_text =
302 &level_picker->items,
303 level_picker->items_cursor);
305 Rect boundary_box = sprite_font_boundary_box(
307 LEVEL_PICKER_LIST_FONT_SCALE,
310 const Vec2f mouse_pos = vec((float) event->motion.x, (float) event->motion.y);
311 if (rect_contains_point(boundary_box, mouse_pos)) {
312 level_picker->selected_item = (int) level_picker->items_cursor;
322 int level_picker_input(LevelPicker *level_picker,
323 const Uint8 *const keyboard_state,
324 SDL_Joystick *the_stick_of_joy)
326 trace_assert(level_picker);
327 trace_assert(keyboard_state);
328 (void) the_stick_of_joy;
332 const char *level_picker_selected_level(const LevelPicker *level_picker)
334 trace_assert(level_picker);
336 if (level_picker->selected_item < 0) {
340 return dynarray_pointer_at(
341 &level_picker->items,
342 (size_t)level_picker->selected_item);
345 void level_picker_clean_selection(LevelPicker *level_picker)
347 trace_assert(level_picker);
348 level_picker->selected_item = -1;
351 int level_picker_enter_camera_event(LevelPicker *level_picker,
354 camera_center_at(camera, level_picker->camera_position);
358 void level_picker_cursor_up(LevelPicker *level_picker)
360 trace_assert(level_picker);
361 if (level_picker->items_cursor > 0) {
362 level_picker->items_cursor--;
366 void level_picker_cursor_down(LevelPicker *level_picker)
368 trace_assert(level_picker);
369 if (level_picker->items_cursor + 1 < level_picker->items.count) {
370 level_picker->items_cursor++;