diff --git a/src/base/all.h b/src/base/all.h index c033289..59785bc 100644 --- a/src/base/all.h +++ b/src/base/all.h @@ -189,6 +189,10 @@ # define assertBreak() (*(volatile int*)0 = 0) #endif +#ifndef offsetof +#define offsetof(st, m) ((size_t)&(((st*)0)->m)) +#endif + #if ENABLE_ASSERT # define assert(c) stmnt( if (!(c)){ assertBreak(); } ) #else @@ -319,6 +323,24 @@ typedef enum Utf8Character { Utf8Character_Count, } Utf8Character; +typedef enum FieldType { + FieldTypeU8, + FieldTypeU16, + FieldTypeU32, + FieldTypeFloat, + FieldTypeString, + FieldTypeEnum, + FieldType_Count, +} FieldType; + +typedef struct FieldDescriptor { + str name; + FieldType type; + size_t offset; + int width; // column width for display + str* enum_vals; +} FieldDescriptor; + typedef struct Box { u32 x; u32 y; @@ -326,6 +348,13 @@ typedef struct Box { u32 width; } Box; +typedef struct TableDrawInfo { + u32 x_offset; + u32 y_offset; + u32 rows; + u32 cols; +} TableDrawInfo; + typedef struct Dim2 { u16 height; u16 width; diff --git a/src/client.c b/src/client.c index 2acb407..17d5d72 100644 --- a/src/client.c +++ b/src/client.c @@ -583,63 +583,11 @@ fn bool updateAndRender(TuiState* tui, void* s, u8* input_buffer, u64 loop_count renderStrToBuffer(tui->frame_buffer, 5, ++line, tmp_buffer, screen_dimensions); line++; // the table - u32 table_x = 5; - u32 table_col_pad = 1; - // render the headers - u32 col_x_pos = 0; - for (u32 i = 0; i < SHIP_DETAIL_COUNT; col_x_pos += SHIP_FIELDS[i].width+table_col_pad, i++) { - renderStrToBuffer(tui->frame_buffer, table_x+col_x_pos, line, SHIP_FIELDS[i].name, screen_dimensions); - for (u32 ii = 0; ii < SHIP_FIELDS[i].width; ii++) { - renderUtf8CharToBuffer(tui->frame_buffer, table_x+col_x_pos+ii, line+1, "━", screen_dimensions); - } - } - line++; - line++; - // render the rows - for (u32 i = 0; i < ShipType_Count; line++, i++) { - col_x_pos = 0; - ShipTemplate ship = SHIPS[i]; - // render the columns - for (u32 ii = 0; ii < SHIP_DETAIL_COUNT; col_x_pos += SHIP_FIELDS[ii].width+table_col_pad, ii++) { - if (i == state->menu.selected_index) { - for (u32 iii = 0; iii < SHIP_FIELDS[ii].width+table_col_pad; iii++) { - u32 pos = XYToPos(table_x+col_x_pos+iii, line, screen_dimensions.width); - tui->frame_buffer[pos].background = ANSI_WHITE; - tui->frame_buffer[pos].foreground = ANSI_BLACK; - tui->frame_buffer[pos].bytes[0] = ' '; - } - } - FieldDescriptor details = SHIP_FIELDS[ii]; - void *field_ptr = (char *)&ship + details.offset; - MemoryZero(tmp_buffer, 64); - switch (details.type) { - case FieldTypeEnum: { - renderStrToBuffer(tui->frame_buffer, table_x+col_x_pos, line, details.enum_vals[ship.type], screen_dimensions); - } break; - case FieldTypeU8: { - sprintf(tmp_buffer, "%d", *(u8 *)field_ptr); - renderStrToBuffer(tui->frame_buffer, table_x+col_x_pos, line, tmp_buffer, screen_dimensions); - } break; - case FieldTypeU16: { - sprintf(tmp_buffer, "%d", *(u16 *)field_ptr); - renderStrToBuffer(tui->frame_buffer, table_x+col_x_pos, line, tmp_buffer, screen_dimensions); - } break; - case FieldTypeU32: { - sprintf(tmp_buffer, "%d", *(u32 *)field_ptr); - renderStrToBuffer(tui->frame_buffer, table_x+col_x_pos, line, tmp_buffer, screen_dimensions); - } break; - case FieldTypeFloat: { - sprintf(tmp_buffer, "%f", *(f32 *)field_ptr); - renderStrToBuffer(tui->frame_buffer, table_x+col_x_pos, line, tmp_buffer, screen_dimensions); - } break; - case FieldTypeString: { - renderStrToBuffer(tui->frame_buffer, table_x+col_x_pos, line, (ptr)field_ptr, screen_dimensions); - } break; - case FieldType_Count: - break; - } - } - } + TableDrawInfo info = { + .x_offset = 5, .y_offset = line, + .rows = ShipType_Count, .cols = SHIP_DETAIL_COUNT, + }; + renderTable(tui, info, state->menu.selected_index, SHIP_FIELDS, 1, SHIPS, sizeof(SHIPS[0])); } break; case ScreenMainGame: { if (user_pressed_tab) { @@ -774,50 +722,23 @@ fn bool updateAndRender(TuiState* tui, void* s, u8* input_buffer, u64 loop_count renderStrToBuffer(tui->frame_buffer, box.x+2, box.y+1, "Welcome to ", screen_dimensions); renderStrToBuffer(tui->frame_buffer, box.x+2+11, box.y+1, state->current.name, screen_dimensions); - char tmp_buffer[64] = {0}; - u32 table_x = box.x+2; - u32 line = box.y+3; - str cols[6] = {"Commodity", "Unit", "#", "Bid", "Ask", "Owned"}; - u32 lens[6] = { 44, 6, 6, 6, 6, 6}; - u32 col_x_pos = 0; - // render headers - for (u32 i = 0; i < 6; col_x_pos += lens[i++]) { - renderStrToBuffer(tui->frame_buffer, table_x+col_x_pos, line, cols[i], screen_dimensions); - for (u32 ii = 0; ii < lens[i]; ii++) { - renderUtf8CharToBuffer(tui->frame_buffer, table_x+col_x_pos+ii, line+1, "━", screen_dimensions); - } - } - line += 2; - // render rows + // the table + TableDrawInfo info = { + .x_offset = box.x+2, .y_offset = box.y+3, + .rows = Commodity_Count, .cols = 6, + }; + MarketCommodity rows[Commodity_Count] = { 0 }; for (u32 i = 0; i < Commodity_Count; i++) { - col_x_pos = 0; + Commodity c = COMMODITIES[i]; u32 qty = state->current.planets[0].commodities[i] + state->current.planets[1].commodities[i] + state->current.planets[2].commodities[i]; - for (u32 ii = 0; ii < 6; col_x_pos += lens[ii++]) { - if (i == state->row.selected_index) { - for (u32 iii = 0; iii < lens[ii]; iii++) { - u32 pos = XYToPos(table_x+col_x_pos+iii, line+i, screen_dimensions.width); - tui->frame_buffer[pos].background = ANSI_WHITE; - tui->frame_buffer[pos].foreground = ANSI_BLACK; - tui->frame_buffer[pos].bytes[0] = ' '; - } - } - MemoryZero(tmp_buffer, 64); - if (ii == 0) { - sprintf(tmp_buffer, "%s", COMMODITIES[i].name); - } else if (ii == 1) { - sprintf(tmp_buffer, "%s", COMMODITIES[i].unit == StorageUnitKg ? "kg" : "cont"); - } else if (ii == 2) { - sprintf(tmp_buffer, "%d", qty); - } else if (ii == 3) { - sprintf(tmp_buffer, "%d", (u32)priceForCommodity(i, qty, true)); - } else if (ii == 4) { - sprintf(tmp_buffer, "%d", (u32)priceForCommodity(i, qty, false)); - } else if (ii == 5) { - sprintf(tmp_buffer, "%d", state->me.commodities[i]); - } - renderStrToBuffer(tui->frame_buffer, table_x+col_x_pos, line+i, tmp_buffer, screen_dimensions); - } + rows[i].type = c.type; + rows[i].unit = c.unit; + rows[i].bid = priceForCommodity(i, qty, true); + rows[i].ask = priceForCommodity(i, qty, false); + rows[i].qty = qty; + rows[i].owned = state->me.commodities[i]; } + renderTable(tui, info, state->row.selected_index, MARKET_COMMODITY_FIELDS, 1, rows, sizeof(MarketCommodity)); } break; case Tab_Count: {} break; } diff --git a/src/lib/tui.c b/src/lib/tui.c index 86360bd..8a2654c 100644 --- a/src/lib/tui.c +++ b/src/lib/tui.c @@ -525,6 +525,64 @@ fn Pos2 renderCommandPalette(TuiState* tui, String current_search, CommandPalett return result; } +fn void renderTable(TuiState* tui, TableDrawInfo t, u32 selected_index, FieldDescriptor fields[], u32 table_col_pad, void* data, u32 sizeof_data_item) { + char tmp_buffer[64] = {0}; + u32 col_x_pos = 0; + // render headers + for (u32 i = 0; i < t.cols; col_x_pos += fields[i++].width+table_col_pad) { + renderStrToBuffer(tui->frame_buffer, t.x_offset+col_x_pos, t.y_offset, fields[i].name, tui->screen_dimensions); + for (u32 ii = 0; ii < fields[i].width; ii++) { + renderUtf8CharToBuffer(tui->frame_buffer, t.x_offset+col_x_pos+ii, t.y_offset+1, "━", tui->screen_dimensions); + } + } + t.y_offset += 2; + // render the rows + for (u32 i = 0; i < t.rows; t.y_offset++, i++) { + col_x_pos = 0; + ptr item_offset = data + (sizeof_data_item * i); + // render the columns + for (u32 ii = 0; ii < t.cols; col_x_pos += fields[ii].width+table_col_pad, ii++) { + if (i == selected_index) { + for (u32 iii = 0; iii < fields[ii].width+table_col_pad; iii++) { + u32 pos = XYToPos(t.x_offset+col_x_pos+iii, t.y_offset, tui->screen_dimensions.width); + tui->frame_buffer[pos].background = ANSI_WHITE; + tui->frame_buffer[pos].foreground = ANSI_BLACK; + tui->frame_buffer[pos].bytes[0] = ' '; + } + } + FieldDescriptor details = fields[ii]; + void *field_ptr = item_offset + details.offset; + MemoryZero(tmp_buffer, 64); + switch (details.type) { + case FieldTypeEnum: { + renderStrToBuffer(tui->frame_buffer, t.x_offset+col_x_pos, t.y_offset, details.enum_vals[*(u8 *)field_ptr], tui->screen_dimensions); + } break; + case FieldTypeU8: { + sprintf(tmp_buffer, "%d", *(u8 *)field_ptr); + renderStrToBuffer(tui->frame_buffer, t.x_offset+col_x_pos, t.y_offset, tmp_buffer, tui->screen_dimensions); + } break; + case FieldTypeU16: { + sprintf(tmp_buffer, "%d", *(u16 *)field_ptr); + renderStrToBuffer(tui->frame_buffer, t.x_offset+col_x_pos, t.y_offset, tmp_buffer, tui->screen_dimensions); + } break; + case FieldTypeU32: { + sprintf(tmp_buffer, "%d", *(u32 *)field_ptr); + renderStrToBuffer(tui->frame_buffer, t.x_offset+col_x_pos, t.y_offset, tmp_buffer, tui->screen_dimensions); + } break; + case FieldTypeFloat: { + sprintf(tmp_buffer, "%f", *(f32 *)field_ptr); + renderStrToBuffer(tui->frame_buffer, t.x_offset+col_x_pos, t.y_offset, tmp_buffer, tui->screen_dimensions); + } break; + case FieldTypeString: { + renderStrToBuffer(tui->frame_buffer, t.x_offset+col_x_pos, t.y_offset, (ptr)field_ptr, tui->screen_dimensions); + } break; + case FieldType_Count: + break; + } + } + } +} + fn void renderChoiceMenu(TuiState* tui, u16 x, u16 y, ptr options[], u32 len, bool choosable, u32 selected_index, u8* colors) { for (u32 i = 0; i < len; i++) { u32 pos = x + (tui->screen_dimensions.width*(y+i)); diff --git a/src/shared.h b/src/shared.h index f2d4d03..26dc955 100644 --- a/src/shared.h +++ b/src/shared.h @@ -88,30 +88,6 @@ fn f32 calcInterestRate(u32 cost, u32 down_payment) { return mortgage_rate; } -typedef enum { - FieldTypeU8, - FieldTypeU16, - FieldTypeU32, - FieldTypeFloat, - FieldTypeString, - FieldTypeEnum, - FieldType_Count, -} FieldType; - -typedef struct FieldDescriptor { - str name; - FieldType type; - size_t offset; - int width; // column width for display - str* enum_vals; -} FieldDescriptor; - -// Define field metadata using offsetof -#ifndef offsetof -#define offsetof(st, m) ((size_t)&(((st*)0)->m)) -#endif - -// Field descriptors for Employee global FieldDescriptor SHIP_FIELDS[SHIP_DETAIL_COUNT] = { { "Type", FieldTypeEnum, offsetof(ShipTemplate, type), 16, (str*)&SHIP_TYPE_STRINGS }, { "Cost", FieldTypeU32, offsetof(ShipTemplate, base_cost), 8 }, @@ -155,6 +131,30 @@ typedef enum CommodityType { Commodity_Count, } CommodityType; +global str COMMODITY_STRINGS[Commodity_Count] = { + "Hydrogen Fuel", + "Oxygen", + "Water", + "Fertilizer", + "Raw Textiles", + "Low Grade Ore", + "High Grade Ore", + "Plastics", + "Grain", + "Meat", + "Spices", + "Electronics", + "Glass", + "Hand Tools", + "Semi-conductors (Silicon, Arsenic, Boron)", + "Common Metals (Iron, Nickel, Zinc)", + "Rare Metals (Titanium, Chromium)", + "Precious Metals (Silver, Gold, Platinum)", + "Alcohol", + "Clothes", + "Personal Sundries (Cutlery, Toys, Misc)", +}; + typedef enum StorageUnit { StorageUnitKg, StorageUnitContainer, @@ -162,6 +162,12 @@ typedef enum StorageUnit { StorageUnit_Count, } StorageUnit; +global str STORAGE_UNIT_STRINGS[StorageUnit_Count] = { + "kg", + "cont", + "atmo" +}; + typedef struct Commodity { CommodityType type; StorageUnit unit; @@ -257,6 +263,24 @@ global Commodity COMMODITIES[Commodity_Count] = { .price = 1800, .qty = 40, .consumption = 3, }, }; +typedef struct MarketCommodity { + CommodityType type; + StorageUnit unit; + u32 bid; + u32 ask; + u32 qty; + u32 owned; +} MarketCommodity; + +global FieldDescriptor MARKET_COMMODITY_FIELDS[Commodity_Count] = { + { "Commodity", FieldTypeEnum, offsetof(MarketCommodity, type), 44, (str*)&COMMODITY_STRINGS }, + { "Unit", FieldTypeEnum, offsetof(MarketCommodity, unit), 6, (str*)&STORAGE_UNIT_STRINGS }, + { "#", FieldTypeU32, offsetof(MarketCommodity, qty), 6 }, + { "Bid", FieldTypeU32, offsetof(MarketCommodity, bid), 6 }, + { "Ask", FieldTypeU32, offsetof(MarketCommodity, ask), 6 }, + { "Owned", FieldTypeU32, offsetof(MarketCommodity, owned), 6 }, +}; + fn f32 priceForCommodity(CommodityType type, u32 quantity, bool bid) { f32 result = 0.0; Commodity details = COMMODITIES[type];