diff --git a/workspace/all/common/api.c b/workspace/all/common/api.c index cbc1b6b7..54fc6327 100644 --- a/workspace/all/common/api.c +++ b/workspace/all/common/api.c @@ -69,9 +69,13 @@ UI_Layout ui = { .pill_height = 30, .row_count = 6, .padding = 10, + .edge_padding = 10, .text_baseline = 6, // (30 * 2) / 10 = 6 for 30dp pill - .button_size = 20, + .button_size = 20, // (30 * 2) / 3 = 20 for 30dp pill (button icons) .button_margin = 5, + .option_size = 22, // (30 * 3) / 4 = 22 for 30dp pill (submenu rows) + .option_baseline = 2, // (22 * 1) / 10 = 2 for 22dp option (font.medium) + .option_value_baseline = 4, // ~18% - slightly lower for smaller font.small .button_padding = 12, }; @@ -103,7 +107,7 @@ void UI_initLayout(int screen_width, int screen_height, float diagonal_inches) { // Calculate PPI and dp_scale float diagonal_px = sqrtf((float)(screen_width * screen_width + screen_height * screen_height)); float ppi = diagonal_px / diagonal_inches; - float raw_dp_scale = ppi / 144.0f; + float raw_dp_scale = ppi / 120.0f; // Apply platform scale modifier if defined #ifdef SCALE_MODIFIER @@ -119,10 +123,20 @@ void UI_initLayout(int screen_width, int screen_height, float diagonal_inches) { // All rows (content + footer) use the same pill_height for visual consistency const int MIN_PILL = 28; const int MAX_PILL = 32; - const int DEFAULT_PADDING = 10; + + // Internal padding between UI elements (always 10dp) + const int internal_padding = 10; + + // Edge padding: distance from screen edges + // EDGE_PADDING allows smaller values on devices where bezel provides visual margin +#ifdef EDGE_PADDING + const int edge_padding = EDGE_PADDING; +#else + const int edge_padding = internal_padding; +#endif int screen_height_dp = (int)(screen_height / gfx_dp_scale + 0.5f); - int available_dp = screen_height_dp - (DEFAULT_PADDING * 2); + int available_dp = screen_height_dp - (edge_padding * 2); // Calculate maximum possible rows (no arbitrary limit) int max_possible_rows = (available_dp / MIN_PILL) - 1; // -1 for footer @@ -182,7 +196,8 @@ void UI_initLayout(int screen_width, int screen_height, float diagonal_inches) { ui.screen_height_px = screen_height; ui.pill_height = best_pill; ui.row_count = best_rows; - ui.padding = DEFAULT_PADDING; + ui.padding = internal_padding; + ui.edge_padding = edge_padding; int used_dp = (ui.row_count + 1) * ui.pill_height; (void)used_dp; // Used in LOG_info below @@ -191,7 +206,7 @@ void UI_initLayout(int screen_width, int screen_height, float diagonal_inches) { (used_dp * 100.0f) / available_dp); // Derived proportional sizes (also ensure even pixels where needed) - ui.button_size = (ui.pill_height * 2) / 3; // ~20 for 30dp pill + ui.button_size = (ui.pill_height * 2) / 3; // ~20 for 30dp pill (button icons) int button_px = DP(ui.button_size); if (button_px % 2 != 0) ui.button_size++; // Buttons look better even @@ -199,10 +214,19 @@ void UI_initLayout(int screen_width, int screen_height, float diagonal_inches) { ui.button_margin = (ui.pill_height - ui.button_size) / 2; // Center button in pill ui.button_padding = (ui.pill_height * 2) / 5; // ~12 for 30dp pill + // Submenu option rows - larger than button icons + ui.option_size = (ui.pill_height * 3) / 4; // ~22 for 30dp pill + int option_px = DP(ui.option_size); + if (option_px % 2 != 0) + ui.option_size++; // Options look better even + // Text baseline offset - positions text slightly above center to account for // visual weight of font glyphs (most text sits above baseline, descenders are rare) // Gives ~6dp for 30dp pill, scales proportionally with pill height ui.text_baseline = (ui.pill_height * 2) / 10; + // Option baselines for submenu rows (smaller fonts need different positioning) + ui.option_baseline = (ui.option_size * 10 + 5) / 100; // ~10% with rounding (2dp for 22dp) + ui.option_value_baseline = (ui.option_size * 18 + 5) / 100; // ~18% with rounding (4dp for 22dp) // Settings indicators ui.settings_size = ui.pill_height / 8; // ~4dp for 30dp pill @@ -210,8 +234,8 @@ void UI_initLayout(int screen_width, int screen_height, float diagonal_inches) { LOG_info("UI_initLayout: %dx%d @ %.2f\" → PPI=%.0f, dp_scale=%.2f\n", screen_width, screen_height, diagonal_inches, ppi, gfx_dp_scale); - LOG_info("UI_initLayout: pill=%ddp, rows=%d, padding=%ddp\n", ui.pill_height, ui.row_count, - ui.padding); + LOG_info("UI_initLayout: pill=%ddp, rows=%d, padding=%ddp, edge_padding=%ddp\n", ui.pill_height, + ui.row_count, ui.padding, ui.edge_padding); } static struct GFX_Context { @@ -431,6 +455,7 @@ SDL_Surface* GFX_init(int mode) { asset_rgbs[ASSET_BLACK_PILL] = RGB_BLACK; asset_rgbs[ASSET_DARK_GRAY_PILL] = RGB_DARK_GRAY; asset_rgbs[ASSET_OPTION] = RGB_DARK_GRAY; + asset_rgbs[ASSET_OPTION_WHITE] = RGB_WHITE; asset_rgbs[ASSET_BUTTON] = RGB_WHITE; asset_rgbs[ASSET_PAGE_BG] = RGB_WHITE; asset_rgbs[ASSET_STATE_BG] = RGB_WHITE; @@ -467,6 +492,7 @@ SDL_Surface* GFX_init(int mode) { asset_rects[ASSET_BLACK_PILL] = (SDL_Rect){ASSET_SCALE4(33, 1, 30, 30)}; asset_rects[ASSET_DARK_GRAY_PILL] = (SDL_Rect){ASSET_SCALE4(65, 1, 30, 30)}; asset_rects[ASSET_OPTION] = (SDL_Rect){ASSET_SCALE4(97, 1, 20, 20)}; + asset_rects[ASSET_OPTION_WHITE] = (SDL_Rect){ASSET_SCALE4(1, 33, 20, 20)}; // Same as BUTTON src asset_rects[ASSET_BUTTON] = (SDL_Rect){ASSET_SCALE4(1, 33, 20, 20)}; asset_rects[ASSET_PAGE_BG] = (SDL_Rect){ASSET_SCALE4(64, 33, 15, 15)}; asset_rects[ASSET_STATE_BG] = (SDL_Rect){ASSET_SCALE4(23, 54, 8, 8)}; @@ -489,60 +515,116 @@ SDL_Surface* GFX_init(int mode) { asset_rects[ASSET_WIFI] = (SDL_Rect){ASSET_SCALE4(95, 39, 14, 10)}; asset_rects[ASSET_HOLE] = (SDL_Rect){ASSET_SCALE4(1, 63, 20, 20)}; - // Scale assets if dp_scale doesn't exactly match the loaded asset tier - // Uses per-asset scaling to ensure pixel-perfect pill caps and buttons - if (fabsf(gfx_dp_scale - (float)asset_scale) > 0.01f) { - float scale_ratio = gfx_dp_scale / (float)asset_scale; - int pill_px = DP(ui.pill_height); - int button_px = DP(ui.button_size); + // Calculate target pixel sizes for UI elements + float scale_ratio = gfx_dp_scale / (float)asset_scale; + int pill_px = DP(ui.pill_height); + int button_px = DP(ui.button_size); + int option_px = DP(ui.option_size); + + // Asset scaling categories: + // - SCALE_PILL: Scale to pill_px (main menu pills) + // - SCALE_BUTTON: Scale to button_px (button icons) + // - SCALE_OPTION: Scale to option_px, placed in virtual area (submenu option rows) + // - SCALE_PROPORTIONAL: Scale proportionally with dp_scale + // - SCALE_CENTERED: Scale proportionally, ensure even offset from pill_px for centering + enum { SCALE_PROPORTIONAL, SCALE_PILL, SCALE_BUTTON, SCALE_OPTION, SCALE_CENTERED }; + + // Define scaling behavior for each asset + // Assets with SCALE_OPTION need virtual area (they're scaled differently than source) + int asset_scale_type[ASSET_COUNT]; + for (int i = 0; i < ASSET_COUNT; i++) + asset_scale_type[i] = SCALE_PROPORTIONAL; // Default + + // Pills scale to pill_px + asset_scale_type[ASSET_WHITE_PILL] = SCALE_PILL; + asset_scale_type[ASSET_BLACK_PILL] = SCALE_PILL; + asset_scale_type[ASSET_DARK_GRAY_PILL] = SCALE_PILL; + + // Button icons scale to button_px + asset_scale_type[ASSET_BUTTON] = SCALE_BUTTON; + asset_scale_type[ASSET_HOLE] = SCALE_BUTTON; + + // Option pills scale to option_px (virtual assets - need their own space) + asset_scale_type[ASSET_OPTION] = SCALE_OPTION; + asset_scale_type[ASSET_OPTION_WHITE] = SCALE_OPTION; + + // Icons that get centered in pills need even pixel difference + asset_scale_type[ASSET_BRIGHTNESS] = SCALE_CENTERED; + asset_scale_type[ASSET_VOLUME_MUTE] = SCALE_CENTERED; + asset_scale_type[ASSET_VOLUME] = SCALE_CENTERED; + asset_scale_type[ASSET_WIFI] = SCALE_CENTERED; + + // Count virtual assets to calculate extra space needed + int virtual_asset_count = 0; + for (int i = 0; i < ASSET_COUNT; i++) { + if (asset_scale_type[i] == SCALE_OPTION) + virtual_asset_count++; + } - // Calculate destination sheet dimensions (proportionally scaled) + // Always scale if we have virtual assets or dp_scale doesn't match asset tier + int needs_scaling = + (fabsf(gfx_dp_scale - (float)asset_scale) > 0.01f) || (virtual_asset_count > 0); + + if (needs_scaling) { + // Calculate destination sheet dimensions + // Add extra row for virtual assets (those scaled to custom sizes) int sheet_w = (int)(loaded_assets->w * scale_ratio + 0.5f); - int sheet_h = (int)(loaded_assets->h * scale_ratio + 0.5f); + int base_h = (int)(loaded_assets->h * scale_ratio + 0.5f); + int virtual_row_h = (virtual_asset_count > 0) ? option_px : 0; + int sheet_h = base_h + virtual_row_h; // Create destination surface in RGBA8888 format - // Keep alpha channel throughout - SDL handles RGBA→RGB565 at final screen blit gfx.assets = SDL_CreateRGBSurface(0, sheet_w, sheet_h, 32, RGBA_MASK_8888); - // Process each asset individually to ensure exact sizing - // Pills/buttons get exact dimensions, other assets scale proportionally + // Track position for virtual assets + int virtual_x = 0; + int virtual_y = base_h; + + // Process each asset individually for (int i = 0; i < ASSET_COUNT; i++) { // Source rectangle in the @Nx sheet SDL_Rect src_rect = {asset_rects[i].x, asset_rects[i].y, asset_rects[i].w, asset_rects[i].h}; - // Destination position (scaled proportionally) + // Destination position (scaled proportionally by default) SDL_Rect dst_rect = {(int)(src_rect.x * scale_ratio + 0.5f), (int)(src_rect.y * scale_ratio + 0.5f), 0, 0}; int target_w, target_h; - // Determine target dimensions based on asset type - // Strategy: Pills/buttons need exact sizes (GFX_blitPill relies on perfect dimensions) - // Other assets (icons, battery) use proportional scaling with bilinear smoothing - if (i == ASSET_WHITE_PILL || i == ASSET_BLACK_PILL || i == ASSET_DARK_GRAY_PILL) { - // Pill caps (30×30 @1x) → exactly pill_px - // GFX_blitPill splits these in half for left/right caps + // Determine target dimensions based on scale type + switch (asset_scale_type[i]) { + case SCALE_PILL: target_w = target_h = pill_px; - } else if (i == ASSET_BUTTON || i == ASSET_HOLE || i == ASSET_OPTION) { - // Buttons/holes (20×20 @1x) → exactly button_px + break; + + case SCALE_BUTTON: target_w = target_h = button_px; - } else { - // All other assets: proportional scaling with rounding + break; + + case SCALE_OPTION: + // Virtual asset - place in dedicated area at bottom of sheet + target_w = target_h = option_px; + dst_rect.x = virtual_x; + dst_rect.y = virtual_y; + virtual_x += option_px; + break; + + case SCALE_CENTERED: + // Proportional but ensure even offset from pill for centering target_w = (int)(src_rect.w * scale_ratio + 0.5f); target_h = (int)(src_rect.h * scale_ratio + 0.5f); + if ((pill_px - target_w) % 2 != 0) + target_w++; + if ((pill_px - target_h) % 2 != 0) + target_h++; + break; - // Ensure even pixel difference from pill for perfect centering - // Only applies to standalone icons that get centered in pills - if (i == ASSET_BRIGHTNESS || i == ASSET_VOLUME_MUTE || i == ASSET_VOLUME || - i == ASSET_WIFI) { - if ((pill_px - target_w) % 2 != 0) - target_w++; - if ((pill_px - target_h) % 2 != 0) - target_h++; - } - // Battery assets (outline, fill, bolt) use proportional scaling only - // Their internal positioning is handled by proportional offsets in GFX_blitBattery + case SCALE_PROPORTIONAL: + default: + target_w = (int)(src_rect.w * scale_ratio + 0.5f); + target_h = (int)(src_rect.h * scale_ratio + 0.5f); + break; } // Extract this asset region from source sheet into RGBA8888 surface @@ -1033,18 +1115,23 @@ void GFX_blitPill(int asset, SDL_Surface* dst, const SDL_Rect* dst_rect) { if (h == 0) h = asset_rects[asset].h; - int r = h / 2; + // Asset is a square (pill_px × pill_px), split into left and right halves + // For odd heights, left cap gets the extra pixel to avoid clipping + int asset_w = asset_rects[asset].w; + int left_cap = (asset_w + 1) / 2; // rounds up for odd widths + int right_cap = asset_w / 2; // rounds down for odd widths + if (w < h) w = h; - w -= h; + int middle = w - h; - GFX_blitAsset(asset, &(SDL_Rect){0, 0, r, h}, dst, &(SDL_Rect){x, y}); - x += r; - if (w > 0) { - SDL_FillRect(dst, &(SDL_Rect){x, y, w, h}, asset_rgbs[asset]); - x += w; + GFX_blitAsset(asset, &(SDL_Rect){0, 0, left_cap, h}, dst, &(SDL_Rect){x, y}); + x += left_cap; + if (middle > 0) { + SDL_FillRect(dst, &(SDL_Rect){x, y, middle, h}, asset_rgbs[asset]); + x += middle; } - GFX_blitAsset(asset, &(SDL_Rect){r, 0, r, h}, dst, &(SDL_Rect){x, y}); + GFX_blitAsset(asset, &(SDL_Rect){left_cap, 0, right_cap, h}, dst, &(SDL_Rect){x, y}); } /** @@ -1210,14 +1297,13 @@ void GFX_blitButton(char* hint, char* button, SDL_Surface* dst, SDL_Rect* dst_re if (strlen(button) == 1) { GFX_blitAsset(ASSET_BUTTON, NULL, dst, dst_rect); - // label - center text in button using DP-aware centering - // Bias vertical position up slightly to account for visual weight of glyphs + // label - center glyph in button using precise glyph metrics text = TTF_RenderUTF8_Blended(font.medium, button, COLOR_BUTTON_TEXT); - int offset_y = - DP_CENTER_PX(ui.button_size, text->h) - DP(1); // Move up ~1dp for visual balance + int offset_x, offset_y; + GFX_centerGlyph(DP(ui.button_size), DP(ui.button_size), font.medium, (Uint16)button[0], + &offset_x, &offset_y); SDL_BlitSurface(text, NULL, dst, - &(SDL_Rect){dst_rect->x + DP_CENTER_PX(ui.button_size, text->w), - dst_rect->y + offset_y}); + &(SDL_Rect){dst_rect->x + offset_x, dst_rect->y + offset_y}); ox += DP(ui.button_size); SDL_FreeSurface(text); } else { @@ -1325,8 +1411,8 @@ int GFX_blitHardwareGroup(SDL_Surface* dst, int show_setting) { if (show_setting && !GetHDMI()) { ow = DP(ui.pill_height + ui.settings_width + ui.padding + 4); - ox = ui.screen_width_px - DP(ui.padding) - ow; - oy = DP(ui.padding); + ox = ui.screen_width_px - DP(ui.edge_padding) - ow; + oy = DP(ui.edge_padding); GFX_blitPill(gfx.mode == MODE_MAIN ? ASSET_DARK_GRAY_PILL : ASSET_BLACK_PILL, dst, &(SDL_Rect){ox, oy, ow, DP(ui.pill_height)}); @@ -1369,8 +1455,8 @@ int GFX_blitHardwareGroup(SDL_Surface* dst, int show_setting) { if (show_wifi) ow += ww; - ox = ui.screen_width_px - DP(ui.padding) - ow; - oy = DP(ui.padding); + ox = ui.screen_width_px - DP(ui.edge_padding) - ow; + oy = DP(ui.edge_padding); GFX_blitPill(gfx.mode == MODE_MAIN ? ASSET_DARK_GRAY_PILL : ASSET_BLACK_PILL, dst, &(SDL_Rect){ox, oy, ow, DP(ui.pill_height)}); if (show_wifi) { @@ -1439,8 +1525,8 @@ int GFX_blitButtonGroup(char** pairs, int primary, SDL_Surface* dst, int align_r int w = 0; // individual button dimension int h = 0; // hints index ow = 0; // full pill width - ox = align_right ? dst->w - DP(ui.padding) : DP(ui.padding); - oy = dst->h - DP(ui.padding + ui.pill_height); + ox = align_right ? dst->w - DP(ui.edge_padding) : DP(ui.edge_padding); + oy = dst->h - DP(ui.edge_padding + ui.pill_height); for (int i = 0; i < 2; i++) { if (!pairs[i * 2]) diff --git a/workspace/all/common/api.h b/workspace/all/common/api.h index f69e0b8d..8aee2ea4 100644 --- a/workspace/all/common/api.h +++ b/workspace/all/common/api.h @@ -76,6 +76,42 @@ extern float gfx_dp_scale; */ #define DP_CENTER_PX(dp_size, px_size) (((DP(dp_size) - (px_size)) + 1) / 2) +/** + * Calculate X and Y offsets to visually center a single glyph in a container. + * + * Uses TTF_GlyphMetrics to get precise glyph bounds rather than relying on + * the rendered surface dimensions (which include side bearings and descender space). + * This produces accurate visual centering for button labels like "A", "B", etc. + * + * @param container_w Container width in pixels + * @param container_h Container height in pixels + * @param font TTF_Font to get metrics from + * @param ch Character to center (as UTF-16 codepoint) + * @param out_x Pointer to receive X offset for the text surface + * @param out_y Pointer to receive Y offset for the text surface + */ +static inline void GFX_centerGlyph(int container_w, int container_h, TTF_Font* font, Uint16 ch, + int* out_x, int* out_y) { + int minx, maxx, miny, maxy, advance; + TTF_GlyphMetrics(font, ch, &minx, &maxx, &miny, &maxy, &advance); + + // Glyph visual width and height (actual inked area) + int glyph_w = maxx - minx; + int glyph_h = maxy - miny; + + // Center the visual glyph bounds within container + // minx is the left bearing (offset from render origin to first inked pixel) + // The rendered surface places the glyph at x=minx, so we adjust for that + *out_x = (container_w - glyph_w + 1) / 2 - minx; + + // maxy is distance from baseline to top of glyph + // Ascent is the font's max distance above baseline + // The rendered surface has ascent pixels above the baseline + int ascent = TTF_FontAscent(font); + int glyph_top_in_surface = ascent - maxy; // where glyph starts in surface + *out_y = (container_h - glyph_h + 1) / 2 - glyph_top_in_surface; +} + /** * Convert physical pixels to display points. * @@ -113,10 +149,14 @@ typedef struct UI_Layout { int screen_height_px; // Screen height in pixels (cached for convenience) int pill_height; // Height of menu pills in dp (28-32 typical) int row_count; // Number of visible menu rows (6-8) - int padding; // Screen edge padding in dp + int padding; // Internal spacing between UI elements in dp + int edge_padding; // Distance from screen edges in dp (reduced on devices with bezels) int text_baseline; // Vertical offset for text centering in pill - int button_size; // Size of button graphics in dp + int button_size; // Size of button icons in dp int button_margin; // Margin around buttons in dp + int option_size; // Height of submenu option rows in dp + int option_baseline; // Vertical offset for label text (font.medium) in option rows + int option_value_baseline; // Vertical offset for value text (font.small) in option rows int button_padding; // Padding inside buttons in dp int settings_size; // Size of setting indicators in dp int settings_width; // Width of setting indicators in dp @@ -232,8 +272,9 @@ enum { ASSET_WHITE_PILL, // Rounded rectangle (white) ASSET_BLACK_PILL, // Rounded rectangle (black) ASSET_DARK_GRAY_PILL, // Rounded rectangle (dark gray) - ASSET_OPTION, // Option indicator - ASSET_BUTTON, // Button background + ASSET_OPTION, // Option row background (gray, option_size) + ASSET_OPTION_WHITE, // Option row selected (white, option_size) + ASSET_BUTTON, // Button background (white, button_size) ASSET_PAGE_BG, // Page background ASSET_STATE_BG, // State indicator background ASSET_PAGE, // Page indicator diff --git a/workspace/all/minarch/minarch.c b/workspace/all/minarch/minarch.c index 86073bf9..e64354f9 100644 --- a/workspace/all/minarch/minarch.c +++ b/workspace/all/minarch/minarch.c @@ -4637,8 +4637,8 @@ static int Menu_message(char* message, char** pairs) { if (dirty) { GFX_clear(screen); GFX_blitMessage(font.medium, message, screen, - &(SDL_Rect){0, DP(ui.padding), DP(ui.screen_width), - DP(ui.screen_height - ui.pill_height - ui.padding)}); + &(SDL_Rect){0, DP(ui.edge_padding), DP(ui.screen_width), + DP(ui.screen_height - ui.pill_height - ui.edge_padding)}); GFX_blitButtonGroup(pairs, 0, screen, 1); GFX_flip(screen); dirty = 0; @@ -5177,6 +5177,58 @@ static void OptionSaveChanges_updateDesc(void) { #define OPTION_PADDING 8 +/** + * Calculates optimal label and value widths using proportional truncation. + * + * Distributes available space fairly based on natural text sizes. If both fit, + * uses natural sizes. Otherwise, distributes proportionally while enforcing + * minimum allocations (25% for label, 20% for value). + * + * @param label_text Label string to measure + * @param value_text Value string to measure (NULL if no value) + * @param total_width Total available width in pixels + * @param label_w_out Output: allocated width for label text + * @param value_w_out Output: allocated width for value text + */ +static void calculateProportionalWidths(const char* label_text, const char* value_text, + int total_width, int* label_w_out, int* value_w_out) { + int natural_label_w, natural_value_w = 0; + TTF_SizeUTF8(font.medium, label_text, &natural_label_w, NULL); + if (value_text) + TTF_SizeUTF8(font.small, value_text, &natural_value_w, NULL); + + int total_natural = natural_label_w + natural_value_w; + + if (total_natural <= total_width) { + // Both fit! Use natural sizes + *label_w_out = natural_label_w; + *value_w_out = natural_value_w; + } else { + // Distribute proportionally based on natural ratio + if (total_natural > 0) { + *label_w_out = (total_width * natural_label_w) / total_natural; + *value_w_out = (total_width * natural_value_w) / total_natural; + } else { + // Fallback: split 50/50 (should never happen) + *label_w_out = total_width / 2; + *value_w_out = total_width / 2; + } + + // Enforce minimums (25% label, 20% value) + int min_label = total_width / 4; + int min_value = total_width / 5; + + if (*label_w_out < min_label) { + *label_w_out = min_label; + *value_w_out = total_width - *label_w_out; + } + if (*value_w_out < min_value) { + *value_w_out = min_value; + *label_w_out = total_width - *value_w_out; + } + } +} + static int Menu_options(MenuList* list) { MenuItem* items = list->items; int type = list->type; @@ -5187,8 +5239,8 @@ static int Menu_options(MenuList* list) { int await_input = 0; // dependent on option list offset top and bottom, eg. the gray triangles - int max_visible_options = (ui.screen_height - (ui.padding + ui.pill_height) * 2) / - ui.button_size; // 7 for 480, 10 for 720 + int max_visible_options = (ui.screen_height - (ui.edge_padding + ui.pill_height) * 2) / + ui.option_size; // 7 for 480, 10 for 720 int count; for (count = 0; items[count].name; count++) @@ -5361,17 +5413,17 @@ static int Menu_options(MenuList* list) { for (int i = 0; i < count; i++) { MenuItem* item = &items[i]; int w = 0; - TTF_SizeUTF8(font.small, item->name, &w, NULL); + TTF_SizeUTF8(font.medium, item->name, &w, NULL); w += DP(OPTION_PADDING * 2); if (w > mw) mw = w; } // cache the result - list->max_width = mw = MIN(mw, DP(ui.screen_width - ui.padding * 2)); + list->max_width = mw = MIN(mw, DP(ui.screen_width - ui.edge_padding * 2)); } int ox = DP_CENTER_PX(ui.screen_width, mw); - int oy = DP(ui.padding + ui.pill_height); + int oy = DP(ui.edge_padding + ui.pill_height); int selected_row = selected - start; for (int i = start, j = 0; i < end; i++, j++) { MenuItem* item = &items[i]; @@ -5381,30 +5433,28 @@ static int Menu_options(MenuList* list) { if (j == selected_row) { // move out of conditional if centering int w = 0; - TTF_SizeUTF8(font.small, item->name, &w, NULL); + TTF_SizeUTF8(font.medium, item->name, &w, NULL); w += DP(OPTION_PADDING * 2); GFX_blitPill( - ASSET_BUTTON, screen, - &(SDL_Rect){ox, oy + DP(j * ui.button_size), w, DP(ui.button_size)}); + ASSET_OPTION_WHITE, screen, + &(SDL_Rect){ox, oy + DP(j * ui.option_size), w, DP(ui.option_size)}); text_color = COLOR_BLACK; if (item->desc) desc = item->desc; } - text = TTF_RenderUTF8_Blended(font.small, item->name, text_color); - SDL_BlitSurface( - text, NULL, screen, - &(SDL_Rect){ox + DP(OPTION_PADDING), oy + DP((j * ui.button_size) + 1)}); + text = TTF_RenderUTF8_Blended(font.medium, item->name, text_color); + SDL_BlitSurface(text, NULL, screen, + &(SDL_Rect){ox + DP(OPTION_PADDING), + oy + DP(j * ui.option_size + ui.option_baseline)}); SDL_FreeSurface(text); } } else if (type == MENU_FIXED) { // NOTE: no need to calculate max width - int mw = DP(ui.screen_width - ui.padding * 2); - // int lw,rw; - // lw = rw = mw / 2; + int mw = DP(ui.screen_width - ui.edge_padding * 2); int ox, oy; - ox = oy = DP(ui.padding); + ox = oy = DP(ui.edge_padding); oy += DP(ui.pill_height); int selected_row = selected - start; @@ -5416,36 +5466,48 @@ static int Menu_options(MenuList* list) { // gray pill GFX_blitPill( ASSET_OPTION, screen, - &(SDL_Rect){ox, oy + DP(j * ui.button_size), mw, DP(ui.button_size)}); + &(SDL_Rect){ox, oy + DP(j * ui.option_size), mw, DP(ui.option_size)}); } + // Calculate optimal widths using proportional truncation + int label_text_w, value_text_w; + int total_available = mw - DP(OPTION_PADDING * 3); + const char* value_str = (item->value >= 0) ? item->values[item->value] : NULL; + calculateProportionalWidths(item->name, value_str, total_available, + &label_text_w, &value_text_w); + + int label_w = label_text_w + DP(OPTION_PADDING * 2); + + // Render value text if (item->value >= 0) { - text = TTF_RenderUTF8_Blended(font.tiny, item->values[item->value], - COLOR_WHITE); // always white - SDL_BlitSurface(text, NULL, screen, - &(SDL_Rect){ox + mw - text->w - DP(OPTION_PADDING), - oy + DP((j * ui.button_size) + 3)}); + char truncated[256]; + GFX_truncateText(font.small, item->values[item->value], truncated, + value_text_w, 0); + text = TTF_RenderUTF8_Blended(font.small, truncated, COLOR_WHITE); + SDL_BlitSurface( + text, NULL, screen, + &(SDL_Rect){ox + mw - text->w - DP(OPTION_PADDING), + oy + DP(j * ui.option_size + ui.option_value_baseline)}); SDL_FreeSurface(text); } - // TODO: blit a black pill on unselected rows (to cover longer item->values?) or truncate longer item->values? if (j == selected_row) { // white pill - int w = 0; - TTF_SizeUTF8(font.small, item->name, &w, NULL); - w += DP(OPTION_PADDING * 2); - GFX_blitPill( - ASSET_BUTTON, screen, - &(SDL_Rect){ox, oy + DP(j * ui.button_size), w, DP(ui.button_size)}); + GFX_blitPill(ASSET_OPTION_WHITE, screen, + &(SDL_Rect){ox, oy + DP(j * ui.option_size), label_w, + DP(ui.option_size)}); text_color = COLOR_BLACK; if (item->desc) desc = item->desc; } - text = TTF_RenderUTF8_Blended(font.small, item->name, text_color); - SDL_BlitSurface( - text, NULL, screen, - &(SDL_Rect){ox + DP(OPTION_PADDING), oy + DP((j * ui.button_size) + 1)}); + // Render label text + char label_truncated[256]; + GFX_truncateText(font.medium, item->name, label_truncated, label_text_w, 0); + text = TTF_RenderUTF8_Blended(font.medium, label_truncated, text_color); + SDL_BlitSurface(text, NULL, screen, + &(SDL_Rect){ox + DP(OPTION_PADDING), + oy + DP(j * ui.option_size + ui.option_baseline)}); SDL_FreeSurface(text); } } else if (type == MENU_VAR || type == MENU_INPUT) { @@ -5458,13 +5520,13 @@ static int Menu_options(MenuList* list) { int w = 0; int lw = 0; int rw = 0; - TTF_SizeUTF8(font.small, item->name, &lw, NULL); + TTF_SizeUTF8(font.medium, item->name, &lw, NULL); // every value list in an input table is the same // so only calculate rw for the first item... if (!mrw || type != MENU_INPUT) { for (int j = 0; item->values[j]; j++) { - TTF_SizeUTF8(font.tiny, item->values[j], &rw, NULL); + TTF_SizeUTF8(font.small, item->values[j], &rw, NULL); if (lw + rw > w) w = lw + rw; if (rw > mrw) @@ -5478,48 +5540,61 @@ static int Menu_options(MenuList* list) { mw = w; } // cache the result - list->max_width = mw = MIN(mw, DP(ui.screen_width - ui.padding * 2)); + list->max_width = mw = MIN(mw, DP(ui.screen_width - ui.edge_padding * 2)); } int ox = DP_CENTER_PX(ui.screen_width, mw); - int oy = DP(ui.padding + ui.pill_height); + int oy = DP(ui.edge_padding + ui.pill_height); int selected_row = selected - start; for (int i = start, j = 0; i < end; i++, j++) { MenuItem* item = &items[i]; SDL_Color text_color = COLOR_WHITE; + // Calculate optimal widths using proportional truncation + int label_text_w, value_text_w; + int total_available = mw - DP(OPTION_PADDING * 3); + const char* value_str = (item->value >= 0) ? item->values[item->value] : NULL; + calculateProportionalWidths(item->name, value_str, total_available, + &label_text_w, &value_text_w); + + int label_w = label_text_w + DP(OPTION_PADDING * 2); + if (j == selected_row) { // gray pill GFX_blitPill( ASSET_OPTION, screen, - &(SDL_Rect){ox, oy + DP(j * ui.button_size), mw, DP(ui.button_size)}); + &(SDL_Rect){ox, oy + DP(j * ui.option_size), mw, DP(ui.option_size)}); // white pill - int w = 0; - TTF_SizeUTF8(font.small, item->name, &w, NULL); - w += DP(OPTION_PADDING * 2); - GFX_blitPill( - ASSET_BUTTON, screen, - &(SDL_Rect){ox, oy + DP(j * ui.button_size), w, DP(ui.button_size)}); + GFX_blitPill(ASSET_OPTION_WHITE, screen, + &(SDL_Rect){ox, oy + DP(j * ui.option_size), label_w, + DP(ui.option_size)}); text_color = COLOR_BLACK; if (item->desc) desc = item->desc; } - text = TTF_RenderUTF8_Blended(font.small, item->name, text_color); - SDL_BlitSurface( - text, NULL, screen, - &(SDL_Rect){ox + DP(OPTION_PADDING), oy + DP((j * ui.button_size) + 1)}); + // Render label text + char label_truncated[256]; + GFX_truncateText(font.medium, item->name, label_truncated, label_text_w, 0); + text = TTF_RenderUTF8_Blended(font.medium, label_truncated, text_color); + SDL_BlitSurface(text, NULL, screen, + &(SDL_Rect){ox + DP(OPTION_PADDING), + oy + DP(j * ui.option_size + ui.option_baseline)}); SDL_FreeSurface(text); if (await_input && j == selected_row) { // buh } else if (item->value >= 0) { - text = TTF_RenderUTF8_Blended(font.tiny, item->values[item->value], - COLOR_WHITE); // always white - SDL_BlitSurface(text, NULL, screen, - &(SDL_Rect){ox + mw - text->w - DP(OPTION_PADDING), - oy + DP((j * ui.button_size) + 3)}); + // Render value text + char truncated[256]; + GFX_truncateText(font.small, item->values[item->value], truncated, + value_text_w, 0); + text = TTF_RenderUTF8_Blended(font.small, truncated, COLOR_WHITE); + SDL_BlitSurface( + text, NULL, screen, + &(SDL_Rect){ox + mw - text->w - DP(OPTION_PADDING), + oy + DP(j * ui.option_size + ui.option_value_baseline)}); SDL_FreeSurface(text); } } @@ -5528,16 +5603,17 @@ static int Menu_options(MenuList* list) { if (count > max_visible_options) { #define SCROLL_WIDTH 24 #define SCROLL_HEIGHT 4 +#define SCROLL_MARGIN 4 // Tight spacing anchored to option list int ox = (DP(ui.screen_width) - DP(SCROLL_WIDTH)) / 2; - int oy = (DP(ui.pill_height) - DP(SCROLL_HEIGHT)) / 2; + int options_top = DP(ui.edge_padding + ui.pill_height); + int options_bottom = options_top + DP(max_visible_options * ui.option_size); + if (start > 0) GFX_blitAsset(ASSET_SCROLL_UP, NULL, screen, - &(SDL_Rect){ox, DP(ui.padding) + oy}); + &(SDL_Rect){ox, options_top - DP(SCROLL_HEIGHT + SCROLL_MARGIN)}); if (end < count) GFX_blitAsset(ASSET_SCROLL_DOWN, NULL, screen, - &(SDL_Rect){ox, DP(ui.screen_height - ui.padding - - ui.pill_height - ui.button_size) + - oy}); + &(SDL_Rect){ox, options_bottom + DP(SCROLL_MARGIN)}); } if (!desc && list->desc) @@ -5548,7 +5624,7 @@ static int Menu_options(MenuList* list) { GFX_sizeText(font.tiny, desc, DP(12), &w, &h); GFX_blitText(font.tiny, desc, DP(12), COLOR_WHITE, screen, &(SDL_Rect){DP_CENTER_PX(ui.screen_width, w), - DP(ui.screen_height) - DP(ui.padding) - h, w, h}); + DP(ui.screen_height) - DP(ui.edge_padding) - h, w, h}); } GFX_flip(screen); @@ -6018,7 +6094,7 @@ static void Menu_loop(void) { int ox, oy; int ow = GFX_blitHardwareGroup(screen, show_setting); - int max_width = DP(ui.screen_width) - DP(ui.padding * 2) - ow; + int max_width = DP(ui.screen_width) - DP(ui.edge_padding * 2) - ow; char display_name[256]; int text_width = GFX_truncateText(font.large, rom_name, display_name, max_width, @@ -6027,12 +6103,13 @@ static void Menu_loop(void) { SDL_Surface* text; text = TTF_RenderUTF8_Blended(font.large, display_name, COLOR_WHITE); - GFX_blitPill( - ASSET_BLACK_PILL, screen, - &(SDL_Rect){DP(ui.padding), DP(ui.padding), max_width, DP(ui.pill_height)}); - SDL_BlitSurface( - text, &(SDL_Rect){0, 0, max_width - DP(ui.button_padding * 2), text->h}, screen, - &(SDL_Rect){DP(ui.padding + ui.button_padding), DP(ui.padding + ui.text_baseline)}); + GFX_blitPill(ASSET_BLACK_PILL, screen, + &(SDL_Rect){DP(ui.edge_padding), DP(ui.edge_padding), max_width, + DP(ui.pill_height)}); + SDL_BlitSurface(text, &(SDL_Rect){0, 0, max_width - DP(ui.button_padding * 2), text->h}, + screen, + &(SDL_Rect){DP(ui.edge_padding + ui.button_padding), + DP(ui.edge_padding + ui.text_baseline)}); SDL_FreeSurface(text); if (show_setting && !GetHDMI()) @@ -6043,9 +6120,12 @@ static void Menu_loop(void) { 0); GFX_blitButtonGroup((char*[]){"B", "BACK", "A", "OKAY", NULL}, 1, screen, 1); - // Vertically center the menu items (oy is in DP units) - int menu_height_dp = ui.padding + (MENU_ITEM_COUNT * ui.pill_height); - oy = (ui.screen_height - menu_height_dp) / 2; + // Vertically center menu items between header and footer (all in DP) + int header_offset = ui.edge_padding + ui.pill_height; + int footer_offset = ui.screen_height - ui.edge_padding - ui.pill_height; + int content_area_height = footer_offset - header_offset; + int menu_height_dp = MENU_ITEM_COUNT * ui.pill_height; + oy = header_offset + (content_area_height - menu_height_dp) / 2 - ui.padding; for (int i = 0; i < MENU_ITEM_COUNT; i++) { char* item = menu.items[i]; @@ -6055,13 +6135,13 @@ static void Menu_loop(void) { // disc change if (menu.total_discs > 1 && i == ITEM_CONT) { GFX_blitPill(ASSET_DARK_GRAY_PILL, screen, - &(SDL_Rect){DP(ui.padding), DP(oy + ui.padding), - DP(ui.screen_width - ui.padding * 2), + &(SDL_Rect){DP(ui.edge_padding), DP(oy + ui.padding), + DP(ui.screen_width - ui.edge_padding * 2), DP(ui.pill_height)}); text = TTF_RenderUTF8_Blended(font.large, disc_name, COLOR_WHITE); SDL_BlitSurface( text, NULL, screen, - &(SDL_Rect){DP(ui.screen_width - ui.padding - ui.button_padding) - + &(SDL_Rect){DP(ui.screen_width - ui.edge_padding - ui.button_padding) - text->w, DP(oy + ui.padding + ui.text_baseline)}); SDL_FreeSurface(text); @@ -6072,7 +6152,7 @@ static void Menu_loop(void) { // pill GFX_blitPill(ASSET_WHITE_PILL, screen, - &(SDL_Rect){DP(ui.padding), + &(SDL_Rect){DP(ui.edge_padding), DP(oy + ui.padding + (i * ui.pill_height)), ow, DP(ui.pill_height)}); text_color = COLOR_BLACK; @@ -6080,7 +6160,7 @@ static void Menu_loop(void) { // shadow text = TTF_RenderUTF8_Blended(font.large, item, COLOR_BLACK); SDL_BlitSurface(text, NULL, screen, - &(SDL_Rect){DP(2 + ui.padding + ui.button_padding), + &(SDL_Rect){DP(2 + ui.edge_padding + ui.button_padding), DP(1 + ui.padding + oy + (i * ui.pill_height) + ui.text_baseline)}); SDL_FreeSurface(text); @@ -6090,7 +6170,7 @@ static void Menu_loop(void) { text = TTF_RenderUTF8_Blended(font.large, item, text_color); SDL_BlitSurface( text, NULL, screen, - &(SDL_Rect){DP(ui.padding + ui.button_padding), + &(SDL_Rect){DP(ui.edge_padding + ui.button_padding), DP(oy + ui.padding + (i * ui.pill_height) + ui.text_baseline)}); SDL_FreeSurface(text); } @@ -6104,7 +6184,7 @@ static void Menu_loop(void) { int hh = DEVICE_HEIGHT / 2; int pw = hw + DP(WINDOW_RADIUS * 2); int ph = hh + DP(WINDOW_RADIUS * 2 + PAGINATION_HEIGHT + WINDOW_RADIUS); - ox = DEVICE_WIDTH - pw - DP(ui.padding); + ox = DEVICE_WIDTH - pw - DP(ui.edge_padding); oy = (DEVICE_HEIGHT - ph) / 2; // window diff --git a/workspace/all/minui/minui.c b/workspace/all/minui/minui.c index bd0db415..f487442f 100644 --- a/workspace/all/minui/minui.c +++ b/workspace/all/minui/minui.c @@ -2210,7 +2210,7 @@ int main(int argc, char* argv[]) { SDL_Surface* thumb_orig = IMG_Load(res_path); // Scale to fit within available space (50% of width, full height minus padding) - int padding = DP(ui.padding); + int padding = DP(ui.edge_padding); int max_width = (ui.screen_width_px / 2) - padding; int max_height = ui.screen_height_px - (padding * 2); thumb = GFX_scaleToFit(thumb_orig, max_width, max_height); @@ -2229,7 +2229,7 @@ int main(int argc, char* argv[]) { } // Position on right side with padding, vertically centered - int padding = DP(ui.padding); + int padding = DP(ui.edge_padding); ox = ui.screen_width_px - thumb->w - padding; oy = (ui.screen_height_px - thumb->h) / 2; SDL_BlitSurface(thumb, NULL, screen, &(SDL_Rect){ox, oy, 0, 0}); @@ -2338,7 +2338,7 @@ int main(int argc, char* argv[]) { // ox is in pixels (thumbnail offset), screen width converted from DP to pixels int available_width = (had_thumb && j != selected_row ? ox : DP(ui.screen_width)) - - DP(ui.padding * 2); + DP(ui.edge_padding * 2); if (i == top->start && !(had_thumb && j != selected_row)) available_width -= ow; // @@ -2353,8 +2353,8 @@ int main(int argc, char* argv[]) { int max_width = MIN(available_width, text_width); if (j == selected_row) { GFX_blitPill(ASSET_WHITE_PILL, screen, - &(SDL_Rect){DP(ui.padding), - DP(ui.padding + (j * ui.pill_height)), + &(SDL_Rect){DP(ui.edge_padding), + DP(ui.edge_padding + (j * ui.pill_height)), max_width, DP(ui.pill_height)}); text_color = COLOR_BLACK; } else if (entry->unique) { @@ -2370,9 +2370,9 @@ int main(int argc, char* argv[]) { &(SDL_Rect){0, 0, max_width - DP(ui.button_padding * 2), text->h}, screen, &(SDL_Rect){ - DP(ui.padding + ui.button_padding), - DP(ui.padding + (j * ui.pill_height) + ui.text_baseline), 0, - 0}); + DP(ui.edge_padding + ui.button_padding), + DP(ui.edge_padding + (j * ui.pill_height) + ui.text_baseline), + 0, 0}); GFX_truncateText(font.large, entry_name, display_name, available_width, DP(ui.button_padding * 2)); @@ -2382,9 +2382,10 @@ int main(int argc, char* argv[]) { SDL_BlitSurface( text, &(SDL_Rect){0, 0, max_width - DP(ui.button_padding * 2), text->h}, screen, - &(SDL_Rect){DP(ui.padding + ui.button_padding), - DP(ui.padding + (j * ui.pill_height) + ui.text_baseline), 0, - 0}); + &(SDL_Rect){ + DP(ui.edge_padding + ui.button_padding), + DP(ui.edge_padding + (j * ui.pill_height) + ui.text_baseline), 0, + 0}); SDL_FreeSurface(text); } } else { diff --git a/workspace/all/paks/Clock/src/clock.c b/workspace/all/paks/Clock/src/clock.c index 7b9fba01..dc8def0d 100644 --- a/workspace/all/paks/Clock/src/clock.c +++ b/workspace/all/paks/Clock/src/clock.c @@ -304,7 +304,7 @@ int main(int argc, char* argv[]) { // Render date/time in format: YYYY/MM/DD HH:MM:SS [AM/PM] // Vertically center in available space (above footer pill) - int available_height_dp = ui.screen_height - ui.padding - ui.pill_height; + int available_height_dp = ui.screen_height - ui.edge_padding - ui.pill_height; int oy_dp = (available_height_dp - DIGIT_HEIGHT) / 2; int x = DP(ox_dp); diff --git a/workspace/all/paks/Input/src/minput.c b/workspace/all/paks/Input/src/minput.c index f93d00a2..9eb59029 100644 --- a/workspace/all/paks/Input/src/minput.c +++ b/workspace/all/paks/Input/src/minput.c @@ -128,7 +128,7 @@ int main(int argc, char* argv[]) { int has_both = (has_power && has_menu); // Adjust vertical offset if L3/R3 not present (reclaim space) - int oy = DP(ui.padding); + int oy = DP(ui.edge_padding); if (!has_L3 && !has_R3) oy += DP(ui.pill_height); @@ -211,7 +211,7 @@ int main(int argc, char* argv[]) { // D-pad (Up, Down, Left, Right) /////////////////////////////// { - int x = DP(ui.padding + ui.pill_height); + int x = DP(ui.edge_padding + ui.pill_height); int y = oy + DP(ui.pill_height * 2); int o = DP(ui.button_margin); @@ -248,7 +248,7 @@ int main(int argc, char* argv[]) { // Face buttons (A, B, X, Y) /////////////////////////////// { - int x = FIXED_WIDTH - DP(ui.padding + ui.pill_height * 3) + DP(ui.pill_height); + int x = FIXED_WIDTH - DP(ui.edge_padding + ui.pill_height * 3) + DP(ui.pill_height); int y = oy + DP(ui.pill_height * 2); int o = DP(ui.button_margin); @@ -340,7 +340,7 @@ int main(int argc, char* argv[]) { // Analog stick buttons (if available) /////////////////////////////// if (has_L3) { - int x = DP(ui.padding + ui.pill_height); + int x = DP(ui.edge_padding + ui.pill_height); int y = oy + DP(ui.pill_height * 6); int o = DP(ui.button_margin); @@ -349,7 +349,7 @@ int main(int argc, char* argv[]) { } if (has_R3) { - int x = FIXED_WIDTH - DP(ui.padding + ui.pill_height * 3) + DP(ui.pill_height); + int x = FIXED_WIDTH - DP(ui.edge_padding + ui.pill_height * 3) + DP(ui.pill_height); int y = oy + DP(ui.pill_height * 6); int o = DP(ui.button_margin); diff --git a/workspace/all/utils/minui-list/minui-list.c b/workspace/all/utils/minui-list/minui-list.c index 5aff26f7..2fa8a1fc 100644 --- a/workspace/all/utils/minui-list/minui-list.c +++ b/workspace/all/utils/minui-list/minui-list.c @@ -1429,12 +1429,12 @@ bool draw_background(SDL_Surface *screen, struct AppState *state) int imgW = surface->w, imgH = surface->h; // Compute scale factor - float scaleX = (float)(FIXED_WIDTH - 2 * DP(ui.padding)) / imgW; - float scaleY = (float)(FIXED_HEIGHT - 2 * DP(ui.padding)) / imgH; + float scaleX = (float)(FIXED_WIDTH - 2 * DP(ui.edge_padding)) / imgW; + float scaleY = (float)(FIXED_HEIGHT - 2 * DP(ui.edge_padding)) / imgH; float scale = (scaleX < scaleY) ? scaleX : scaleY; // Ensure upscaling only when the image is smaller than the screen - if (imgW * scale < FIXED_WIDTH - 2 * DP(ui.padding) && imgH * scale < FIXED_HEIGHT - 2 * DP(ui.padding)) + if (imgW * scale < FIXED_WIDTH - 2 * DP(ui.edge_padding) && imgH * scale < FIXED_HEIGHT - 2 * DP(ui.edge_padding)) { scale = (scaleX > scaleY) ? scaleX : scaleY; } @@ -1513,7 +1513,7 @@ void draw_screen(SDL_Surface *screen, struct AppState *state, int ow, bool shoul if (strlen(state->title) > 0) { // Truncate title to avoid battery/wifi icon interference - int title_available_width = ui.screen_width_px - DP(ui.padding * 3) - ow; // 3 paddings: left, right, and between title and icon pill + int title_available_width = ui.screen_width_px - DP(ui.edge_padding * 2 + ui.padding) - ow; // edge padding on left/right, internal padding between title and icon pill char truncated_title_text[256]; int title_width = GFX_truncateText(state->fonts.medium, state->title, truncated_title_text, title_available_width, DP(ui.button_padding * 2)); @@ -1523,7 +1523,7 @@ void draw_screen(SDL_Surface *screen, struct AppState *state, int ow, bool shoul if (strcmp(title_alignment, "center") == 0) { title_x_pos = (ui.screen_width_px - title_width) / 2 + DP(ui.button_padding); - int title_interference = title_width - (title_available_width - ow - DP(ui.padding)); // extra ow and padding account for centered text, i.e. available width is offset by ow and padding on both sides of screen + int title_interference = title_width - (title_available_width - ow - DP(ui.edge_padding)); // extra ow and padding account for centered text, i.e. available width is offset by ow and padding on both sides of screen if (title_interference > 0) { title_x_pos -= title_interference / 2; @@ -1531,13 +1531,14 @@ void draw_screen(SDL_Surface *screen, struct AppState *state, int ow, bool shoul } else if (strcmp(title_alignment, "right") == 0) { - title_x_pos = ui.screen_width_px - title_width - ow - DP(ui.padding * 2) + DP(ui.button_padding); + title_x_pos = ui.screen_width_px - title_width - ow - DP(ui.edge_padding * 2) + DP(ui.button_padding); } else // left (default) { - title_x_pos = DP(ui.padding + ui.button_padding); + title_x_pos = DP(ui.edge_padding + ui.button_padding); } + // Leave room for header (battery, wifi icons) even without title initial_list_y_padding = ui.pill_height; if (should_draw_background_image) { @@ -1550,15 +1551,16 @@ void draw_screen(SDL_Surface *screen, struct AppState *state, int ow, bool shoul } else if (strcmp(title_alignment, "right") == 0) { - pill_x_pos = ui.screen_width_px - pill_width - DP(ui.padding); + pill_x_pos = ui.screen_width_px - pill_width - DP(ui.edge_padding); } else // left (default) { - pill_x_pos = DP(ui.padding); + pill_x_pos = DP(ui.edge_padding); } - GFX_blitPill(ASSET_BLACK_PILL, screen, &(SDL_Rect){pill_x_pos, DP(ui.padding), pill_width, DP(ui.pill_height)}); + GFX_blitPill(ASSET_BLACK_PILL, screen, &(SDL_Rect){pill_x_pos, DP(ui.edge_padding), pill_width, DP(ui.pill_height)}); + // Add extra spacing below title initial_list_y_padding = ui.pill_height + (ui.pill_height / 2); } @@ -1571,7 +1573,7 @@ void draw_screen(SDL_Surface *screen, struct AppState *state, int ow, bool shoul SDL_Surface *text = TTF_RenderUTF8_Blended(state->fonts.medium, truncated_title_text, text_color); SDL_Rect pos = { title_x_pos, - DP(ui.padding + 4), + DP(ui.edge_padding + 4), text->w, text->h}; SDL_BlitSurface(text, NULL, screen, &pos); @@ -1585,7 +1587,7 @@ void draw_screen(SDL_Surface *screen, struct AppState *state, int ow, bool shoul int selected_row = state->list_state->selected - state->list_state->first_visible; for (int i = state->list_state->first_visible, j = 0; i < state->list_state->last_visible; i++, j++) { - int available_width = (ui.screen_width_px) - DP(ui.padding * 2); + int available_width = (ui.screen_width_px) - DP(ui.edge_padding * 2); bool in_top_row_no_title = (j == 0 && strlen(state->title) == 0); // Account for the space taken up by ow and it's padding if (in_top_row_no_title) @@ -1656,8 +1658,11 @@ void draw_screen(SDL_Surface *screen, struct AppState *state, int ow, bool shoul } char truncated_display_text[256]; - int text_width = GFX_truncateText(state->fonts.large, display_text, truncated_display_text, available_width, DP(ui.button_padding * 2)); - int pill_width = MIN(available_width, text_width) + color_box_space; + int label_text_w = 0; + TTF_SizeUTF8(font.medium, display_text, &label_text_w, NULL); + GFX_truncateText(font.medium, display_text, truncated_display_text, available_width - DP(OPTION_PADDING * 2), 0); + TTF_SizeUTF8(font.medium, truncated_display_text, &label_text_w, NULL); + int pill_width = label_text_w + DP(OPTION_PADDING * 2) + color_box_space; if (j == selected_row) { @@ -1685,11 +1690,11 @@ void draw_screen(SDL_Surface *screen, struct AppState *state, int ow, bool shoul } else if (strcmp(alignment, "right") == 0) { - pill_x_pos = ui.screen_width_px - pill_width - DP(ui.padding); + pill_x_pos = ui.screen_width_px - pill_width - DP(ui.edge_padding); } else // left (default) { - pill_x_pos = DP(ui.padding); + pill_x_pos = DP(ui.edge_padding); } // Adjust for the pill position in the top row without title @@ -1697,7 +1702,7 @@ void draw_screen(SDL_Surface *screen, struct AppState *state, int ow, bool shoul { if (strcmp(alignment, "center") == 0) { - int interference = pill_width - (available_width - ow - DP(ui.padding)); // extra ow and padding account for centered text, i.e. available width is offset by ow and padding on both sides of screen + int interference = pill_width - (available_width - ow - DP(ui.edge_padding)); // extra ow and padding account for centered text, i.e. available width is offset by ow and padding on both sides of screen if (interference > 0) { pill_x_pos -= interference / 2; @@ -1711,14 +1716,15 @@ void draw_screen(SDL_Surface *screen, struct AppState *state, int ow, bool shoul if (strcmp(display_selected_text, "") != 0) { - GFX_blitPill(ASSET_DARK_GRAY_PILL, screen, &(SDL_Rect){pill_x_pos, DP(ui.padding + (j * ui.pill_height) + initial_list_y_padding), ui.screen_width_px - DP(ui.padding + ui.button_margin), DP(ui.pill_height)}); + int full_width = ui.screen_width_px - DP(ui.edge_padding * 2); + GFX_blitPill(ASSET_OPTION, screen, &(SDL_Rect){pill_x_pos, DP(ui.edge_padding + (j * ui.option_size) + initial_list_y_padding), full_width, DP(ui.option_size)}); } - GFX_blitPill(ASSET_WHITE_PILL, screen, &(SDL_Rect){pill_x_pos, DP(ui.padding + (j * ui.pill_height) + initial_list_y_padding), pill_width, DP(ui.pill_height)}); + GFX_blitPill(ASSET_OPTION_WHITE, screen, &(SDL_Rect){pill_x_pos, DP(ui.edge_padding + (j * ui.option_size) + initial_list_y_padding), pill_width, DP(ui.option_size)}); } SDL_Surface *text; - text = TTF_RenderUTF8_Blended(state->fonts.large, truncated_display_text, text_color); + text = TTF_RenderUTF8_Blended(font.medium, truncated_display_text, text_color); // Calculate text position based on alignment int text_x_pos; @@ -1730,13 +1736,13 @@ void draw_screen(SDL_Surface *screen, struct AppState *state, int ow, bool shoul } else if (strcmp(alignment, "right") == 0) { - text_x_pos = ui.screen_width_px - text->w - DP(ui.padding + ui.button_padding) - color_box_space; - shadow_x_pos = ui.screen_width_px - text->w - DP(2 + ui.padding + ui.button_padding) - color_box_space; + text_x_pos = ui.screen_width_px - text->w - DP(ui.edge_padding + OPTION_PADDING) - color_box_space; + shadow_x_pos = ui.screen_width_px - text->w - DP(2 + ui.edge_padding + OPTION_PADDING) - color_box_space; } else // left (default) { - text_x_pos = DP(ui.padding + ui.button_padding); - shadow_x_pos = DP(2 + ui.padding + ui.button_padding); + text_x_pos = DP(ui.edge_padding + OPTION_PADDING); + shadow_x_pos = DP(2 + ui.edge_padding + OPTION_PADDING); } // Adjust for the pill position in the top row without title @@ -1744,7 +1750,7 @@ void draw_screen(SDL_Surface *screen, struct AppState *state, int ow, bool shoul { if (strcmp(alignment, "center") == 0) { - int interference = pill_width - (available_width - ow - DP(ui.padding)); // extra ow and padding account for centered text, i.e. available width is offset by ow and padding on both sides of screen + int interference = pill_width - (available_width - ow - DP(ui.edge_padding)); // extra ow and padding account for centered text, i.e. available width is offset by ow and padding on both sides of screen if (interference > 0) { text_x_pos -= interference / 2; @@ -1758,7 +1764,7 @@ void draw_screen(SDL_Surface *screen, struct AppState *state, int ow, bool shoul SDL_Rect pos = { text_x_pos, - DP(ui.padding + ((i - state->list_state->first_visible) * ui.pill_height) + initial_list_y_padding + 4), + DP(ui.edge_padding + ((i - state->list_state->first_visible) * ui.option_size) + initial_list_y_padding + ui.option_baseline), text->w, text->h}; @@ -1767,10 +1773,10 @@ void draw_screen(SDL_Surface *screen, struct AppState *state, int ow, bool shoul { // COLOR_BLACK SDL_Surface *accent_text; - accent_text = TTF_RenderUTF8_Blended(state->fonts.large, truncated_display_text, COLOR_BLACK); + accent_text = TTF_RenderUTF8_Blended(font.medium, truncated_display_text, COLOR_BLACK); SDL_Rect accent_pos = { shadow_x_pos, - DP(ui.padding + ((i - state->list_state->first_visible) * ui.pill_height) + initial_list_y_padding + 4 + 2), + DP(ui.edge_padding + ((i - state->list_state->first_visible) * ui.option_size) + initial_list_y_padding + ui.option_baseline + 1), accent_text->w, accent_text->h}; SDL_BlitSurface(accent_text, NULL, screen, &accent_pos); @@ -1785,7 +1791,7 @@ void draw_screen(SDL_Surface *screen, struct AppState *state, int ow, bool shoul // draw the selected option text if (strcmp(display_selected_text, "") != 0) { - initial_cube_x_pos = ui.screen_width_px - DP(ui.padding + ui.button_padding) - color_box_space; + initial_cube_x_pos = ui.screen_width_px - DP(ui.edge_padding + OPTION_PADDING) - color_box_space; if (j != 0 || strlen(state->title) > 0) { SDL_Color selected_text_color = COLOR_WHITE; @@ -1794,9 +1800,10 @@ void draw_screen(SDL_Surface *screen, struct AppState *state, int ow, bool shoul selected_text_color = COLOR_LIGHT_TEXT; } SDL_Surface *selected_text; - selected_text = TTF_RenderUTF8_Blended(state->fonts.large, display_selected_text, selected_text_color); - pos = (SDL_Rect){ui.screen_width_px - selected_text->w - DP(ui.padding + ui.button_padding) - color_box_space, pos.y, selected_text->w, selected_text->h}; - SDL_BlitSurface(selected_text, NULL, screen, &pos); + selected_text = TTF_RenderUTF8_Blended(font.small, display_selected_text, selected_text_color); + int value_y = DP(ui.edge_padding + ((i - state->list_state->first_visible) * ui.option_size) + initial_list_y_padding + ui.option_value_baseline); + SDL_Rect value_pos = (SDL_Rect){ui.screen_width_px - selected_text->w - DP(ui.edge_padding + OPTION_PADDING) - color_box_space, value_y, selected_text->w, selected_text->h}; + SDL_BlitSurface(selected_text, NULL, screen, &value_pos); SDL_FreeSurface(selected_text); } } @@ -1812,14 +1819,14 @@ void draw_screen(SDL_Surface *screen, struct AppState *state, int ow, bool shoul uint32_t outline_color = sdl_color_to_uint32(text_color); SDL_Rect outline_rect = { initial_cube_x_pos + DP(ui.padding), - DP(ui.padding + ((i - state->list_state->first_visible) * ui.pill_height) + initial_list_y_padding + 5), color_placeholder_height, + DP(ui.edge_padding + ((i - state->list_state->first_visible) * ui.option_size) + initial_list_y_padding + 5), color_placeholder_height, color_placeholder_height}; SDL_FillRect(screen, &(SDL_Rect){outline_rect.x, outline_rect.y, outline_rect.w, outline_rect.h}, outline_color); // Draw color cube SDL_Rect color_rect = { initial_cube_x_pos + DP(ui.padding) + 2, - DP(ui.padding + ((i - state->list_state->first_visible) * ui.pill_height) + initial_list_y_padding + 5) + 2, color_placeholder_height - 4, + DP(ui.edge_padding + ((i - state->list_state->first_visible) * ui.option_size) + initial_list_y_padding + 5) + 2, color_placeholder_height - 4, color_placeholder_height - 4}; SDL_FillRect(screen, &(SDL_Rect){color_rect.x, color_rect.y, color_rect.w, color_rect.h}, color); } diff --git a/workspace/all/utils/minui-presenter/minui-presenter.c b/workspace/all/utils/minui-presenter/minui-presenter.c index 8e384cb8..dca5e712 100644 --- a/workspace/all/utils/minui-presenter/minui-presenter.c +++ b/workspace/all/utils/minui-presenter/minui-presenter.c @@ -712,7 +712,7 @@ void draw_screen(SDL_Surface *screen, struct AppState *state) int imgW = surface->w, imgH = surface->h; // Compute scale factor using DP-based screen dimensions - int padding_px = DP(ui.padding * 2); + int padding_px = DP(ui.edge_padding * 2); int available_width_px = ui.screen_width_px - padding_px; int available_height_px = ui.screen_height_px - padding_px; @@ -802,16 +802,16 @@ void draw_screen(SDL_Surface *screen, struct AppState *state) SDL_Surface *text = TTF_RenderUTF8_Blended(state->fonts.small, time_left_str, COLOR_WHITE); SDL_Rect pos = { - DP(ui.padding), - DP(ui.padding), + DP(ui.edge_padding), + DP(ui.edge_padding), text->w, text->h}; SDL_BlitSurface(text, NULL, screen, &pos); - initial_padding = text->h + DP(ui.padding); + initial_padding = text->h + DP(ui.edge_padding); } - int message_padding_dp = ui.padding + ui.button_padding; + int message_padding_dp = ui.edge_padding + ui.button_padding; // get the width and height of every word in the message struct Message words[1024]; @@ -954,11 +954,11 @@ void draw_screen(SDL_Surface *screen, struct AppState *state) int current_message_y = (ui.screen_height_px - messages_height) / 2; if (state->items_state->items[state->items_state->selected].alignment == MessageAlignmentTop) { - current_message_y = DP(ui.padding) + initial_padding; + current_message_y = DP(ui.edge_padding) + initial_padding; } else if (state->items_state->items[state->items_state->selected].alignment == MessageAlignmentBottom) { - current_message_y = ui.screen_height_px - messages_height - DP(ui.padding) - initial_padding; + current_message_y = ui.screen_height_px - messages_height - DP(ui.edge_padding) - initial_padding; } for (int i = 0; i <= message_count; i++) diff --git a/workspace/magicmini/platform/platform.h b/workspace/magicmini/platform/platform.h index dd93e55c..904d74c4 100644 --- a/workspace/magicmini/platform/platform.h +++ b/workspace/magicmini/platform/platform.h @@ -135,6 +135,8 @@ /////////////////////////////// #define SCREEN_DIAGONAL 2.8f // Physical screen diagonal in inches +#define SCALE_MODIFIER 0.92f // Reduce UI size to fit more content on small screen +#define EDGE_PADDING 0 // No edge padding needed - bezel provides visual margin #define FIXED_WIDTH 640 // Screen width in pixels #define FIXED_HEIGHT 480 // Screen height in pixels (VGA) diff --git a/workspace/miyoomini/platform/platform.h b/workspace/miyoomini/platform/platform.h index 62da1c7f..79596403 100644 --- a/workspace/miyoomini/platform/platform.h +++ b/workspace/miyoomini/platform/platform.h @@ -146,6 +146,8 @@ extern int is_560p; // Set to 1 for 560p screen variant /////////////////////////////// #define SCREEN_DIAGONAL 2.8f // Physical screen diagonal in inches +#define SCALE_MODIFIER (is_plus ? 1.0f : 0.92f) // Standard: reduce UI size; Plus: default +#define EDGE_PADDING (is_plus ? 10 : 5) // Standard: reduced padding; Plus: default #define FIXED_WIDTH (is_560p ? 752 : 640) // Screen width: 752px (560p) or 640px (standard) #define FIXED_HEIGHT (is_560p ? 560 : 480) // Screen height: 560px (560p) or 480px (standard) diff --git a/workspace/my282/platform/platform.h b/workspace/my282/platform/platform.h index d54655e1..6f2bb215 100644 --- a/workspace/my282/platform/platform.h +++ b/workspace/my282/platform/platform.h @@ -134,6 +134,8 @@ /////////////////////////////// #define SCREEN_DIAGONAL 2.8f // Physical screen diagonal in inches (estimated) +#define SCALE_MODIFIER 0.92f // Reduce UI size to fit more content on small screen +#define EDGE_PADDING 5 // Reduced edge padding - bezel provides some visual margin #define FIXED_WIDTH 640 // Screen width in pixels #define FIXED_HEIGHT 480 // Screen height in pixels (VGA) diff --git a/workspace/tg5040/platform/platform.h b/workspace/tg5040/platform/platform.h index 947d7067..248dbfba 100644 --- a/workspace/tg5040/platform/platform.h +++ b/workspace/tg5040/platform/platform.h @@ -162,6 +162,7 @@ extern int is_brick; // Set to 1 for Brick variant (1024x768 display) /////////////////////////////// #define SCREEN_DIAGONAL (is_brick ? 3.2f : 4.95f) // Diagonal: 3.2" (Brick) or 4.95" (Smart Pro) +#define EDGE_PADDING 5 // Reduced edge padding - bezel provides visual margin #define FIXED_WIDTH (is_brick ? 1024 : 1280) // Width: 1024px (Brick) or 1280px (standard) #define FIXED_HEIGHT (is_brick ? 768 : 720) // Height: 768px (Brick) or 720px (standard) diff --git a/workspace/trimuismart/platform/platform.h b/workspace/trimuismart/platform/platform.h index ac72fdc7..6007d9f4 100644 --- a/workspace/trimuismart/platform/platform.h +++ b/workspace/trimuismart/platform/platform.h @@ -135,6 +135,8 @@ /////////////////////////////// #define SCREEN_DIAGONAL 2.4f // Physical screen diagonal in inches +#define SCALE_MODIFIER 0.88f // Reduce UI size to fit more content on small screen +#define EDGE_PADDING 0 // No edge padding needed - bezel provides visual margin #define FIXED_WIDTH 320 // Screen width in pixels #define FIXED_HEIGHT 240 // Screen height in pixels (QVGA) diff --git a/workspace/zero28/platform/platform.h b/workspace/zero28/platform/platform.h index 4cb5142f..dc7ada14 100644 --- a/workspace/zero28/platform/platform.h +++ b/workspace/zero28/platform/platform.h @@ -149,6 +149,8 @@ /////////////////////////////// #define SCREEN_DIAGONAL 2.8f // Physical screen diagonal in inches +#define SCALE_MODIFIER 0.92f // Reduce UI size to fit more content on small screen +#define EDGE_PADDING 0 // No edge padding needed - bezel provides visual margin #define FIXED_WIDTH 640 // Screen width in pixels #define FIXED_HEIGHT 480 // Screen height in pixels (VGA)