From 9f4cb283c15a1badd82f097a01790602d49d45a4 Mon Sep 17 00:00:00 2001
From: Dominic Masters <dominic@domsplace.com>
Date: Tue, 8 Oct 2024 09:17:00 -0500
Subject: [PATCH] UI Menu more or less done perfectly.

---
 lib/AudioFile                              |   1 +
 src/dawn/display/draw/drawstateoverworld.c |  16 +--
 src/dawn/display/draw/drawui.c             | 155 +++++++++++++--------
 src/dawn/display/draw/drawui.h             |  15 +-
 src/dawn/ui/menu.c                         |  17 ++-
 src/dawn/ui/menu.h                         |   5 +
 src/dawn/ui/testmenu.c                     |  25 +++-
 7 files changed, 153 insertions(+), 81 deletions(-)
 create mode 160000 lib/AudioFile

diff --git a/lib/AudioFile b/lib/AudioFile
new file mode 160000
index 00000000..bcb61a47
--- /dev/null
+++ b/lib/AudioFile
@@ -0,0 +1 @@
+Subproject commit bcb61a47e64fe4e7fc3d8a8664b8a23a192bddb7
diff --git a/src/dawn/display/draw/drawstateoverworld.c b/src/dawn/display/draw/drawstateoverworld.c
index d72643c5..36ff21a9 100644
--- a/src/dawn/display/draw/drawstateoverworld.c
+++ b/src/dawn/display/draw/drawstateoverworld.c
@@ -16,7 +16,7 @@ void drawStateOverworld() {
   map_t *map = GAME.currentMap;
   if(map == NULL) return;
 
-  // Try get player
+  // Draw the map, based on player position
   entity_t *player = mapEntityGetByType(map, ENTITY_TYPE_PLAYER);
   uint16_t cameraPositionX, cameraPositionY;
   if(player == NULL) {
@@ -27,14 +27,14 @@ void drawStateOverworld() {
     cameraPositionY = player->y;
   }
 
-  drawMap(
-    GAME.currentMap,
-    cameraPositionX, cameraPositionY,
-    0, 0,
-    FRAME_WIDTH, FRAME_HEIGHT
-  );
+  // drawMap(
+  //   GAME.currentMap,
+  //   cameraPositionX, cameraPositionY,
+  //   0, 0,
+  //   FRAME_WIDTH, FRAME_HEIGHT
+  // );
 
   // Draw UI
-  drawUITextbox();
+  // drawUITextbox();
   drawUITestMenu();
 }
\ No newline at end of file
diff --git a/src/dawn/display/draw/drawui.c b/src/dawn/display/draw/drawui.c
index a09279c7..919b55f5 100644
--- a/src/dawn/display/draw/drawui.c
+++ b/src/dawn/display/draw/drawui.c
@@ -12,6 +12,7 @@
 #include "display/draw/drawtext.h"
 #include "ui/textbox.h"
 #include "ui/testmenu.h"
+#include "util/math.h"
 
 void drawUIBox(
   const uint16_t x,
@@ -60,66 +61,134 @@ void drawUIBox(
 }
 
 void drawUIMenu(
-  const menu_t *menu,
+  menu_t *menu,
   const uint16_t x,
   const uint16_t y,
   const uint16_t width,
-  const char_t* strings[],
-  const int16_t stringCount,
+  const uint16_t height,
   const uint8_t cursorColor,
   const uint8_t textColor
 ) {
   assertTrue(menu, "Menu cannot be NULL.");
-  assertTrue(strings, "Strings cannot be NULL.");
-  assertTrue(stringCount > 0, "String count must be greater than 0.");
   assertTrue(menu->rows > 0, "Rows must be greater than 0.");
   assertTrue(menu->columns > 0, "Columns must be greater than 0.");
   assertTrue(width*2 > menu->columns, "Width must be greater than columns.");
 
-  uint16_t stringWidth = (width / menu->columns) + 1;// +1 for cursor and padding
+  uint16_t colWidth;
+  uint16_t startingCol;
+  uint16_t startingRow;
+  uint16_t rowCount;
+  uint16_t colCount;
+  char_t *str;
+  size_t i;
 
-  // Draw the menu
-  for(uint16_t row = 0; row < menu->rows; row++) {
-    for(uint16_t col = 0; col < menu->columns; col++) {
+  // Determine the width of each column. This must include space for the cursor
+  // and the text.
+  if(menu->columns == 1 || rowCount == 0) {
+    colWidth = width;
+    colCount = 1;
+  } else {
+    size_t longestString = 0;
+    i = 0;
+    while(true) {
+      str = menu->strings[i++];
+      if(str == NULL) break;
+
+      size_t len = strlen(str);
+      if(len > longestString) longestString = len;
+
+      // If we overrun the size of the box, we can stop looking.
+      if((len+1) > width) {
+        longestString = width;
+        break;
+      }
+    }
+
+    // How many columns can we fit?
+    uint16_t possibleColumnsAtThisWidth = width / (longestString + 1);
+
+    if(possibleColumnsAtThisWidth < menu->columns) {
+      // We need to take a subset of the columns
+      colWidth = width / possibleColumnsAtThisWidth;
+      colCount = possibleColumnsAtThisWidth;
+    } else {
+      // We can fit all columns in
+      colWidth = width / menu->columns;
+      colCount = menu->columns;
+    }
+
+    assertTrue(longestString != 0, "Longest string has length 0?");
+  }
+
+  // Determine count of rows to render
+  if(rowCount == 1) {
+    rowCount = 1;
+  } else {
+    rowCount = mathMin(rowCount, height);
+  }
+
+  // Where are we rendering from?
+  startingCol = menu->renderOffsetX;
+
+  if(menu->x >= startingCol + colCount) {
+    startingCol = (menu->x + 1) - colCount;
+  } else if(menu->x < startingCol) {
+    startingCol = menu->x;
+  }
+
+  startingRow = menu->renderOffsetY;
+  if(menu->y >= startingRow + rowCount) {
+    startingRow = (menu->y + 1) - rowCount;
+  } else if(menu->y < startingRow) {
+    startingRow = menu->y;
+  }
+
+  // Update back to the menu
+  menu->renderOffsetX = startingCol;
+  menu->renderOffsetY = startingRow;
+
+  for(uint16_t col = startingCol; col < startingCol+colCount; col++) {
+    for(uint16_t row = startingRow; row < startingRow+rowCount; row++) {
       uint16_t index = (row * menu->columns) + col;
-      if(index >= stringCount) break;
+      if(menu->strings[index] == NULL) continue;
 
       drawText(
-        strings[index],
-        -1,
-        x + (col * stringWidth) + 1,
-        y + row,
+        menu->strings[index],
+        colWidth,
+        x + (colWidth * (col - startingCol)) + 1,
+        y + (row - startingRow),
         textColor
       );
     }
   }
 
   // Draw the cursor
-  size_t i = (x + (menu->x * stringWidth)) + ((y + menu->y) * FRAME_WIDTH);
+  i = (
+    x + ((menu->x - startingCol) * colWidth)
+  ) + (
+    (y + (menu->y - startingRow)) * FRAME_WIDTH
+  );
   FRAME_BUFFER[i] = '>';
   FRAME_COLOR[i] = cursorColor;
 }
 
 void drawUIMenuBox(
-  const menu_t *menu,
+  menu_t *menu,
   const uint16_t x,
   const uint16_t y,
   const uint16_t width,
   const uint16_t height,
-  const char_t* strings[],
-  const int16_t stringCount,
   const uint8_t cursorColor,
   const uint8_t textColor,
   const uint8_t boxColor
 ) {
   drawUIBox(x, y, width, height, boxColor, true);
-
-  // drawUIMenu(
-  //   menu,
-  //   x + 1, y + 1, width - 2,
-  //   strings, stringCount,
-  //   cursorColor, textColor
-  // );
+  drawUIMenu(
+    menu,
+    x + 1, y + 1,
+    width - 2, height - 2,
+    cursorColor, textColor
+  );
 }
 
 void drawUITextbox() {
@@ -159,44 +228,10 @@ void drawUITextbox() {
 }
 
 void drawUITestMenu() {
-  const char_t* strings[] = {
-    "Option 1",
-    "Option 2",
-    "Option 3",
-    "Option 4",
-    "Option 5",
-    "Option 6",
-    "Option 7",
-    "Option 8",
-    "Option 9",
-    "Option 10",
-    "Option 11",
-    "Option 12",
-    "Option 13",
-    "Option 14",
-    "Option 15",
-    "Option 16",
-    "Option 17",
-    "Option 18",
-    "Option 19",
-    "Option 20",
-    "Option 21",
-    "Option 22",
-    "Option 23",
-    "Option 24",
-    "Option 25",
-    "Option 26",
-    "Option 27",
-    "Option 28",
-    "Option 29",
-    "Option 30"
-  };
-
   drawUIMenuBox(
     &TEST_MENU.menu,
     0, 0,
     FRAME_WIDTH, 5,
-    strings, sizeof(strings) / sizeof(char_t*),
-    COLOR_CYAN, COLOR_WHITE, COLOR_MAGENTA
+    COLOR_CYAN, COLOR_RED, COLOR_WHITE
   );
 }
\ No newline at end of file
diff --git a/src/dawn/display/draw/drawui.h b/src/dawn/display/draw/drawui.h
index 27157e0c..23fcde02 100644
--- a/src/dawn/display/draw/drawui.h
+++ b/src/dawn/display/draw/drawui.h
@@ -40,19 +40,16 @@ void drawUIBox(
  * @param x The x position to draw the menu.
  * @param y The y position to draw the menu.
  * @param width The width of the menu.
- * @param strings The strings to draw in the menu.
- * @param stringCount The number of strings in the menu.
+ * @param height The height of the menu.
  * @param cursorColor The color of the cursor.
  * @param textColor The color of the text.
  */
 void drawUIMenu(
-  const menu_t *menu,
+  menu_t *menu,
   const uint16_t x,
   const uint16_t y,
   const uint16_t width,
-  
-  const char_t* strings[],
-  const int16_t stringCount,
+  const uint16_t height,
   const uint8_t cursorColor,
   const uint8_t textColor
 );
@@ -65,20 +62,16 @@ void drawUIMenu(
  * @param y The y position to draw the menu.
  * @param width The width of the menu.
  * @param height The height of the menu.
- * @param strings The strings to draw in the menu.
- * @param stringCount The number of strings in the menu.
  * @param cursorColor The color of the cursor.
  * @param textColor The color of the text.
  * @param boxColor The color of the box.
  */
 void drawUIMenuBox(
-  const menu_t *menu,
+  menu_t *menu,
   const uint16_t x,
   const uint16_t y,
   const uint16_t width,
   const uint16_t height,
-  const char_t* strings[],
-  const int16_t stringCount,
   const uint8_t cursorColor,
   const uint8_t textColor,
   const uint8_t boxColor
diff --git a/src/dawn/ui/menu.c b/src/dawn/ui/menu.c
index d4b0b40c..e22e2e7d 100644
--- a/src/dawn/ui/menu.c
+++ b/src/dawn/ui/menu.c
@@ -10,7 +10,11 @@
 #include "input.h"
 #include "game/time.h"
 
-void menuInit(menu_t *menu, const uint16_t rows, const uint16_t columns) {
+void menuInit(
+  menu_t *menu,
+  const uint16_t columns,
+  const uint16_t rows
+) {
   assertNotNull(menu, "Menu cannot be NULL.");
   assertTrue(rows > 0, "Rows must be greater than 0.");
   assertTrue(columns > 0, "Columns must be greater than 0.");
@@ -109,4 +113,15 @@ void menuPositionSet(menu_t *menu, const uint16_t x, const uint16_t y) {
   assertNotNull(menu, "Menu cannot be NULL.");
   menu->x = x % menu->columns;
   menu->y = y % menu->rows;
+
+  // Is there a string here?
+  uint16_t index = (menu->y * menu->columns) + menu->x;
+  if(menu->strings[index] == NULL) {
+    if(menu->y == 0) {
+      // idk yet
+      menu->x = 0;
+    } else {
+      menu->y--;
+    }
+  }
 }
\ No newline at end of file
diff --git a/src/dawn/ui/menu.h b/src/dawn/ui/menu.h
index 6717e988..dc4be137 100644
--- a/src/dawn/ui/menu.h
+++ b/src/dawn/ui/menu.h
@@ -10,6 +10,7 @@
 
 #define MENU_HELD_TIME_INITIAL 0.5f
 #define MENU_HELD_TIME_REPEAT 0.2f
+#define MENU_ITEM_COUNT_MAX 256
 
 typedef enum {
   MENU_DIRECTION_UP = 0,
@@ -22,6 +23,10 @@ typedef struct {
   uint16_t x, y;
   uint16_t rows, columns;
   float_t repeatHeld;
+  const char_t* strings[MENU_ITEM_COUNT_MAX]; 
+
+  // Visual things
+  uint16_t renderOffsetX, renderOffsetY;
 } menu_t;
 
 /**
diff --git a/src/dawn/ui/testmenu.c b/src/dawn/ui/testmenu.c
index d7b35539..52ca6d7e 100644
--- a/src/dawn/ui/testmenu.c
+++ b/src/dawn/ui/testmenu.c
@@ -10,7 +10,30 @@
 testmenu_t TEST_MENU;
 
 void testMenuInit() {
-  menuInit(&TEST_MENU.menu, 3, 4);
+  const char_t* strings[] = {
+    "Option 1",
+    "Option 2",
+    "Option 3",
+    "Option 4",
+    "Option 5",
+    "Option 6",
+    "Option 7",
+    "Option 8",
+    "Option 9",
+    "Option 10",
+    "Option 11",
+    "Option 12",
+    "Option 13",
+    "Option 14",
+    "Option 15",
+    "Option 16",
+    "Option 17",
+  };
+
+  uint16_t columns = 3;
+  uint16_t rows = 6;
+  menuInit(&TEST_MENU.menu, columns, rows);
+  memcpy(TEST_MENU.menu.strings, strings, sizeof(strings));
 }
 
 void testMenuUpdate() {