]> git.lizzy.rs Git - nothing.git/blobdiff - src/game/level_picker.c
cursor -> items_cursor
[nothing.git] / src / game / level_picker.c
index 2131cd631aee7d53ceccf17083eed172d5e5f690..6085ae735a436ca79e77b0f3a6dba658784c71e5 100644 (file)
@@ -1,64 +1,56 @@
 #include <stdio.h>
 
 #include "./level_picker.h"
-#include "game/level/background.h"
+
 #include "game/sprite_font.h"
 #include "system/lt.h"
 #include "system/nth_alloc.h"
 #include "system/stacktrace.h"
 #include "system/str.h"
-#include "ui/list_selector.h"
 #include "system/log.h"
-#include "game/level_folder.h"
-#include "ui/wiggly_text.h"
-#include "ui/slider.h"
+#include "system/file.h"
 
 #define TITLE_MARGIN_TOP 100.0f
 #define TITLE_MARGIN_BOTTOM 100.0f
 
-struct LevelPicker
-{
-    Lt *lt;
-    Background *background;
-    Vec2f camera_position;
-    LevelFolder *level_folder;
-    WigglyText wiggly_text;
-    Slider volume_slider;
-    Vec2f volume_slider_scale;
-    ListSelector *list_selector;
-};
-
-LevelPicker *create_level_picker(const Sprite_font *sprite_font, const char *dirpath)
+#define LEVEL_PICKER_LIST_FONT_SCALE vec(5.0f, 5.0f)
+#define LEVEL_PICKER_LIST_PADDING_BOTTOM 50.0f
+
+#define ITEM_HEIGHT (FONT_CHAR_HEIGHT * LEVEL_PICKER_LIST_FONT_SCALE.y + LEVEL_PICKER_LIST_PADDING_BOTTOM)
+
+#define SCROLLBAR_WIDTH 20
+#define SCROLLING_SPEED_FRACTION 0.25f
+
+void level_picker_populate(LevelPicker *level_picker,
+                           const char *dirpath)
 {
+    trace_assert(level_picker);
     trace_assert(dirpath);
 
-    Lt *lt = create_lt();
+    level_picker->background.base_color = hexstr("073642");
+    level_picker->camera_position = vec(0.0f, 0.0f);
 
-    LevelPicker *level_picker = PUSH_LT(
-        lt,
-        nth_calloc(1, sizeof(LevelPicker)),
-        free);
-    if (level_picker == NULL) {
-        RETURN_LT(lt, NULL);
-    }
-    level_picker->lt = lt;
-
-    level_picker->background = PUSH_LT(
-        lt,
-        create_background(hexstr("073642")),
-        destroy_background);
-    if (level_picker->background == NULL) {
-        RETURN_LT(lt, NULL);
-    }
+    {
+        dynarray_clear(&level_picker->items);
+        level_picker->items = create_dynarray(METADATA_FILEPATH_MAX_SIZE);
 
-    level_picker->camera_position = vec(0.0f, 0.0f);
+        DIR *level_dir = opendir(dirpath);
+        if (level_dir == NULL) {
+            log_fail("Can't open asset folder: %s\n", dirpath);
+            abort();
+        }
+
+        char filepath[METADATA_FILEPATH_MAX_SIZE];
+        for (struct dirent *d = readdir(level_dir);
+             d != NULL;
+             d = readdir(level_dir)) {
+            if (*d->d_name == '.') continue;
 
-    level_picker->level_folder = PUSH_LT(
-        lt,
-        create_level_folder(dirpath),
-        destroy_level_folder);
-    if (level_picker->level_folder == NULL) {
-        RETURN_LT(lt, NULL);
+            snprintf(filepath, METADATA_FILEPATH_MAX_SIZE,
+                     "%s/%s", dirpath, d->d_name);
+            dynarray_push(&level_picker->items, filepath);
+        }
+        closedir(level_dir);
     }
 
     level_picker->wiggly_text = (WigglyText) {
@@ -66,34 +58,6 @@ LevelPicker *create_level_picker(const Sprite_font *sprite_font, const char *dir
         .scale = {10.0f, 10.0f},
         .color = COLOR_WHITE,
     };
-
-    level_picker->volume_slider = (Slider) {
-                                     .drag = 0,
-                                     .value = 80.0f,
-                                     .max_value = 100.0f,
-    };
-    level_picker->volume_slider_scale = vec(0.25f, 0.10f);
-
-    level_picker->list_selector = PUSH_LT(
-        lt,
-        create_list_selector(
-            sprite_font,
-            level_folder_titles(level_picker->level_folder),
-            level_folder_count(level_picker->level_folder),
-            vec(5.0f, 5.0f),
-            50.0f),
-        destroy_list_selector);
-    if (level_picker->list_selector == NULL) {
-        RETURN_LT(lt, NULL);
-    }
-
-    return level_picker;
-}
-
-void destroy_level_picker(LevelPicker *level_picker)
-{
-    trace_assert(level_picker);
-    RETURN_LT0(level_picker->lt);
 }
 
 int level_picker_render(const LevelPicker *level_picker,
@@ -103,21 +67,80 @@ int level_picker_render(const LevelPicker *level_picker,
 
     const Rect viewport = camera_view_port_screen(camera);
 
-    if (background_render(level_picker->background, camera) < 0) {
+    if (background_render(&level_picker->background, camera) < 0) {
         return -1;
     }
 
-    const Vec2f title_size = wiggly_text_size(&level_picker->wiggly_text, camera);
+    const Vec2f title_size = wiggly_text_size(&level_picker->wiggly_text);
+    const float scrolling_area_height = viewport.h - ITEM_HEIGHT - level_picker->items_position.y;
 
-    if (wiggly_text_render(
-            &level_picker->wiggly_text,
-            camera,
-            vec(viewport.w * 0.5f - title_size.x * 0.5f, TITLE_MARGIN_TOP)) < 0) {
-        return -1;
+    wiggly_text_render(
+        &level_picker->wiggly_text,
+        camera,
+        vec(viewport.w * 0.5f - title_size.x * 0.5f, TITLE_MARGIN_TOP));
+
+    const float proportional_scroll = level_picker->scroll.y * scrolling_area_height / level_picker->size.y;
+    const float number_of_items_in_scrolling_area = scrolling_area_height / ITEM_HEIGHT;
+    const float percent_of_visible_items = number_of_items_in_scrolling_area / ((float) level_picker->items.count - 1);
+
+    if(percent_of_visible_items < 1) {
+        SDL_Rect scrollbar = rect_for_sdl(
+            rect_from_vecs(
+                vec(level_picker->items_position.x + level_picker->size.x, level_picker->items_position.y),
+                vec(SCROLLBAR_WIDTH, scrolling_area_height)));
+
+        SDL_Rect scrollbar_thumb = rect_for_sdl(
+            rect_from_vecs(
+                vec(level_picker->items_position.x + level_picker->size.x, level_picker->items_position.y - proportional_scroll),
+                vec(SCROLLBAR_WIDTH, scrolling_area_height * percent_of_visible_items)));
+
+        if (SDL_SetRenderDrawColor(camera->renderer, 255, 255, 255, 255) < 0) {
+            return -1;
+        }
+
+        if (SDL_RenderDrawRect(camera->renderer, &scrollbar) < 0) {
+            return -1;
+        }
+
+        if (SDL_RenderFillRect(camera->renderer, &scrollbar_thumb) < 0) {
+            return -1;
+        }
     }
 
-    if (list_selector_render(level_picker->list_selector, camera->renderer) < 0) {
-        return -1;
+    for (size_t i = 0; i < level_picker->items.count; ++i) {
+        const Vec2f current_position = vec_sum(
+            level_picker->items_position,
+            vec(0.0f, (float) i * ITEM_HEIGHT + level_picker->scroll.y));
+
+        if(current_position.y > level_picker->items_position.y + scrolling_area_height ||
+            current_position.y < level_picker->items_position.y) {
+            continue;
+        }
+
+        const char *item_text = dynarray_pointer_at(&level_picker->items, i);
+
+        sprite_font_render_text(
+            &camera->font,
+            camera->renderer,
+            current_position,
+            LEVEL_PICKER_LIST_FONT_SCALE,
+            rgba(1.0f, 1.0f, 1.0f, 1.0f),
+            item_text);
+
+        if (i == level_picker->items_cursor) {
+            SDL_Rect boundary_box = rect_for_sdl(
+                sprite_font_boundary_box(
+                    current_position,
+                    LEVEL_PICKER_LIST_FONT_SCALE,
+                    item_text));
+            if (SDL_SetRenderDrawColor(camera->renderer, 255, 255, 255, 255) < 0) {
+                return -1;
+            }
+
+            if (SDL_RenderDrawRect(camera->renderer, &boundary_box) < 0) {
+                return -1;
+            }
+        }
     }
 
     {
@@ -127,39 +150,34 @@ int level_picker_render(const LevelPicker *level_picker,
         const Vec2f position = vec(0.0f, viewport.h - size.y * FONT_CHAR_HEIGHT);
 
         /* HTML */
-        if (camera_render_text_screen(
-                camera,
-                "Press 'N' to create new level",
-                size,
-                COLOR_WHITE,
-                vec(position.x + padding,
-                    position.y - padding)) < 0) {
-            return -1;
-        }
+        camera_render_text_screen(
+            camera,
+            "Press 'N' to create new level",
+            size,
+            COLOR_WHITE,
+            vec(position.x + padding,
+                position.y - padding));
     }
 
-    {
-        /* CSS volume */
-        const Rect position = {
-          .w = viewport.w * level_picker->volume_slider_scale.x,
-          .h = viewport.h * level_picker->volume_slider_scale.y,
-          .x = viewport.w - viewport.w * level_picker->volume_slider_scale.x - 5.0f,
-          .y = 5.0f,
-        };
-
-        /* HTML volume */
-        if (slider_render(&level_picker->volume_slider, camera, position) < 0) {
-            return -1;
-        }
-    }
     return 0;
 }
 
 int level_picker_update(LevelPicker *level_picker,
+                        Camera *camera,
                         float delta_time)
 {
     trace_assert(level_picker);
 
+    const Rect viewport = camera_view_port_screen(camera);
+    const float scrolling_area_height = viewport.h - ITEM_HEIGHT - level_picker->items_position.y;
+
+    if ((float) level_picker->items_cursor * ITEM_HEIGHT + level_picker->scroll.y > scrolling_area_height) {
+        level_picker->scroll.y -= ITEM_HEIGHT * SCROLLING_SPEED_FRACTION;
+    }
+    if ((float) level_picker->items_cursor * ITEM_HEIGHT + level_picker->scroll.y < 0) {
+        level_picker->scroll.y += ITEM_HEIGHT * SCROLLING_SPEED_FRACTION;
+    }
+
     vec_add(&level_picker->camera_position,
             vec(50.0f * delta_time, 0.0f));
 
@@ -170,61 +188,140 @@ int level_picker_update(LevelPicker *level_picker,
     return 0;
 }
 
+static
+Vec2f level_picker_list_size(const LevelPicker *level_picker)
+{
+    trace_assert(level_picker);
+
+    Vec2f result = vec(0.0f, 0.0f);
+
+    for (size_t i = 0; i < level_picker->items.count; ++i) {
+        const char *item_text = dynarray_pointer_at(
+            &level_picker->items,
+            i);
+
+        Rect boundary_box = sprite_font_boundary_box(
+            vec(0.0f, 0.0f),
+            LEVEL_PICKER_LIST_FONT_SCALE,
+            item_text);
+
+        result.x = fmaxf(result.x, boundary_box.w);
+        result.y += boundary_box.h + LEVEL_PICKER_LIST_PADDING_BOTTOM;
+    }
+
+    return result;
+}
+
+
 int level_picker_event(LevelPicker *level_picker,
-                       const SDL_Event *event,
-                       const Camera *camera)
+                       const SDL_Event *event)
 {
     trace_assert(level_picker);
     trace_assert(event);
 
-    {
-      const Rect viewport = camera_view_port_screen(camera);
-      const Rect position = {
-                             .w = viewport.w * level_picker->volume_slider_scale.x,
-                             .h = viewport.h * level_picker->volume_slider_scale.y,
-                             .x = viewport.w - viewport.w * level_picker->volume_slider_scale.x - 5.0f,
-                             .y = 5.0f,
-      };
-      int selected = 0;
-      if (slider_event(
-                &level_picker->volume_slider,
-                event,
-                position,
-                &selected) < 0) {
-        return -1;
-      }
-      if(selected){
-        return 0;
-      }
-    }
     switch (event->type) {
     case SDL_WINDOWEVENT: {
         switch (event->window.event) {
         case SDL_WINDOWEVENT_SHOWN:
-        case SDL_WINDOWEVENT_RESIZED: {
-            const Vec2f font_scale = vec(5.0f, 5.0f);
-            const float padding_bottom = 50.0f;
-
+        case SDL_WINDOWEVENT_SIZE_CHANGED: {
             int width;
-            SDL_GetWindowSize(SDL_GetWindowFromID(event->window.windowID), &width, NULL);
+            SDL_GetRendererOutputSize(SDL_GetRenderer(SDL_GetWindowFromID(event->window.windowID)), &width, NULL);
+            const Vec2f title_size = wiggly_text_size(&level_picker->wiggly_text);
+            level_picker->size = level_picker_list_size(level_picker);
+
+            level_picker->items_position =
+                vec((float)width * 0.5f - level_picker->size.x * 0.5f,
+                    TITLE_MARGIN_TOP + title_size.y + TITLE_MARGIN_BOTTOM);
+        } break;
+        }
+    } break;
+
+    case SDL_KEYDOWN:
+        switch (event->key.keysym.sym) {
+        case SDLK_UP:
+            if (level_picker->items_cursor == 0) {
+                level_picker->items_cursor = level_picker->items.count - 1;
+            } else {
+                level_picker->items_cursor--;
+            }
+            break;
+        case SDLK_DOWN:
+            level_picker->items_cursor++;
+            if (level_picker->items_cursor == level_picker->items.count) {
+                level_picker->items_cursor = 0;
+            }
+            break;
+        case SDLK_RETURN:
+            if (level_picker->items_cursor < level_picker->items.count) {
+                level_picker->selected_item = (int) level_picker->items_cursor;
+            }
+            break;
+        }
+        break;
+
+    case SDL_MOUSEMOTION: {
+        const Vec2f mouse_pos = vec((float) event->motion.x, (float) event->motion.y);
+        Vec2f position = vec_sum(
+            level_picker->items_position,
+            level_picker->scroll);
+
+        for (size_t i = 0; i < level_picker->items.count; ++i) {
+            const char *item_text = dynarray_pointer_at(
+                &level_picker->items,
+                i);
 
-            const Vec2f title_size = wiggly_text_size(&level_picker->wiggly_text, camera);
+            Rect boundary_box = sprite_font_boundary_box(
+                position,
+                LEVEL_PICKER_LIST_FONT_SCALE,
+                item_text);
+
+            if (rect_contains_point(boundary_box, mouse_pos)) {
+                level_picker->items_cursor = i;
+            }
+
+            position.y += boundary_box.h + LEVEL_PICKER_LIST_PADDING_BOTTOM;
+        }
+    } break;
 
-            const Vec2f selector_size = list_selector_size(
-                level_picker->list_selector,
-                font_scale,
-                padding_bottom);
+    case SDL_MOUSEBUTTONDOWN: {
+        switch (event->button.button) {
+        case SDL_BUTTON_LEFT: {
+            // check if the click position was actually inside...
+            // note: make sure there's actually stuff in the list! tsoding likes
+            // to remove all levels and change title to "SMOL BREAK"...
+            if (level_picker->items.count == 0)
+                break;
+
+            // note: this assumes that all list items are the same height!
+            // this is probably a valid assumption as long as we use a sprite font.
+            float single_item_height =
+                FONT_CHAR_HEIGHT * LEVEL_PICKER_LIST_FONT_SCALE.y + LEVEL_PICKER_LIST_PADDING_BOTTOM;
+
+            Vec2f position = vec_sum(
+                level_picker->items_position,
+                level_picker->scroll);
+            vec_add(&position, vec(0.0f, (float) level_picker->items_cursor * single_item_height));
+
+            const char *item_text =
+                dynarray_pointer_at(
+                    &level_picker->items,
+                    level_picker->items_cursor);
+
+            Rect boundary_box = sprite_font_boundary_box(
+                position,
+                LEVEL_PICKER_LIST_FONT_SCALE,
+                item_text);
 
-            list_selector_move(
-                level_picker->list_selector,
-                vec((float)width * 0.5f - selector_size.x * 0.5f,
-                    TITLE_MARGIN_TOP + title_size.y + TITLE_MARGIN_BOTTOM));
+            const Vec2f mouse_pos = vec((float) event->motion.x, (float) event->motion.y);
+            if (rect_contains_point(boundary_box, mouse_pos)) {
+                level_picker->selected_item = (int) level_picker->items_cursor;
+            }
         } break;
         }
     } break;
     }
 
-    return list_selector_event(level_picker->list_selector, event);
+    return 0;
 }
 
 int level_picker_input(LevelPicker *level_picker,
@@ -241,20 +338,19 @@ const char *level_picker_selected_level(const LevelPicker *level_picker)
 {
     trace_assert(level_picker);
 
-    const int selected_index = list_selector_selected(level_picker->list_selector);
-    if (selected_index < 0) {
+    if (level_picker->selected_item < 0) {
         return NULL;
     }
 
-    const char **filenames = level_folder_filenames(level_picker->level_folder);
-
-    return filenames[selected_index];
+    return dynarray_pointer_at(
+        &level_picker->items,
+        (size_t)level_picker->selected_item);
 }
 
 void level_picker_clean_selection(LevelPicker *level_picker)
 {
     trace_assert(level_picker);
-    list_selector_clean_selection(level_picker->list_selector);
+    level_picker->selected_item = -1;
 }
 
 int level_picker_enter_camera_event(LevelPicker *level_picker,
@@ -263,9 +359,3 @@ int level_picker_enter_camera_event(LevelPicker *level_picker,
     camera_center_at(camera, level_picker->camera_position);
     return 0;
 }
-
-float level_picker_get_volume(LevelPicker *level_picker)
-{
-  trace_assert(level_picker);
-  return level_picker->volume_slider.value;
-}