From 525c037e937fe77a9c26d992599801c1a36ff0cd Mon Sep 17 00:00:00 2001 From: Nick Chapman Date: Tue, 25 Nov 2025 20:52:00 -0800 Subject: [PATCH 01/17] Implement resolution-independent UI system. --- makefile.qa | 7 +- tests/unit/all/common/test_ui_layout.c | 379 ++++++++++++++++++++++ workspace/all/clock/clock.c | 34 +- workspace/all/common/api.c | 364 ++++++++++++++++----- workspace/all/common/api.h | 75 +++++ workspace/all/minarch/minarch.c | 146 ++++----- workspace/all/minput/minput.c | 138 ++++---- workspace/all/minui/minui.c | 76 ++--- workspace/all/say/say.c | 2 +- workspace/desktop/platform/platform.h | 5 +- workspace/m17/platform/platform.h | 3 +- workspace/magicmini/platform/platform.h | 1 + workspace/miyoomini/platform/platform.h | 4 +- workspace/my282/platform/platform.h | 1 + workspace/my355/platform/platform.h | 4 +- workspace/rg35xx/platform/platform.h | 1 + workspace/rg35xxplus/platform/platform.h | 4 +- workspace/rgb30/platform/platform.h | 4 +- workspace/tg5040/platform/platform.h | 4 +- workspace/trimuismart/platform/platform.h | 1 + workspace/zero28/platform/platform.h | 4 +- 21 files changed, 966 insertions(+), 291 deletions(-) create mode 100644 tests/unit/all/common/test_ui_layout.c diff --git a/makefile.qa b/makefile.qa index e2170bfd..e3c62e49 100644 --- a/makefile.qa +++ b/makefile.qa @@ -189,7 +189,7 @@ TEST_INCLUDES = -I tests/support -I tests/support/unity -I workspace/all/common TEST_UNITY = tests/support/unity/unity.c # All test executables (built from tests/unit/ and tests/integration/) -TEST_EXECUTABLES = tests/utils_test tests/pad_test tests/collections_test tests/gfx_text_test tests/audio_resampler_test tests/minarch_paths_test tests/minui_utils_test tests/m3u_parser_test tests/minui_file_utils_test tests/map_parser_test tests/collection_parser_test tests/recent_parser_test tests/recent_writer_test tests/directory_utils_test tests/binary_file_utils_test tests/integration_workflows_test +TEST_EXECUTABLES = tests/utils_test tests/pad_test tests/collections_test tests/gfx_text_test tests/audio_resampler_test tests/minarch_paths_test tests/minui_utils_test tests/m3u_parser_test tests/minui_file_utils_test tests/map_parser_test tests/collection_parser_test tests/recent_parser_test tests/recent_writer_test tests/directory_utils_test tests/binary_file_utils_test tests/ui_layout_test tests/integration_workflows_test # Default targets: use Docker for consistency test: docker-test @@ -284,6 +284,11 @@ tests/binary_file_utils_test: tests/unit/all/common/test_binary_file_utils.c wor @echo "Building binary file I/O tests..." @$(CC) -o $@ $^ $(TEST_INCLUDES) $(TEST_CFLAGS) -D_DEFAULT_SOURCE +# Build UI layout / DP system tests (pure math, no mocking needed) +tests/ui_layout_test: tests/unit/all/common/test_ui_layout.c workspace/all/common/log.c $(TEST_UNITY) + @echo "Building UI layout / DP system tests..." + @$(CC) -o $@ $^ $(TEST_INCLUDES) $(TEST_CFLAGS) -lm + # Build integration tests (tests multiple components working together with real file I/O) tests/integration_workflows_test: tests/integration/test_workflows.c \ tests/integration/integration_support.c \ diff --git a/tests/unit/all/common/test_ui_layout.c b/tests/unit/all/common/test_ui_layout.c new file mode 100644 index 00000000..b1b893a0 --- /dev/null +++ b/tests/unit/all/common/test_ui_layout.c @@ -0,0 +1,379 @@ +/** + * test_ui_layout.c - Unit tests for Display Points (DP) UI layout calculations + * + * Tests the UI_initLayout() function which calculates: + * - dp_scale from screen PPI + * - Optimal pill height to fill screen + * - Row count (6-8) + * - Derived button sizes + */ + +#include "unity.h" + +#include + +// Define minimal platform stubs (we don't need full platform.h) +#define PLATFORM "test" + +// UI Layout types - copy from api.h to avoid SDL dependencies +typedef struct UI_Layout { + int pill_height; + int row_count; + int padding; + int text_baseline; + int button_size; + int button_margin; + int button_padding; +} UI_Layout; + +// External globals and function (implemented in api.c, but we'll redefine for testing) +float gfx_dp_scale = 2.0f; +UI_Layout ui = { + .pill_height = 30, + .row_count = 6, + .padding = 10, + .text_baseline = 4, + .button_size = 20, + .button_margin = 5, + .button_padding = 12, +}; + +// DP macro for testing +#define DP(x) ((int)((x) *gfx_dp_scale + 0.5f)) + +// Copy UI_initLayout implementation for testing +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; + gfx_dp_scale = ppi / 160.0f; + + // Bounds for layout calculation + const int MIN_PILL = 28; + const int MAX_PILL = 32; + const int MIN_ROWS = 6; + const int MAX_ROWS = 8; + const int DEFAULT_PADDING = 10; + + // Calculate available height in dp + int screen_height_dp = (int)(screen_height / gfx_dp_scale + 0.5f); + int available_dp = screen_height_dp - (DEFAULT_PADDING * 2); + + // Find best row count where pill height fits in acceptable range + int best_rows = MIN_ROWS; + int best_pill = 30; + + // Try to fit as many rows as possible + for (int rows = MAX_ROWS; rows >= MIN_ROWS; rows--) { + int total_rows = rows + 2; // +1 header spacing, +1 footer + int pill = available_dp / total_rows; + + if (pill >= MIN_PILL && pill <= MAX_PILL) { + // Found a good fit - use it + best_rows = rows; + best_pill = pill; + break; + } else if (pill < MIN_PILL && rows > MIN_ROWS) { + // Too many rows, not enough space - try fewer rows + continue; + } else if (rows == MIN_ROWS) { + // Reached minimum rows - use closest valid pill size + if (pill < MIN_PILL) { + best_pill = MIN_PILL; + } else if (pill > MAX_PILL) { + best_pill = MAX_PILL; + } else { + best_pill = pill; + } + best_rows = rows; + } + } + + // Store calculated values + ui.pill_height = best_pill; + ui.row_count = best_rows; + ui.padding = DEFAULT_PADDING; + + // Derived proportional sizes + ui.button_size = (best_pill * 2) / 3; + ui.button_margin = (best_pill - ui.button_size) / 2; + ui.button_padding = (best_pill * 2) / 5; + ui.text_baseline = (4 * best_pill + 15) / 30; +} + +void setUp(void) { + // Reset to defaults before each test + gfx_dp_scale = 2.0f; + ui.pill_height = 30; + ui.row_count = 6; + ui.padding = 10; + ui.button_size = 20; + ui.button_margin = 5; + ui.button_padding = 12; + ui.text_baseline = 4; +} + +void tearDown(void) { +} + +/////////////////////////////// +// DP Scale Calculation Tests +/////////////////////////////// + +void test_dp_scale_miyoomini_480p(void) { + // Miyoo Mini: 640x480 @ 2.8" → PPI = 286 → dp_scale ≈ 1.79 + UI_initLayout(640, 480, 2.8f); + + float expected_ppi = sqrtf(640 * 640 + 480 * 480) / 2.8f; // ≈ 286 + float expected_dp_scale = expected_ppi / 160.0f; // ≈ 1.79 + + TEST_ASSERT_FLOAT_WITHIN(0.01f, expected_dp_scale, gfx_dp_scale); +} + +void test_dp_scale_miyoomini_560p(void) { + // Miyoo Mini 560p: 752x560 @ 2.8" → PPI = 334 → dp_scale ≈ 2.09 + UI_initLayout(752, 560, 2.8f); + + float expected_ppi = sqrtf(752 * 752 + 560 * 560) / 2.8f; + float expected_dp_scale = expected_ppi / 160.0f; + + TEST_ASSERT_FLOAT_WITHIN(0.01f, expected_dp_scale, gfx_dp_scale); +} + +void test_dp_scale_trimuismart(void) { + // Trimui Smart: 320x240 @ 2.4" → PPI = 167 → dp_scale ≈ 1.04 + UI_initLayout(320, 240, 2.4f); + + float expected_ppi = sqrtf(320 * 320 + 240 * 240) / 2.4f; + float expected_dp_scale = expected_ppi / 160.0f; + + TEST_ASSERT_FLOAT_WITHIN(0.01f, expected_dp_scale, gfx_dp_scale); +} + +void test_dp_scale_tg5040_brick(void) { + // TG5040 Brick: 1024x768 @ 3.2" → PPI = 400 → dp_scale ≈ 2.50 + UI_initLayout(1024, 768, 3.2f); + + float expected_ppi = sqrtf(1024 * 1024 + 768 * 768) / 3.2f; + float expected_dp_scale = expected_ppi / 160.0f; + + TEST_ASSERT_FLOAT_WITHIN(0.01f, expected_dp_scale, gfx_dp_scale); +} + +void test_dp_scale_rg35xx(void) { + // RG35XX: 640x480 @ 3.5" → PPI = 229 → dp_scale ≈ 1.43 + UI_initLayout(640, 480, 3.5f); + + float expected_ppi = sqrtf(640 * 640 + 480 * 480) / 3.5f; + float expected_dp_scale = expected_ppi / 160.0f; + + TEST_ASSERT_FLOAT_WITHIN(0.01f, expected_dp_scale, gfx_dp_scale); +} + +/////////////////////////////// +// Row Count Calculation Tests +/////////////////////////////// + +void test_row_count_small_screen(void) { + // Small screen (240px logical height) should get 6 rows + UI_initLayout(320, 240, 2.4f); + + TEST_ASSERT_EQUAL_INT(6, ui.row_count); +} + +void test_row_count_medium_screen(void) { + // Medium screen (240px logical at 2x = 480px physical) should get 6 rows + UI_initLayout(640, 480, 2.8f); + + // At dp_scale ≈ 1.79, 480px / 1.79 ≈ 268 logical pixels + // Should fit 6-8 rows depending on pill calculation + TEST_ASSERT_TRUE(ui.row_count >= 6 && ui.row_count <= 8); +} + +void test_row_count_tall_screen(void) { + // Tall screen (560p @ 2.8") - logical ≈ 268dp, fits 6 rows optimally + UI_initLayout(752, 560, 2.8f); + + // 268dp - 20 (padding) = 248dp available + // 8 rows would need 24.8dp pills (too small) + // 6 rows needs 31dp pills (good fit) + TEST_ASSERT_EQUAL_INT(6, ui.row_count); +} + +void test_row_count_large_screen(void) { + // Large screen (720px @ 4.95") - logical ≈ 387dp + UI_initLayout(1280, 720, 4.95f); + + // 387dp - 20 (padding) = 367dp available + // 8 rows would need 36.7dp pills (too big, caps at 32) + // 6 rows needs 45.9dp pills (way too big, caps at 32) + // Algorithm will use 6 rows with 32dp max pill + TEST_ASSERT_EQUAL_INT(6, ui.row_count); + TEST_ASSERT_EQUAL_INT(32, ui.pill_height); +} + +/////////////////////////////// +// Pill Height Calculation Tests +/////////////////////////////// + +void test_pill_height_within_bounds(void) { + // All platforms should have pill height 28-32dp + UI_initLayout(640, 480, 2.8f); + + TEST_ASSERT_TRUE(ui.pill_height >= 28); + TEST_ASSERT_TRUE(ui.pill_height <= 32); +} + +void test_pill_height_fills_screen(void) { + // Pill height should be calculated to fill available space + UI_initLayout(640, 480, 2.8f); + + // Calculate logical height + int logical_height = (int)(480 / gfx_dp_scale + 0.5f); + int available = logical_height - (ui.padding * 2); + int total_rows = ui.row_count + 2; // content + header + footer + + // Pill height should approximately fill the space + int expected_pill = available / total_rows; + TEST_ASSERT_INT_WITHIN(2, expected_pill, ui.pill_height); +} + +/////////////////////////////// +// Derived Sizes Tests +/////////////////////////////// + +void test_button_size_proportional(void) { + UI_initLayout(640, 480, 2.8f); + + // button_size should be ~2/3 of pill_height + int expected_button = (ui.pill_height * 2) / 3; + TEST_ASSERT_EQUAL_INT(expected_button, ui.button_size); +} + +void test_button_margin_centers_button(void) { + UI_initLayout(640, 480, 2.8f); + + // button_margin should center button in pill + int expected_margin = (ui.pill_height - ui.button_size) / 2; + TEST_ASSERT_EQUAL_INT(expected_margin, ui.button_margin); +} + +void test_button_padding_proportional(void) { + UI_initLayout(640, 480, 2.8f); + + // button_padding should be ~2/5 of pill_height (12 for 30dp) + int expected_padding = (ui.pill_height * 2) / 5; + TEST_ASSERT_EQUAL_INT(expected_padding, ui.button_padding); +} + +void test_text_baseline_proportional(void) { + UI_initLayout(640, 480, 2.8f); + + // text_baseline should scale proportionally (~4 for 30dp) + int expected_baseline = (4 * ui.pill_height + 15) / 30; + TEST_ASSERT_EQUAL_INT(expected_baseline, ui.text_baseline); +} + +/////////////////////////////// +// Padding Tests +/////////////////////////////// + +void test_padding_consistent(void) { + // Padding should be consistent across all screen sizes + UI_initLayout(320, 240, 2.4f); + TEST_ASSERT_EQUAL_INT(10, ui.padding); + + UI_initLayout(640, 480, 2.8f); + TEST_ASSERT_EQUAL_INT(10, ui.padding); + + UI_initLayout(1280, 720, 4.95f); + TEST_ASSERT_EQUAL_INT(10, ui.padding); +} + +/////////////////////////////// +// DP Macro Tests +/////////////////////////////// + +void test_DP_macro_rounds_correctly(void) { + gfx_dp_scale = 1.79f; + + // DP(30) = (int)(30 * 1.79 + 0.5) = (int)(53.7 + 0.5) = 54 + TEST_ASSERT_EQUAL_INT(54, DP(30)); + + // DP(10) = (int)(10 * 1.79 + 0.5) = (int)(17.9 + 0.5) = 18 + TEST_ASSERT_EQUAL_INT(18, DP(10)); +} + +void test_DP_macro_handles_fractions(void) { + gfx_dp_scale = 2.5f; + + // DP(30) = (int)(30 * 2.5 + 0.5) = 75 + TEST_ASSERT_EQUAL_INT(75, DP(30)); + + // DP(31) = (int)(31 * 2.5 + 0.5) = 78 + TEST_ASSERT_EQUAL_INT(78, DP(31)); +} + +/////////////////////////////// +// Edge Case Tests +/////////////////////////////// + +void test_extremely_small_screen(void) { + // Tiny screen should still work, even if it can't fit 6 rows perfectly + UI_initLayout(240, 160, 2.0f); + + TEST_ASSERT_TRUE(ui.row_count >= 6); + TEST_ASSERT_TRUE(ui.pill_height >= 28); +} + +void test_extremely_large_screen(void) { + // Large screen should cap at 8 rows with max pill size + UI_initLayout(1920, 1080, 7.0f); + + TEST_ASSERT_TRUE(ui.row_count <= 8); + TEST_ASSERT_TRUE(ui.pill_height <= 32); +} + +/////////////////////////////// +// Test Runner +/////////////////////////////// + +int main(void) { + UNITY_BEGIN(); + + // DP scale calculations + RUN_TEST(test_dp_scale_miyoomini_480p); + RUN_TEST(test_dp_scale_miyoomini_560p); + RUN_TEST(test_dp_scale_trimuismart); + RUN_TEST(test_dp_scale_tg5040_brick); + RUN_TEST(test_dp_scale_rg35xx); + + // Row count calculations + RUN_TEST(test_row_count_small_screen); + RUN_TEST(test_row_count_medium_screen); + RUN_TEST(test_row_count_tall_screen); + RUN_TEST(test_row_count_large_screen); + + // Pill height calculations + RUN_TEST(test_pill_height_within_bounds); + RUN_TEST(test_pill_height_fills_screen); + + // Derived sizes + RUN_TEST(test_button_size_proportional); + RUN_TEST(test_button_margin_centers_button); + RUN_TEST(test_button_padding_proportional); + RUN_TEST(test_text_baseline_proportional); + + // Padding + RUN_TEST(test_padding_consistent); + + // DP macro + RUN_TEST(test_DP_macro_rounds_correctly); + RUN_TEST(test_DP_macro_handles_fractions); + + // Edge cases + RUN_TEST(test_extremely_small_screen); + RUN_TEST(test_extremely_large_screen); + + return UNITY_END(); +} diff --git a/workspace/all/clock/clock.c b/workspace/all/clock/clock.c index 68ff9f2f..d495742a 100644 --- a/workspace/all/clock/clock.c +++ b/workspace/all/clock/clock.c @@ -59,7 +59,7 @@ int main(int argc, char* argv[]) { // Pre-render all digit and separator characters into a sprite sheet // for fast blitting during the main loop SDL_Surface* digits = - SDL_CreateRGBSurface(SDL_SWSURFACE, SCALE2(120, 16), FIXED_DEPTH, RGBA_MASK_AUTO); + SDL_CreateRGBSurface(SDL_SWSURFACE, DP2(120, 16), FIXED_DEPTH, RGBA_MASK_AUTO); SDL_FillRect(digits, NULL, RGB_BLACK); SDL_Surface* digit; @@ -74,11 +74,11 @@ int main(int argc, char* argv[]) { while (c = chars[i]) { digit = TTF_RenderUTF8_Blended(font.large, c, COLOR_WHITE); // Colon sits too low naturally, adjust vertically - int y = i == CHAR_COLON ? SCALE1(-1.5) : 0; + int y = i == CHAR_COLON ? DP(-1.5) : 0; SDL_BlitSurface( digit, NULL, digits, - &(SDL_Rect){(i * SCALE1(DIGIT_WIDTH)) + (SCALE1(DIGIT_WIDTH) - digit->w) / 2, - y + (SCALE1(DIGIT_HEIGHT) - digit->h) / 2}); + &(SDL_Rect){(i * DP(DIGIT_WIDTH)) + (DP(DIGIT_WIDTH) - digit->w) / 2, + y + (DP(DIGIT_HEIGHT) - digit->h) / 2}); SDL_FreeSurface(digit); i += 1; } @@ -111,9 +111,9 @@ int main(int argc, char* argv[]) { * @return New x position after blitting (x + digit width) */ int blit(int i, int x, int y) { - SDL_BlitSurface(digits, &(SDL_Rect){i * SCALE1(10), 0, SCALE2(10, 16)}, screen, + SDL_BlitSurface(digits, &(SDL_Rect){i * DP(10), 0, DP2(10, 16)}, screen, &(SDL_Rect){x, y}); - return x + SCALE1(10); + return x + DP(10); } /** @@ -301,18 +301,18 @@ int main(int argc, char* argv[]) { // Center the date/time display // Width is 188 pixels (@1x) in 24-hour mode, 223 pixels in 12-hour mode - int ox = (screen->w - (show_24hour ? SCALE1(188) : SCALE1(223))) / 2; + int ox = (screen->w - (show_24hour ? DP(188) : DP(223))) / 2; // Render date/time in format: YYYY/MM/DD HH:MM:SS [AM/PM] int x = ox; - int y = SCALE1((((FIXED_HEIGHT / FIXED_SCALE) - PILL_SIZE - DIGIT_HEIGHT) / 2)); + int y = DP((((FIXED_HEIGHT / FIXED_SCALE) - ui.pill_height - DIGIT_HEIGHT) / 2)); x = blitNumber(year_selected, x, y); x = blit(CHAR_SLASH, x, y); x = blitNumber(month_selected, x, y); x = blit(CHAR_SLASH, x, y); x = blitNumber(day_selected, x, y); - x += SCALE1(10); // space between date and time + x += DP(10); // space between date and time am_selected = hour_selected < 12; if (show_24hour) { @@ -329,25 +329,25 @@ int main(int argc, char* argv[]) { int ampm_w; if (!show_24hour) { - x += SCALE1(10); // space before AM/PM + x += DP(10); // space before AM/PM SDL_Surface* text = TTF_RenderUTF8_Blended(font.large, am_selected ? "AM" : "PM", COLOR_WHITE); - ampm_w = text->w + SCALE1(2); - SDL_BlitSurface(text, NULL, screen, &(SDL_Rect){x, y - SCALE1(3)}); + ampm_w = text->w + DP(2); + SDL_BlitSurface(text, NULL, screen, &(SDL_Rect){x, y - DP(3)}); SDL_FreeSurface(text); } // Draw selection cursor underline x = ox; - y += SCALE1(19); + y += DP(19); if (select_cursor != CURSOR_YEAR) { - x += SCALE1(50); // Width of "YYYY/" - x += (select_cursor - 1) * SCALE1(30); + x += DP(50); // Width of "YYYY/" + x += (select_cursor - 1) * DP(30); } blitBar(x, y, (select_cursor == CURSOR_YEAR - ? SCALE1(40) - : (select_cursor == CURSOR_AMPM ? ampm_w : SCALE1(20)))); + ? DP(40) + : (select_cursor == CURSOR_AMPM ? ampm_w : DP(20)))); GFX_flip(screen); dirty = 0; diff --git a/workspace/all/common/api.c b/workspace/all/common/api.c index 0dbc31a1..27a91dda 100644 --- a/workspace/all/common/api.c +++ b/workspace/all/common/api.c @@ -57,6 +57,119 @@ uint32_t RGB_LIGHT_GRAY; uint32_t RGB_GRAY; uint32_t RGB_DARK_GRAY; +/////////////////////////////// +// Display Points (DP) scaling system +/////////////////////////////// + +// Global display scale factor (PPI / 160) +float gfx_dp_scale = 2.0f; // Default to 2x until UI_initLayout is called + +// Runtime-calculated UI layout parameters +UI_Layout ui = { + .pill_height = 30, + .row_count = 6, + .padding = 10, + .text_baseline = 4, + .button_size = 20, + .button_margin = 5, + .button_padding = 12, +}; + +/** + * Initializes the DP scaling system and UI layout. + * + * Calculates dp_scale from screen PPI using the formula: + * ppi = sqrt(width² + height²) / diagonal_inches + * dp_scale = ppi / 160.0 + * + * Then computes optimal pill height to perfectly fill the screen + * with 6-8 rows of menu items. + */ +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; + gfx_dp_scale = ppi / 160.0f; + + // Apply platform scale modifier if defined +#ifdef SCALE_MODIFIER + gfx_dp_scale *= SCALE_MODIFIER; +#endif + + // Bounds for layout calculation + const int MIN_PILL = 28; + const int MAX_PILL = 32; + const int MIN_ROWS = 6; + const int MAX_ROWS = 8; + const int DEFAULT_PADDING = 10; // Consistent padding in dp + + // Calculate available height in dp + int screen_height_dp = (int)(screen_height / gfx_dp_scale + 0.5f); + int available_dp = screen_height_dp - (DEFAULT_PADDING * 2); + + // Find best row count where pill height fits in acceptable range + // We need: content rows + 2 (header spacing + footer row) + int best_rows = MIN_ROWS; + int best_pill = 30; + + // Try to fit as many rows as possible + for (int rows = MAX_ROWS; rows >= MIN_ROWS; rows--) { + int total_rows = rows + 2; // +1 header spacing, +1 footer + int pill = available_dp / total_rows; + + if (pill >= MIN_PILL && pill <= MAX_PILL) { + // Found a good fit - use it + best_rows = rows; + best_pill = pill; + break; + } else if (pill < MIN_PILL && rows > MIN_ROWS) { + // Too many rows, not enough space - try fewer rows + continue; + } else if (rows == MIN_ROWS) { + // Reached minimum rows - use closest valid pill size + if (pill < MIN_PILL) { + best_pill = MIN_PILL; + } else if (pill > MAX_PILL) { + best_pill = MAX_PILL; + } else { + best_pill = pill; + } + best_rows = rows; + } + } + + // Store calculated values, adjusting DP to ensure even physical pixels + ui.pill_height = best_pill; + ui.row_count = best_rows; + ui.padding = DEFAULT_PADDING; + + // Adjust pill_height so it produces even physical pixels (for alignment) + int pill_px = DP(ui.pill_height); + if (pill_px % 2 != 0) { + // Try increasing first (prefer slightly larger) + if (DP(ui.pill_height + 1) <= DP(MAX_PILL)) { + ui.pill_height++; + } else { + ui.pill_height--; + } + } + + // Derived proportional sizes (also ensure even pixels where needed) + ui.button_size = (ui.pill_height * 2) / 3; // ~20 for 30dp pill + int button_px = DP(ui.button_size); + if (button_px % 2 != 0) + ui.button_size++; // Buttons look better even + + 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 + ui.text_baseline = (4 * ui.pill_height + 15) / 30; // ~4 for 30dp pill + + 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); +} + static struct GFX_Context { SDL_Surface* screen; SDL_Surface* assets; @@ -123,6 +236,11 @@ SDL_Surface* GFX_init(int mode) { gfx.vsync = VSYNC_STRICT; gfx.mode = mode; + // Initialize DP scaling system +#ifdef SCREEN_DIAGONAL + UI_initLayout(gfx.screen->w, gfx.screen->h, SCREEN_DIAGONAL); +#endif + RGB_WHITE = SDL_MapRGB(gfx.screen->format, TRIAD_WHITE); RGB_BLACK = SDL_MapRGB(gfx.screen->format, TRIAD_BLACK); RGB_LIGHT_GRAY = SDL_MapRGB(gfx.screen->format, TRIAD_LIGHT_GRAY); @@ -144,43 +262,108 @@ SDL_Surface* GFX_init(int mode) { asset_rgbs[ASSET_DOT] = RGB_LIGHT_GRAY; asset_rgbs[ASSET_HOLE] = RGB_BLACK; - asset_rects[ASSET_WHITE_PILL] = (SDL_Rect){SCALE4(1, 1, 30, 30)}; - asset_rects[ASSET_BLACK_PILL] = (SDL_Rect){SCALE4(33, 1, 30, 30)}; - asset_rects[ASSET_DARK_GRAY_PILL] = (SDL_Rect){SCALE4(65, 1, 30, 30)}; - asset_rects[ASSET_OPTION] = (SDL_Rect){SCALE4(97, 1, 20, 20)}; - asset_rects[ASSET_BUTTON] = (SDL_Rect){SCALE4(1, 33, 20, 20)}; - asset_rects[ASSET_PAGE_BG] = (SDL_Rect){SCALE4(64, 33, 15, 15)}; - asset_rects[ASSET_STATE_BG] = (SDL_Rect){SCALE4(23, 54, 8, 8)}; - asset_rects[ASSET_PAGE] = (SDL_Rect){SCALE4(39, 54, 6, 6)}; - asset_rects[ASSET_BAR] = (SDL_Rect){SCALE4(33, 58, 4, 4)}; - asset_rects[ASSET_BAR_BG] = (SDL_Rect){SCALE4(15, 55, 4, 4)}; - asset_rects[ASSET_BAR_BG_MENU] = (SDL_Rect){SCALE4(85, 56, 4, 4)}; - asset_rects[ASSET_UNDERLINE] = (SDL_Rect){SCALE4(85, 51, 3, 3)}; - asset_rects[ASSET_DOT] = (SDL_Rect){SCALE4(33, 54, 2, 2)}; - asset_rects[ASSET_BRIGHTNESS] = (SDL_Rect){SCALE4(23, 33, 19, 19)}; - asset_rects[ASSET_VOLUME_MUTE] = (SDL_Rect){SCALE4(44, 33, 10, 16)}; - asset_rects[ASSET_VOLUME] = (SDL_Rect){SCALE4(44, 33, 18, 16)}; - asset_rects[ASSET_BATTERY] = (SDL_Rect){SCALE4(47, 51, 17, 10)}; - asset_rects[ASSET_BATTERY_LOW] = (SDL_Rect){SCALE4(66, 51, 17, 10)}; - asset_rects[ASSET_BATTERY_FILL] = (SDL_Rect){SCALE4(81, 33, 12, 6)}; - asset_rects[ASSET_BATTERY_FILL_LOW] = (SDL_Rect){SCALE4(1, 55, 12, 6)}; - asset_rects[ASSET_BATTERY_BOLT] = (SDL_Rect){SCALE4(81, 41, 12, 6)}; - asset_rects[ASSET_SCROLL_UP] = (SDL_Rect){SCALE4(97, 23, 24, 6)}; - asset_rects[ASSET_SCROLL_DOWN] = (SDL_Rect){SCALE4(97, 31, 24, 6)}; - asset_rects[ASSET_WIFI] = (SDL_Rect){SCALE4(95, 39, 14, 10)}; - asset_rects[ASSET_HOLE] = (SDL_Rect){SCALE4(1, 63, 20, 20)}; + // Select asset tier based on dp_scale (round up to next available tier) + // Available tiers: @1x, @2x, @3x, @4x + int asset_scale = (int)ceilf(gfx_dp_scale); + if (asset_scale < 1) + asset_scale = 1; + if (asset_scale > 4) + asset_scale = 4; + // Load asset sprite sheet at selected tier char asset_path[MAX_PATH]; - sprintf(asset_path, RES_PATH "/assets@%ix.png", FIXED_SCALE); + sprintf(asset_path, RES_PATH "/assets@%ix.png", asset_scale); if (!exists(asset_path)) LOG_info("missing assets, you're about to segfault dummy!\n"); - gfx.assets = IMG_Load(asset_path); + SDL_Surface* loaded_assets = IMG_Load(asset_path); + + // Define asset rectangles in the loaded sprite sheet (at asset_scale) + // Base coordinates are @1x, multiply by asset_scale for actual position +#define ASSET_SCALE4(x, y, w, h) \ + ((x) *asset_scale), ((y) *asset_scale), ((w) *asset_scale), ((h) *asset_scale) + + asset_rects[ASSET_WHITE_PILL] = (SDL_Rect){ASSET_SCALE4(1, 1, 30, 30)}; + 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_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)}; + asset_rects[ASSET_PAGE] = (SDL_Rect){ASSET_SCALE4(39, 54, 6, 6)}; + asset_rects[ASSET_BAR] = (SDL_Rect){ASSET_SCALE4(33, 58, 4, 4)}; + asset_rects[ASSET_BAR_BG] = (SDL_Rect){ASSET_SCALE4(15, 55, 4, 4)}; + asset_rects[ASSET_BAR_BG_MENU] = (SDL_Rect){ASSET_SCALE4(85, 56, 4, 4)}; + asset_rects[ASSET_UNDERLINE] = (SDL_Rect){ASSET_SCALE4(85, 51, 3, 3)}; + asset_rects[ASSET_DOT] = (SDL_Rect){ASSET_SCALE4(33, 54, 2, 2)}; + asset_rects[ASSET_BRIGHTNESS] = (SDL_Rect){ASSET_SCALE4(23, 33, 19, 19)}; + asset_rects[ASSET_VOLUME_MUTE] = (SDL_Rect){ASSET_SCALE4(44, 33, 10, 16)}; + asset_rects[ASSET_VOLUME] = (SDL_Rect){ASSET_SCALE4(44, 33, 18, 16)}; + asset_rects[ASSET_BATTERY] = (SDL_Rect){ASSET_SCALE4(47, 51, 17, 10)}; + asset_rects[ASSET_BATTERY_LOW] = (SDL_Rect){ASSET_SCALE4(66, 51, 17, 10)}; + asset_rects[ASSET_BATTERY_FILL] = (SDL_Rect){ASSET_SCALE4(81, 33, 12, 6)}; + asset_rects[ASSET_BATTERY_FILL_LOW] = (SDL_Rect){ASSET_SCALE4(1, 55, 12, 6)}; + asset_rects[ASSET_BATTERY_BOLT] = (SDL_Rect){ASSET_SCALE4(81, 41, 12, 6)}; + asset_rects[ASSET_SCROLL_UP] = (SDL_Rect){ASSET_SCALE4(97, 23, 24, 6)}; + asset_rects[ASSET_SCROLL_DOWN] = (SDL_Rect){ASSET_SCALE4(97, 31, 24, 6)}; + asset_rects[ASSET_WIFI] = (SDL_Rect){ASSET_SCALE4(95, 39, 14, 10)}; + asset_rects[ASSET_HOLE] = (SDL_Rect){ASSET_SCALE4(1, 63, 20, 20)}; + + // If dp_scale doesn't match asset_scale, scale the assets + if (fabsf(gfx_dp_scale - (float)asset_scale) > 0.01f) { + // Scale down from asset_scale to dp_scale + float scale_ratio = gfx_dp_scale / (float)asset_scale; + int new_w = (int)(loaded_assets->w * scale_ratio + 0.5f); + int new_h = (int)(loaded_assets->h * scale_ratio + 0.5f); + + // Create scaled surface with same format + gfx.assets = SDL_CreateRGBSurface(0, new_w, new_h, loaded_assets->format->BitsPerPixel, + loaded_assets->format->Rmask, loaded_assets->format->Gmask, + loaded_assets->format->Bmask, loaded_assets->format->Amask); + + // Scale using SDL_BlitScaled (nearest-neighbor, sharp but correctly sized) + // TODO: Implement bilinear filtering for smoother edges + SDL_BlitScaled(loaded_assets, NULL, gfx.assets, NULL); + SDL_FreeSurface(loaded_assets); + + // Scale asset rectangles to match the scaled surface + // Round dimensions to ensure even differences with pill size for perfect centering + int pill_px = DP(ui.pill_height); + for (int i = 0; i < ASSET_COUNT; i++) { + asset_rects[i].x = (int)(asset_rects[i].x * scale_ratio + 0.5f); + asset_rects[i].y = (int)(asset_rects[i].y * scale_ratio + 0.5f); + + int w = (int)(asset_rects[i].w * scale_ratio + 0.5f); + int h = (int)(asset_rects[i].h * scale_ratio + 0.5f); + + // For standalone assets that get centered in pills, ensure even difference + // Skip battery family (outline, fill, bolt) - they have internal positioning dependencies + // Only adjust: brightness, volume, wifi + if (i == ASSET_BRIGHTNESS || i == ASSET_VOLUME_MUTE || i == ASSET_VOLUME || + i == ASSET_WIFI) { + if ((pill_px - w) % 2 != 0) w++; // Make difference even + if ((pill_px - h) % 2 != 0) h++; // Make difference even + } + + asset_rects[i].w = w; + asset_rects[i].h = h; + } + + LOG_info("GFX_init: Scaled assets from @%dx to dp_scale=%.2f (nearest-neighbor)\n", + asset_scale, gfx_dp_scale); + } else { + // Perfect match, use assets as-is + gfx.assets = loaded_assets; + LOG_info("GFX_init: Using assets@%dx (exact match for dp_scale=%.2f)\n", asset_scale, + gfx_dp_scale); + } + +#undef ASSET_SCALE4 TTF_Init(); - font.large = TTF_OpenFont(FONT_PATH, SCALE1(FONT_LARGE)); - font.medium = TTF_OpenFont(FONT_PATH, SCALE1(FONT_MEDIUM)); - font.small = TTF_OpenFont(FONT_PATH, SCALE1(FONT_SMALL)); - font.tiny = TTF_OpenFont(FONT_PATH, SCALE1(FONT_TINY)); + font.large = TTF_OpenFont(FONT_PATH, DP(FONT_LARGE)); + font.medium = TTF_OpenFont(FONT_PATH, DP(FONT_MEDIUM)); + font.small = TTF_OpenFont(FONT_PATH, DP(FONT_SMALL)); + font.tiny = TTF_OpenFont(FONT_PATH, DP(FONT_TINY)); TTF_SetFontStyle(font.large, TTF_STYLE_BOLD); TTF_SetFontStyle(font.medium, TTF_STYLE_BOLD); @@ -698,18 +881,45 @@ void GFX_blitBattery(SDL_Surface* dst, const SDL_Rect* dst_rect) { y = dst_rect->y; } SDL_Rect rect = asset_rects[ASSET_BATTERY]; - x += (SCALE1(PILL_SIZE) - (rect.w + FIXED_SCALE)) / 2; - y += (SCALE1(PILL_SIZE) - rect.h) / 2; + int x_off = (DP(ui.pill_height) - rect.w) / 2; + int y_off = (DP(ui.pill_height) - rect.h) / 2; + + static int logged = 0; + if (!logged) { + LOG_info("GFX_blitBattery: pill=%dpx, battery=%dx%d, offset=(%d,%d)\n", DP(ui.pill_height), + rect.w, rect.h, x_off, y_off); + logged = 1; + } + + x += x_off; + y += y_off; + + // Battery fill/bolt offsets must scale with the actual battery asset size + // At @1x design: fill is 3px right, 2px down from battery top-left + // Scale this based on actual battery width: rect.w / 17 (17 = @1x battery width) + int fill_x_offset = (rect.w * 3 + 8) / 17; // (rect.w * 3/17), rounded + int fill_y_offset = (rect.h * 2 + 5) / 10; // (rect.h * 2/10), rounded if (pwr.is_charging) { + if (!logged) { + LOG_info("Battery bolt: offset=(%d,%d) from battery corner\n", fill_x_offset, + fill_y_offset); + } GFX_blitAsset(ASSET_BATTERY, NULL, dst, &(SDL_Rect){x, y}); - GFX_blitAsset(ASSET_BATTERY_BOLT, NULL, dst, &(SDL_Rect){x + SCALE1(3), y + SCALE1(2)}); + GFX_blitAsset(ASSET_BATTERY_BOLT, NULL, dst, + &(SDL_Rect){x + fill_x_offset, y + fill_y_offset}); } else { int percent = pwr.charge; GFX_blitAsset(percent <= 10 ? ASSET_BATTERY_LOW : ASSET_BATTERY, NULL, dst, &(SDL_Rect){x, y}); rect = asset_rects[ASSET_BATTERY_FILL]; + + if (!logged) { + LOG_info("Battery fill: %dx%d, offset=(%d,%d)\n", rect.w, rect.h, fill_x_offset, + fill_y_offset); + } + SDL_Rect clip = rect; clip.w *= percent; clip.w /= 100; @@ -719,7 +929,7 @@ void GFX_blitBattery(SDL_Surface* dst, const SDL_Rect* dst_rect) { clip.y = 0; GFX_blitAsset(percent <= 20 ? ASSET_BATTERY_FILL_LOW : ASSET_BATTERY_FILL, &clip, dst, - &(SDL_Rect){x + SCALE1(3) + clip.x, y + SCALE1(2)}); + &(SDL_Rect){x + fill_x_offset + clip.x, y + fill_y_offset}); } } @@ -744,16 +954,16 @@ int GFX_getButtonWidth(char* hint, char* button) { int special_case = !strcmp(button, BRIGHTNESS_BUTTON_LABEL); // TODO: oof if (strlen(button) == 1) { - button_width += SCALE1(BUTTON_SIZE); + button_width += DP(ui.button_size); } else { - button_width += SCALE1(BUTTON_SIZE) / 2; + button_width += DP(ui.button_size) / 2; TTF_SizeUTF8(special_case ? font.large : font.tiny, button, &width, NULL); button_width += width; } - button_width += SCALE1(BUTTON_MARGIN); + button_width += DP(ui.button_margin); TTF_SizeUTF8(font.small, hint, &width, NULL); - button_width += width + SCALE1(BUTTON_MARGIN); + button_width += width + DP(ui.button_margin); return button_width; } @@ -783,34 +993,34 @@ void GFX_blitButton(char* hint, char* button, SDL_Surface* dst, SDL_Rect* dst_re // label text = TTF_RenderUTF8_Blended(font.medium, button, COLOR_BUTTON_TEXT); SDL_BlitSurface(text, NULL, dst, - &(SDL_Rect){dst_rect->x + (SCALE1(BUTTON_SIZE) - text->w) / 2, - dst_rect->y + (SCALE1(BUTTON_SIZE) - text->h) / 2}); - ox += SCALE1(BUTTON_SIZE); + &(SDL_Rect){dst_rect->x + (DP(ui.button_size) - text->w) / 2, + dst_rect->y + (DP(ui.button_size) - text->h) / 2}); + ox += DP(ui.button_size); SDL_FreeSurface(text); } else { text = TTF_RenderUTF8_Blended(special_case ? font.large : font.tiny, button, COLOR_BUTTON_TEXT); GFX_blitPill(ASSET_BUTTON, dst, - &(SDL_Rect){dst_rect->x, dst_rect->y, SCALE1(BUTTON_SIZE) / 2 + text->w, - SCALE1(BUTTON_SIZE)}); - ox += SCALE1(BUTTON_SIZE) / 4; + &(SDL_Rect){dst_rect->x, dst_rect->y, DP(ui.button_size) / 2 + text->w, + DP(ui.button_size)}); + ox += DP(ui.button_size) / 4; - int oy = special_case ? SCALE1(-2) : 0; + int oy = special_case ? DP(-2) : 0; SDL_BlitSurface(text, NULL, dst, &(SDL_Rect){ox + dst_rect->x, - oy + dst_rect->y + (SCALE1(BUTTON_SIZE) - text->h) / 2, text->w, + oy + dst_rect->y + (DP(ui.button_size) - text->h) / 2, text->w, text->h}); ox += text->w; - ox += SCALE1(BUTTON_SIZE) / 4; + ox += DP(ui.button_size) / 4; SDL_FreeSurface(text); } - ox += SCALE1(BUTTON_MARGIN); + ox += DP(ui.button_margin); // hint text text = TTF_RenderUTF8_Blended(font.small, hint, COLOR_WHITE); SDL_BlitSurface(text, NULL, dst, - &(SDL_Rect){ox + dst_rect->x, dst_rect->y + (SCALE1(BUTTON_SIZE) - text->h) / 2, + &(SDL_Rect){ox + dst_rect->x, dst_rect->y + (DP(ui.button_size) - text->h) / 2, text->w, text->h}); SDL_FreeSurface(text); } @@ -842,7 +1052,7 @@ void GFX_blitMessage(TTF_Font* ttf_font, char* msg, SDL_Surface* dst, const SDL_ if (row_count == 0) return; - int rendered_height = SCALE1(LINE_HEIGHT) * row_count; + int rendered_height = DP(LINE_HEIGHT) * row_count; int y = dst_rect->y; y += (dst_rect->h - rendered_height) / 2; @@ -867,7 +1077,7 @@ void GFX_blitMessage(TTF_Font* ttf_font, char* msg, SDL_Surface* dst, const SDL_ SDL_BlitSurface(text, NULL, dst, &(SDL_Rect){x, y}); SDL_FreeSurface(text); } - y += SCALE1(LINE_HEIGHT); + y += DP(LINE_HEIGHT); } } @@ -890,11 +1100,11 @@ int GFX_blitHardwareGroup(SDL_Surface* dst, int show_setting) { int ow = 0; if (show_setting && !GetHDMI()) { - ow = SCALE1(PILL_SIZE + SETTINGS_WIDTH + 10 + 4); - ox = dst->w - SCALE1(PADDING) - ow; - oy = SCALE1(PADDING); + ow = DP(ui.pill_height + SETTINGS_WIDTH + 10 + 4); + ox = dst->w - DP(ui.padding) - ow; + oy = DP(ui.padding); GFX_blitPill(gfx.mode == MODE_MAIN ? ASSET_DARK_GRAY_PILL : ASSET_BLACK_PILL, dst, - &(SDL_Rect){ox, oy, ow, SCALE1(PILL_SIZE)}); + &(SDL_Rect){ox, oy, ow, DP(ui.pill_height)}); int setting_value; int setting_min; @@ -911,40 +1121,40 @@ int GFX_blitHardwareGroup(SDL_Surface* dst, int show_setting) { int asset = show_setting == 1 ? ASSET_BRIGHTNESS : (setting_value > 0 ? ASSET_VOLUME : ASSET_VOLUME_MUTE); - int ax = ox + (show_setting == 1 ? SCALE1(6) : SCALE1(8)); - int ay = oy + (show_setting == 1 ? SCALE1(5) : SCALE1(7)); + int ax = ox + (show_setting == 1 ? DP(6) : DP(8)); + int ay = oy + (show_setting == 1 ? DP(5) : DP(7)); GFX_blitAsset(asset, NULL, dst, &(SDL_Rect){ax, ay}); - ox += SCALE1(PILL_SIZE); - oy += SCALE1((PILL_SIZE - SETTINGS_SIZE) / 2); + ox += DP(ui.pill_height); + oy += DP((ui.pill_height - SETTINGS_SIZE) / 2); GFX_blitPill(gfx.mode == MODE_MAIN ? ASSET_BAR_BG : ASSET_BAR_BG_MENU, dst, - &(SDL_Rect){ox, oy, SCALE1(SETTINGS_WIDTH), SCALE1(SETTINGS_SIZE)}); + &(SDL_Rect){ox, oy, DP(SETTINGS_WIDTH), DP(SETTINGS_SIZE)}); float percent = ((float)(setting_value - setting_min) / (setting_max - setting_min)); if (show_setting == 1 || setting_value > 0) { GFX_blitPill( ASSET_BAR, dst, - &(SDL_Rect){ox, oy, SCALE1(SETTINGS_WIDTH) * percent, SCALE1(SETTINGS_SIZE)}); + &(SDL_Rect){ox, oy, DP(SETTINGS_WIDTH) * percent, DP(SETTINGS_SIZE)}); } } else { // TODO: handle wifi int show_wifi = PLAT_isOnline(); // NOOOOO! not every frame! - int ww = SCALE1(PILL_SIZE - 3); - ow = SCALE1(PILL_SIZE); + int ww = DP(ui.pill_height - 3); + ow = DP(ui.pill_height); if (show_wifi) ow += ww; - ox = dst->w - SCALE1(PADDING) - ow; - oy = SCALE1(PADDING); + ox = dst->w - DP(ui.padding) - ow; + oy = DP(ui.padding); GFX_blitPill(gfx.mode == MODE_MAIN ? ASSET_DARK_GRAY_PILL : ASSET_BLACK_PILL, dst, - &(SDL_Rect){ox, oy, ow, SCALE1(PILL_SIZE)}); + &(SDL_Rect){ox, oy, ow, DP(ui.pill_height)}); if (show_wifi) { SDL_Rect rect = asset_rects[ASSET_WIFI]; int x = ox; int y = oy; - x += (SCALE1(PILL_SIZE) - rect.w) / 2; - y += (SCALE1(PILL_SIZE) - rect.h) / 2; + x += (DP(ui.pill_height) - rect.w) / 2; + y += (DP(ui.pill_height) - rect.h) / 2; GFX_blitAsset(ASSET_WIFI, NULL, dst, &(SDL_Rect){x, y}); ox += ww; @@ -1005,8 +1215,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 - SCALE1(PADDING) : SCALE1(PADDING); - oy = dst->h - SCALE1(PADDING + PILL_SIZE); + ox = align_right ? dst->w - DP(ui.padding) : DP(ui.padding); + oy = dst->h - DP(ui.padding + ui.pill_height); for (int i = 0; i < 2; i++) { if (!pairs[i * 2]) @@ -1021,20 +1231,20 @@ int GFX_blitButtonGroup(char** pairs, int primary, SDL_Surface* dst, int align_r hints[h].button = button; hints[h].ow = w; h += 1; - ow += SCALE1(BUTTON_MARGIN) + w; + ow += DP(ui.button_margin) + w; } - ow += SCALE1(BUTTON_MARGIN); + ow += DP(ui.button_margin); if (align_right) ox -= ow; GFX_blitPill(gfx.mode == MODE_MAIN ? ASSET_DARK_GRAY_PILL : ASSET_BLACK_PILL, dst, - &(SDL_Rect){ox, oy, ow, SCALE1(PILL_SIZE)}); + &(SDL_Rect){ox, oy, ow, DP(ui.pill_height)}); - ox += SCALE1(BUTTON_MARGIN); - oy += SCALE1(BUTTON_MARGIN); + ox += DP(ui.button_margin); + oy += DP(ui.button_margin); for (int i = 0; i < h; i++) { GFX_blitButton(hints[i].hint, hints[i].button, dst, &(SDL_Rect){ox, oy}); - ox += hints[i].ow + SCALE1(BUTTON_MARGIN); + ox += hints[i].ow + DP(ui.button_margin); } return ow; } diff --git a/workspace/all/common/api.h b/workspace/all/common/api.h index 4580650e..c8ffe12c 100644 --- a/workspace/all/common/api.h +++ b/workspace/all/common/api.h @@ -18,6 +18,81 @@ #include "scaler.h" #include "sdl.h" +/////////////////////////////// +// Display Points (DP) scaling system +/////////////////////////////// + +/** + * Resolution-independent UI scaling based on physical screen density (PPI). + * + * The DP system automatically calculates optimal UI scaling from each device's + * physical screen size, eliminating per-platform manual tuning. UI elements + * are specified in density-independent "display points" (dp), then converted + * to physical pixels at runtime. + * + * Core formula: + * ppi = sqrt(width² + height²) / diagonal_inches + * dp_scale = ppi / 160.0 (160 = Android MDPI baseline) + * + * Example: Miyoo Mini (640x480, 2.8") → 286 PPI → dp_scale ≈ 1.79 + * + * Usage: + * DP(30) // Convert 30dp to physical pixels + * DP2(10, 20) // Convert two values + * DP4(x, y, w, h) // Convert four values (for SDL_Rect) + */ + +/** + * Global display scale factor. + * + * Calculated at startup from screen PPI. All UI coordinates should be + * converted through DP() macros using this value. + */ +extern float gfx_dp_scale; + +/** + * Convert display points to physical pixels. + * + * @param x Value in display points + * @return Value in physical pixels (rounded) + */ +#define DP(x) ((int)((x) * gfx_dp_scale + 0.5f)) +#define DP2(a, b) DP(a), DP(b) +#define DP3(a, b, c) DP(a), DP(b), DP(c) +#define DP4(a, b, c, d) DP(a), DP(b), DP(c), DP(d) + +/** + * Runtime-calculated UI layout parameters. + * + * These values are computed by UI_initLayout() based on screen dimensions + * to optimally fill the display without per-platform manual configuration. + */ +typedef struct UI_Layout { + 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 text_baseline; // Vertical offset for text centering in pill + int button_size; // Size of button graphics in dp + int button_margin; // Margin around buttons in dp + int button_padding; // Padding inside buttons in dp +} UI_Layout; + +extern UI_Layout ui; + +/** + * Initializes the DP scaling system and UI layout. + * + * Calculates dp_scale from screen PPI, then computes optimal pill height, + * row count, and padding to fill the screen perfectly. + * + * @param screen_width Physical screen width in pixels + * @param screen_height Physical screen height in pixels + * @param diagonal_inches Physical screen diagonal in inches + * + * @note Called automatically from GFX_init() + */ +void UI_initLayout(int screen_width, int screen_height, float diagonal_inches); + /////////////////////////////// // Video page buffer constants /////////////////////////////// diff --git a/workspace/all/minarch/minarch.c b/workspace/all/minarch/minarch.c index 7a60089d..ac58d17e 100644 --- a/workspace/all/minarch/minarch.c +++ b/workspace/all/minarch/minarch.c @@ -2090,8 +2090,8 @@ static void OptionList_init(const struct retro_core_option_definition* defs) { // these magic numbers are more about chars per line than pixel width // so it's not going to be relative to the screen size, only the scale // what does that even mean? - GFX_wrapText(font.tiny, item->desc, SCALE1(240), 2); // TODO magic number! - GFX_wrapText(font.medium, item->full, SCALE1(240), 7); // TODO: magic number! + GFX_wrapText(font.tiny, item->desc, DP(240), 2); // TODO magic number! + GFX_wrapText(font.medium, item->full, DP(240), 7); // TODO: magic number! } for (count = 0; def->values[count].value; count++) @@ -3042,7 +3042,7 @@ enum { }; #define DIGIT_SPACE DIGIT_COUNT static void MSG_init(void) { - digits = SDL_CreateRGBSurface(SDL_SWSURFACE, SCALE2(DIGIT_WIDTH * DIGIT_COUNT, DIGIT_HEIGHT), + digits = SDL_CreateRGBSurface(SDL_SWSURFACE, DP2(DIGIT_WIDTH * DIGIT_COUNT, DIGIT_HEIGHT), FIXED_DEPTH, 0, 0, 0, 0); SDL_FillRect(digits, NULL, RGB_BLACK); @@ -3055,8 +3055,8 @@ static void MSG_init(void) { digit = TTF_RenderUTF8_Blended(font.tiny, c, COLOR_WHITE); SDL_BlitSurface( digit, NULL, digits, - &(SDL_Rect){(i * SCALE1(DIGIT_WIDTH)) + (SCALE1(DIGIT_WIDTH) - digit->w) / 2, - (SCALE1(DIGIT_HEIGHT) - digit->h) / 2}); + &(SDL_Rect){(i * DP(DIGIT_WIDTH)) + (DP(DIGIT_WIDTH) - digit->w) / 2, + (DP(DIGIT_HEIGHT) - digit->h) / 2}); SDL_FreeSurface(digit); i += 1; } @@ -3064,9 +3064,9 @@ static void MSG_init(void) { static int MSG_blitChar(int n, int x, int y) { if (n != DIGIT_SPACE) SDL_BlitSurface(digits, - &(SDL_Rect){n * SCALE1(DIGIT_WIDTH), 0, SCALE2(DIGIT_WIDTH, DIGIT_HEIGHT)}, + &(SDL_Rect){n * DP(DIGIT_WIDTH), 0, DP2(DIGIT_WIDTH, DIGIT_HEIGHT)}, screen, &(SDL_Rect){x, y}); - return x + SCALE1(DIGIT_WIDTH + DIGIT_TRACKING); + return x + DP(DIGIT_WIDTH + DIGIT_TRACKING); } static int MSG_blitInt(int num, int x, int y) { int i = num; @@ -4555,8 +4555,8 @@ static int Menu_message(char* message, char** pairs) { if (dirty) { GFX_clear(screen); GFX_blitMessage(font.medium, message, screen, - &(SDL_Rect){0, SCALE1(PADDING), screen->w, - screen->h - SCALE1(PILL_SIZE + PADDING)}); + &(SDL_Rect){0, DP(ui.padding), screen->w, + screen->h - DP(ui.pill_height + ui.padding)}); GFX_blitButtonGroup(pairs, 0, screen, 1); GFX_flip(screen); dirty = 0; @@ -5106,8 +5106,8 @@ static int Menu_options(MenuList* list) { // dependent on option list offset top and bottom, eg. the gray triangles int max_visible_options = - (screen->h - ((SCALE1(PADDING + PILL_SIZE) * 2) + SCALE1(BUTTON_SIZE))) / - SCALE1(BUTTON_SIZE); // 7 for 480, 10 for 720 + (screen->h - ((DP(ui.padding + ui.pill_height) * 2) + DP(ui.button_size))) / + DP(ui.button_size); // 7 for 480, 10 for 720 int count; for (count = 0; items[count].name; count++) @@ -5281,16 +5281,16 @@ static int Menu_options(MenuList* list) { MenuItem* item = &items[i]; int w = 0; TTF_SizeUTF8(font.small, item->name, &w, NULL); - w += SCALE1(OPTION_PADDING * 2); + w += DP(OPTION_PADDING * 2); if (w > mw) mw = w; } // cache the result - list->max_width = mw = MIN(mw, screen->w - SCALE1(PADDING * 2)); + list->max_width = mw = MIN(mw, screen->w - DP(ui.padding * 2)); } int ox = (screen->w - mw) / 2; - int oy = SCALE1(PADDING + PILL_SIZE); + int oy = DP(ui.padding + ui.pill_height); int selected_row = selected - start; for (int i = start, j = 0; i < end; i++, j++) { MenuItem* item = &items[i]; @@ -5301,11 +5301,11 @@ static int Menu_options(MenuList* list) { // move out of conditional if centering int w = 0; TTF_SizeUTF8(font.small, item->name, &w, NULL); - w += SCALE1(OPTION_PADDING * 2); + w += DP(OPTION_PADDING * 2); GFX_blitPill( ASSET_BUTTON, screen, - &(SDL_Rect){ox, oy + SCALE1(j * BUTTON_SIZE), w, SCALE1(BUTTON_SIZE)}); + &(SDL_Rect){ox, oy + DP(j * ui.button_size), w, DP(ui.button_size)}); text_color = COLOR_BLACK; if (item->desc) @@ -5313,18 +5313,18 @@ static int Menu_options(MenuList* list) { } text = TTF_RenderUTF8_Blended(font.small, item->name, text_color); SDL_BlitSurface(text, NULL, screen, - &(SDL_Rect){ox + SCALE1(OPTION_PADDING), - oy + SCALE1((j * BUTTON_SIZE) + 1)}); + &(SDL_Rect){ox + DP(OPTION_PADDING), + oy + DP((j * ui.button_size) + 1)}); SDL_FreeSurface(text); } } else if (type == MENU_FIXED) { // NOTE: no need to calculate max width - int mw = screen->w - SCALE1(PADDING * 2); + int mw = screen->w - DP(ui.padding * 2); // int lw,rw; // lw = rw = mw / 2; int ox, oy; - ox = oy = SCALE1(PADDING); - oy += SCALE1(PILL_SIZE); + ox = oy = DP(ui.padding); + oy += DP(ui.pill_height); int selected_row = selected - start; for (int i = start, j = 0; i < end; i++, j++) { @@ -5335,15 +5335,15 @@ static int Menu_options(MenuList* list) { // gray pill GFX_blitPill( ASSET_OPTION, screen, - &(SDL_Rect){ox, oy + SCALE1(j * BUTTON_SIZE), mw, SCALE1(BUTTON_SIZE)}); + &(SDL_Rect){ox, oy + DP(j * ui.button_size), mw, DP(ui.button_size)}); } 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 - SCALE1(OPTION_PADDING), - oy + SCALE1((j * BUTTON_SIZE) + 3)}); + &(SDL_Rect){ox + mw - text->w - DP(OPTION_PADDING), + oy + DP((j * ui.button_size) + 3)}); SDL_FreeSurface(text); } @@ -5352,10 +5352,10 @@ static int Menu_options(MenuList* list) { // white pill int w = 0; TTF_SizeUTF8(font.small, item->name, &w, NULL); - w += SCALE1(OPTION_PADDING * 2); + w += DP(OPTION_PADDING * 2); GFX_blitPill( ASSET_BUTTON, screen, - &(SDL_Rect){ox, oy + SCALE1(j * BUTTON_SIZE), w, SCALE1(BUTTON_SIZE)}); + &(SDL_Rect){ox, oy + DP(j * ui.button_size), w, DP(ui.button_size)}); text_color = COLOR_BLACK; if (item->desc) @@ -5363,8 +5363,8 @@ static int Menu_options(MenuList* list) { } text = TTF_RenderUTF8_Blended(font.small, item->name, text_color); SDL_BlitSurface(text, NULL, screen, - &(SDL_Rect){ox + SCALE1(OPTION_PADDING), - oy + SCALE1((j * BUTTON_SIZE) + 1)}); + &(SDL_Rect){ox + DP(OPTION_PADDING), + oy + DP((j * ui.button_size) + 1)}); SDL_FreeSurface(text); } } else if (type == MENU_VAR || type == MENU_INPUT) { @@ -5392,16 +5392,16 @@ static int Menu_options(MenuList* list) { } else { w = lw + mrw; } - w += SCALE1(OPTION_PADDING * 4); + w += DP(OPTION_PADDING * 4); if (w > mw) mw = w; } // cache the result - list->max_width = mw = MIN(mw, screen->w - SCALE1(PADDING * 2)); + list->max_width = mw = MIN(mw, screen->w - DP(ui.padding * 2)); } int ox = (screen->w - mw) / 2; - int oy = SCALE1(PADDING + PILL_SIZE); + int oy = DP(ui.padding + ui.pill_height); int selected_row = selected - start; for (int i = start, j = 0; i < end; i++, j++) { MenuItem* item = &items[i]; @@ -5411,15 +5411,15 @@ static int Menu_options(MenuList* list) { // gray pill GFX_blitPill( ASSET_OPTION, screen, - &(SDL_Rect){ox, oy + SCALE1(j * BUTTON_SIZE), mw, SCALE1(BUTTON_SIZE)}); + &(SDL_Rect){ox, oy + DP(j * ui.button_size), mw, DP(ui.button_size)}); // white pill int w = 0; TTF_SizeUTF8(font.small, item->name, &w, NULL); - w += SCALE1(OPTION_PADDING * 2); + w += DP(OPTION_PADDING * 2); GFX_blitPill( ASSET_BUTTON, screen, - &(SDL_Rect){ox, oy + SCALE1(j * BUTTON_SIZE), w, SCALE1(BUTTON_SIZE)}); + &(SDL_Rect){ox, oy + DP(j * ui.button_size), w, DP(ui.button_size)}); text_color = COLOR_BLACK; if (item->desc) @@ -5427,8 +5427,8 @@ static int Menu_options(MenuList* list) { } text = TTF_RenderUTF8_Blended(font.small, item->name, text_color); SDL_BlitSurface(text, NULL, screen, - &(SDL_Rect){ox + SCALE1(OPTION_PADDING), - oy + SCALE1((j * BUTTON_SIZE) + 1)}); + &(SDL_Rect){ox + DP(OPTION_PADDING), + oy + DP((j * ui.button_size) + 1)}); SDL_FreeSurface(text); if (await_input && j == selected_row) { @@ -5437,8 +5437,8 @@ static int Menu_options(MenuList* list) { 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 - SCALE1(OPTION_PADDING), - oy + SCALE1((j * BUTTON_SIZE) + 3)}); + &(SDL_Rect){ox + mw - text->w - DP(OPTION_PADDING), + oy + DP((j * ui.button_size) + 3)}); SDL_FreeSurface(text); } } @@ -5447,16 +5447,16 @@ static int Menu_options(MenuList* list) { if (count > max_visible_options) { #define SCROLL_WIDTH 24 #define SCROLL_HEIGHT 4 - int ox = (screen->w - SCALE1(SCROLL_WIDTH)) / 2; - int oy = SCALE1((PILL_SIZE - SCROLL_HEIGHT) / 2); + int ox = (screen->w - DP(SCROLL_WIDTH)) / 2; + int oy = DP((ui.pill_height - SCROLL_HEIGHT) / 2); if (start > 0) GFX_blitAsset(ASSET_SCROLL_UP, NULL, screen, - &(SDL_Rect){ox, SCALE1(PADDING) + oy}); + &(SDL_Rect){ox, DP(ui.padding) + oy}); if (end < count) GFX_blitAsset( ASSET_SCROLL_DOWN, NULL, screen, &(SDL_Rect){ox, - screen->h - SCALE1(PADDING + PILL_SIZE + BUTTON_SIZE) + oy}); + screen->h - DP(ui.padding + ui.pill_height + ui.button_size) + oy}); } if (!desc && list->desc) @@ -5464,10 +5464,10 @@ static int Menu_options(MenuList* list) { if (desc) { int w, h; - GFX_sizeText(font.tiny, desc, SCALE1(12), &w, &h); + GFX_sizeText(font.tiny, desc, DP(12), &w, &h); GFX_blitText( - font.tiny, desc, SCALE1(12), COLOR_WHITE, screen, - &(SDL_Rect){(screen->w - w) / 2, screen->h - SCALE1(PADDING) - h, w, h}); + font.tiny, desc, DP(12), COLOR_WHITE, screen, + &(SDL_Rect){(screen->w - w) / 2, screen->h - DP(ui.padding) - h, w, h}); } GFX_flip(screen); @@ -5937,21 +5937,21 @@ static void Menu_loop(void) { int ox, oy; int ow = GFX_blitHardwareGroup(screen, show_setting); - int max_width = screen->w - SCALE1(PADDING * 2) - ow; + int max_width = screen->w - DP(ui.padding * 2) - ow; char display_name[256]; int text_width = GFX_truncateText(font.large, rom_name, display_name, max_width, - SCALE1(BUTTON_PADDING * 2)); + DP(ui.button_padding * 2)); max_width = MIN(max_width, text_width); SDL_Surface* text; text = TTF_RenderUTF8_Blended(font.large, display_name, COLOR_WHITE); GFX_blitPill( ASSET_BLACK_PILL, screen, - &(SDL_Rect){SCALE1(PADDING), SCALE1(PADDING), max_width, SCALE1(PILL_SIZE)}); + &(SDL_Rect){DP(ui.padding), DP(ui.padding), max_width, DP(ui.pill_height)}); SDL_BlitSurface( - text, &(SDL_Rect){0, 0, max_width - SCALE1(BUTTON_PADDING * 2), text->h}, screen, - &(SDL_Rect){SCALE1(PADDING + BUTTON_PADDING), SCALE1(PADDING + 4)}); + 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)}); SDL_FreeSurface(text); if (show_setting && !GetHDMI()) @@ -5964,7 +5964,7 @@ static void Menu_loop(void) { // list oy = - (((DEVICE_HEIGHT / FIXED_SCALE) - PADDING * 2) - (MENU_ITEM_COUNT * PILL_SIZE)) / 2; + (((DEVICE_HEIGHT / FIXED_SCALE) - ui.padding * 2) - (MENU_ITEM_COUNT * ui.pill_height)) / 2; for (int i = 0; i < MENU_ITEM_COUNT; i++) { char* item = menu.items[i]; SDL_Color text_color = COLOR_WHITE; @@ -5973,40 +5973,40 @@ static void Menu_loop(void) { // disc change if (menu.total_discs > 1 && i == ITEM_CONT) { GFX_blitPill(ASSET_DARK_GRAY_PILL, screen, - &(SDL_Rect){SCALE1(PADDING), SCALE1(oy + PADDING), - screen->w - SCALE1(PADDING * 2), - SCALE1(PILL_SIZE)}); + &(SDL_Rect){DP(ui.padding), DP(oy + ui.padding), + screen->w - DP(ui.padding * 2), + DP(ui.pill_height)}); text = TTF_RenderUTF8_Blended(font.large, disc_name, COLOR_WHITE); SDL_BlitSurface( text, NULL, screen, - &(SDL_Rect){screen->w - SCALE1(PADDING + BUTTON_PADDING) - text->w, - SCALE1(oy + PADDING + 4)}); + &(SDL_Rect){screen->w - DP(ui.padding + ui.button_padding) - text->w, + DP(oy + ui.padding + ui.text_baseline)}); SDL_FreeSurface(text); } TTF_SizeUTF8(font.large, item, &ow, NULL); - ow += SCALE1(BUTTON_PADDING * 2); + ow += DP(ui.button_padding * 2); // pill GFX_blitPill(ASSET_WHITE_PILL, screen, - &(SDL_Rect){SCALE1(PADDING), - SCALE1(oy + PADDING + (i * PILL_SIZE)), ow, - SCALE1(PILL_SIZE)}); + &(SDL_Rect){DP(ui.padding), + DP(oy + ui.padding + (i * ui.pill_height)), ow, + DP(ui.pill_height)}); text_color = COLOR_BLACK; } else { // shadow text = TTF_RenderUTF8_Blended(font.large, item, COLOR_BLACK); SDL_BlitSurface(text, NULL, screen, - &(SDL_Rect){SCALE1(2 + PADDING + BUTTON_PADDING), - SCALE1(1 + PADDING + oy + (i * PILL_SIZE) + 4)}); + &(SDL_Rect){DP(2 + ui.padding + ui.button_padding), + DP(1 + ui.padding + oy + (i * ui.pill_height) + ui.text_baseline)}); SDL_FreeSurface(text); } // text text = TTF_RenderUTF8_Blended(font.large, item, text_color); SDL_BlitSurface(text, NULL, screen, - &(SDL_Rect){SCALE1(PADDING + BUTTON_PADDING), - SCALE1(oy + PADDING + (i * PILL_SIZE) + 4)}); + &(SDL_Rect){DP(ui.padding + ui.button_padding), + DP(oy + ui.padding + (i * ui.pill_height) + ui.text_baseline)}); SDL_FreeSurface(text); } @@ -6017,15 +6017,15 @@ static void Menu_loop(void) { // unscaled int hw = DEVICE_WIDTH / 2; int hh = DEVICE_HEIGHT / 2; - int pw = hw + SCALE1(WINDOW_RADIUS * 2); - int ph = hh + SCALE1(WINDOW_RADIUS * 2 + PAGINATION_HEIGHT + WINDOW_RADIUS); - ox = DEVICE_WIDTH - pw - SCALE1(PADDING); + 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); oy = (DEVICE_HEIGHT - ph) / 2; // window GFX_blitRect(ASSET_STATE_BG, screen, &(SDL_Rect){ox, oy, pw, ph}); - ox += SCALE1(WINDOW_RADIUS); - oy += SCALE1(WINDOW_RADIUS); + ox += DP(WINDOW_RADIUS); + oy += DP(WINDOW_RADIUS); if (menu.preview_exists) { // has save, has preview // lotta memory churn here @@ -6050,15 +6050,15 @@ static void Menu_loop(void) { } // pagination - ox += (pw - SCALE1(15 * MENU_SLOT_COUNT)) / 2; - oy += hh + SCALE1(WINDOW_RADIUS); + ox += (pw - DP(15 * MENU_SLOT_COUNT)) / 2; + oy += hh + DP(WINDOW_RADIUS); for (int i = 0; i < MENU_SLOT_COUNT; i++) { if (i == menu.slot) GFX_blitAsset(ASSET_PAGE, NULL, screen, - &(SDL_Rect){ox + SCALE1(i * 15), oy}); + &(SDL_Rect){ox + DP(i * 15), oy}); else GFX_blitAsset(ASSET_DOT, NULL, screen, - &(SDL_Rect){ox + SCALE1(i * 15) + 4, oy + SCALE1(2)}); + &(SDL_Rect){ox + DP(i * 15) + 4, oy + DP(2)}); } } diff --git a/workspace/all/minput/minput.c b/workspace/all/minput/minput.c index 303ce1a0..48d3b524 100644 --- a/workspace/all/minput/minput.c +++ b/workspace/all/minput/minput.c @@ -43,10 +43,10 @@ static int getButtonWidth(char* label) { int w = 0; if (strlen(label) <= 2) - w = SCALE1(BUTTON_SIZE); + w = DP(ui.button_size); else { SDL_Surface* text = TTF_RenderUTF8_Blended(font.tiny, label, COLOR_BUTTON_TEXT); - w = SCALE1(BUTTON_SIZE) + text->w; + w = DP(ui.button_size) + text->w; SDL_FreeSurface(text); } return w; @@ -79,17 +79,17 @@ static void blitButton(char* label, SDL_Surface* dst, int pressed, int x, int y, TTF_RenderUTF8_Blended(len == 2 ? font.small : font.medium, label, COLOR_BUTTON_TEXT); GFX_blitAsset(pressed ? ASSET_BUTTON : ASSET_HOLE, NULL, dst, &point); SDL_BlitSurface(text, NULL, dst, - &(SDL_Rect){point.x + (SCALE1(BUTTON_SIZE) - text->w) / 2, - point.y + (SCALE1(BUTTON_SIZE) - text->h) / 2}); + &(SDL_Rect){point.x + (DP(ui.button_size) - text->w) / 2, + point.y + (DP(ui.button_size) - text->h) / 2}); } else { // Long labels: use pill-shaped button with smaller font text = TTF_RenderUTF8_Blended(font.tiny, label, COLOR_BUTTON_TEXT); - w = w ? w : SCALE1(BUTTON_SIZE) / 2 + text->w; + w = w ? w : DP(ui.button_size) / 2 + text->w; GFX_blitPill(pressed ? ASSET_BUTTON : ASSET_HOLE, dst, - &(SDL_Rect){point.x, point.y, w, SCALE1(BUTTON_SIZE)}); + &(SDL_Rect){point.x, point.y, w, DP(ui.button_size)}); SDL_BlitSurface(text, NULL, dst, &(SDL_Rect){point.x + (w - text->w) / 2, - point.y + (SCALE1(BUTTON_SIZE) - text->h) / 2, text->w, + point.y + (DP(ui.button_size) - text->h) / 2, text->w, text->h}); } @@ -128,9 +128,9 @@ 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 = SCALE1(PADDING); + int oy = DP(ui.padding); if (!has_L3 && !has_R3) - oy += SCALE1(PILL_SIZE); + oy += DP(ui.pill_height); int quit = 0; int dirty = 1; @@ -155,26 +155,26 @@ int main(int argc, char* argv[]) { // Left shoulder buttons (L1, L2) /////////////////////////////// { - int x = SCALE1(BUTTON_MARGIN + PADDING); + int x = DP(ui.button_margin + PADDING); int y = oy; int w = 0; int ox = 0; - w = getButtonWidth("L1") + SCALE1(BUTTON_MARGIN) * 2; + w = getButtonWidth("L1") + DP(ui.button_margin) * 2; ox = w; if (has_L2) - w += getButtonWidth("L2") + SCALE1(BUTTON_MARGIN); + w += getButtonWidth("L2") + DP(ui.button_margin); // Offset if L2 not present to maintain visual balance if (!has_L2) - x += SCALE1(PILL_SIZE); + x += DP(ui.pill_height); GFX_blitPill(ASSET_DARK_GRAY_PILL, screen, &(SDL_Rect){x, y, w}); - blitButton("L1", screen, PAD_isPressed(BTN_L1), x + SCALE1(BUTTON_MARGIN), - y + SCALE1(BUTTON_MARGIN), 0); + blitButton("L1", screen, PAD_isPressed(BTN_L1), x + DP(ui.button_margin), + y + DP(ui.button_margin), 0); if (has_L2) blitButton("L2", screen, PAD_isPressed(BTN_L2), x + ox, - y + SCALE1(BUTTON_MARGIN), 0); + y + DP(ui.button_margin), 0); } /////////////////////////////// @@ -186,60 +186,60 @@ int main(int argc, char* argv[]) { int w = 0; int ox = 0; - w = getButtonWidth("R1") + SCALE1(BUTTON_MARGIN) * 2; + w = getButtonWidth("R1") + DP(ui.button_margin) * 2; ox = w; if (has_R2) - w += getButtonWidth("R2") + SCALE1(BUTTON_MARGIN); + w += getButtonWidth("R2") + DP(ui.button_margin); // Right-align the button group - x = FIXED_WIDTH - w - SCALE1(BUTTON_MARGIN + PADDING); + x = FIXED_WIDTH - w - DP(ui.button_margin + PADDING); if (!has_R2) - x -= SCALE1(PILL_SIZE); + x -= DP(ui.pill_height); GFX_blitPill(ASSET_DARK_GRAY_PILL, screen, &(SDL_Rect){x, y, w}); // R2 comes first visually (left position), then R1 blitButton(has_R2 ? "R2" : "R1", screen, PAD_isPressed(has_R2 ? BTN_R2 : BTN_R1), - x + SCALE1(BUTTON_MARGIN), y + SCALE1(BUTTON_MARGIN), 0); + x + DP(ui.button_margin), y + DP(ui.button_margin), 0); if (has_R2) blitButton("R1", screen, PAD_isPressed(BTN_R1), x + ox, - y + SCALE1(BUTTON_MARGIN), 0); + y + DP(ui.button_margin), 0); } /////////////////////////////// // D-pad (Up, Down, Left, Right) /////////////////////////////// { - int x = SCALE1(PADDING + PILL_SIZE); - int y = oy + SCALE1(PILL_SIZE * 2); - int o = SCALE1(BUTTON_MARGIN); + int x = DP(ui.padding + PILL_SIZE); + int y = oy + DP(ui.pill_height * 2); + int o = DP(ui.button_margin); // Vertical bar connecting Up and Down buttons SDL_FillRect(screen, - &(SDL_Rect){x, y + SCALE1(PILL_SIZE / 2), SCALE1(PILL_SIZE), - SCALE1(PILL_SIZE * 2)}, + &(SDL_Rect){x, y + DP(ui.pill_height / 2), DP(ui.pill_height), + DP(ui.pill_height * 2)}, RGB_DARK_GRAY); GFX_blitPill(ASSET_DARK_GRAY_PILL, screen, &(SDL_Rect){x, y, 0}); blitButton("U", screen, PAD_isPressed(BTN_DPAD_UP), x + o, y + o, 0); - y += SCALE1(PILL_SIZE * 2); + y += DP(ui.pill_height * 2); GFX_blitPill(ASSET_DARK_GRAY_PILL, screen, &(SDL_Rect){x, y, 0}); blitButton("D", screen, PAD_isPressed(BTN_DPAD_DOWN), x + o, y + o, 0); - x -= SCALE1(PILL_SIZE); - y -= SCALE1(PILL_SIZE); + x -= DP(ui.pill_height); + y -= DP(ui.pill_height); // Horizontal bar connecting Left and Right buttons SDL_FillRect(screen, - &(SDL_Rect){x + SCALE1(PILL_SIZE / 2), y, SCALE1(PILL_SIZE * 2), - SCALE1(PILL_SIZE)}, + &(SDL_Rect){x + DP(ui.pill_height / 2), y, DP(ui.pill_height * 2), + DP(ui.pill_height)}, RGB_DARK_GRAY); GFX_blitPill(ASSET_DARK_GRAY_PILL, screen, &(SDL_Rect){x, y, 0}); blitButton("L", screen, PAD_isPressed(BTN_DPAD_LEFT), x + o, y + o, 0); - x += SCALE1(PILL_SIZE * 2); + x += DP(ui.pill_height * 2); GFX_blitPill(ASSET_DARK_GRAY_PILL, screen, &(SDL_Rect){x, y, 0}); blitButton("R", screen, PAD_isPressed(BTN_DPAD_RIGHT), x + o, y + o, 0); } @@ -248,28 +248,28 @@ int main(int argc, char* argv[]) { // Face buttons (A, B, X, Y) /////////////////////////////// { - int x = FIXED_WIDTH - SCALE1(PADDING + PILL_SIZE * 3) + SCALE1(PILL_SIZE); - int y = oy + SCALE1(PILL_SIZE * 2); - int o = SCALE1(BUTTON_MARGIN); + int x = FIXED_WIDTH - DP(ui.padding + PILL_SIZE * 3) + DP(ui.pill_height); + int y = oy + DP(ui.pill_height * 2); + int o = DP(ui.button_margin); // X (top) GFX_blitPill(ASSET_DARK_GRAY_PILL, screen, &(SDL_Rect){x, y, 0}); blitButton("X", screen, PAD_isPressed(BTN_X), x + o, y + o, 0); // B (bottom) - y += SCALE1(PILL_SIZE * 2); + y += DP(ui.pill_height * 2); GFX_blitPill(ASSET_DARK_GRAY_PILL, screen, &(SDL_Rect){x, y, 0}); blitButton("B", screen, PAD_isPressed(BTN_B), x + o, y + o, 0); - x -= SCALE1(PILL_SIZE); - y -= SCALE1(PILL_SIZE); + x -= DP(ui.pill_height); + y -= DP(ui.pill_height); // Y (left) GFX_blitPill(ASSET_DARK_GRAY_PILL, screen, &(SDL_Rect){x, y, 0}); blitButton("Y", screen, PAD_isPressed(BTN_Y), x + o, y + o, 0); // A (right) - x += SCALE1(PILL_SIZE * 2); + x += DP(ui.pill_height * 2); GFX_blitPill(ASSET_DARK_GRAY_PILL, screen, &(SDL_Rect){x, y, 0}); blitButton("A", screen, PAD_isPressed(BTN_A), x + o, y + o, 0); } @@ -278,15 +278,15 @@ int main(int argc, char* argv[]) { // Volume buttons (if available) /////////////////////////////// if (has_volume) { - int x = (FIXED_WIDTH - SCALE1(99)) / 2; - int y = oy + SCALE1(PILL_SIZE); - int w = SCALE1(42); + int x = (FIXED_WIDTH - DP(99)) / 2; + int y = oy + DP(ui.pill_height); + int w = DP(42); - GFX_blitPill(ASSET_DARK_GRAY_PILL, screen, &(SDL_Rect){x, y, SCALE1(98)}); - x += SCALE1(BUTTON_MARGIN); - y += SCALE1(BUTTON_MARGIN); + GFX_blitPill(ASSET_DARK_GRAY_PILL, screen, &(SDL_Rect){x, y, DP(98)}); + x += DP(ui.button_margin); + y += DP(ui.button_margin); blitButton("VOL. -", screen, PAD_isPressed(BTN_MINUS), x, y, w); - x += w + SCALE1(BUTTON_MARGIN); + x += w + DP(ui.button_margin); blitButton("VOL. +", screen, PAD_isPressed(BTN_PLUS), x, y, w); } @@ -297,16 +297,16 @@ int main(int argc, char* argv[]) { int bw = 42; int pw = has_both ? (bw * 2 + BUTTON_MARGIN * 3) : (bw + BUTTON_MARGIN * 2); - int x = (FIXED_WIDTH - SCALE1(pw)) / 2; - int y = oy + SCALE1(PILL_SIZE * 3); - int w = SCALE1(bw); + int x = (FIXED_WIDTH - DP(pw)) / 2; + int y = oy + DP(ui.pill_height * 3); + int w = DP(bw); - GFX_blitPill(ASSET_DARK_GRAY_PILL, screen, &(SDL_Rect){x, y, SCALE1(pw)}); - x += SCALE1(BUTTON_MARGIN); - y += SCALE1(BUTTON_MARGIN); + GFX_blitPill(ASSET_DARK_GRAY_PILL, screen, &(SDL_Rect){x, y, DP(pw)}); + x += DP(ui.button_margin); + y += DP(ui.button_margin); if (has_menu) { blitButton("MENU", screen, PAD_isPressed(BTN_MENU), x, y, w); - x += w + SCALE1(BUTTON_MARGIN); + x += w + DP(ui.button_margin); } if (has_power) { blitButton("POWER", screen, PAD_isPressed(BTN_POWER), x, y, w); @@ -317,22 +317,22 @@ int main(int argc, char* argv[]) { // Meta buttons (Select, Start) with quit hint /////////////////////////////// { - int x = (FIXED_WIDTH - SCALE1(99)) / 2; - int y = oy + SCALE1(PILL_SIZE * 5); - int w = SCALE1(42); + int x = (FIXED_WIDTH - DP(99)) / 2; + int y = oy + DP(ui.pill_height * 5); + int w = DP(42); - GFX_blitPill(ASSET_DARK_GRAY_PILL, screen, &(SDL_Rect){x, y, SCALE1(130)}); - x += SCALE1(BUTTON_MARGIN); - y += SCALE1(BUTTON_MARGIN); + GFX_blitPill(ASSET_DARK_GRAY_PILL, screen, &(SDL_Rect){x, y, DP(130)}); + x += DP(ui.button_margin); + y += DP(ui.button_margin); blitButton("SELECT", screen, PAD_isPressed(BTN_SELECT), x, y, w); - x += w + SCALE1(BUTTON_MARGIN); + x += w + DP(ui.button_margin); blitButton("START", screen, PAD_isPressed(BTN_START), x, y, w); - x += w + SCALE1(BUTTON_MARGIN); + x += w + DP(ui.button_margin); // Display "QUIT" hint - press both together to exit SDL_Surface* text = TTF_RenderUTF8_Blended(font.tiny, "QUIT", COLOR_LIGHT_TEXT); SDL_BlitSurface(text, NULL, screen, - &(SDL_Rect){x, y + (SCALE1(BUTTON_SIZE) - text->h) / 2}); + &(SDL_Rect){x, y + (DP(ui.button_size) - text->h) / 2}); SDL_FreeSurface(text); } @@ -340,18 +340,18 @@ int main(int argc, char* argv[]) { // Analog stick buttons (if available) /////////////////////////////// if (has_L3) { - int x = SCALE1(PADDING + PILL_SIZE); - int y = oy + SCALE1(PILL_SIZE * 6); - int o = SCALE1(BUTTON_MARGIN); + int x = DP(ui.padding + PILL_SIZE); + int y = oy + DP(ui.pill_height * 6); + int o = DP(ui.button_margin); GFX_blitPill(ASSET_DARK_GRAY_PILL, screen, &(SDL_Rect){x, y, 0}); blitButton("L3", screen, PAD_isPressed(BTN_L3), x + o, y + o, 0); } if (has_R3) { - int x = FIXED_WIDTH - SCALE1(PADDING + PILL_SIZE * 3) + SCALE1(PILL_SIZE); - int y = oy + SCALE1(PILL_SIZE * 6); - int o = SCALE1(BUTTON_MARGIN); + int x = FIXED_WIDTH - DP(ui.padding + PILL_SIZE * 3) + DP(ui.pill_height); + int y = oy + DP(ui.pill_height * 6); + int o = DP(ui.button_margin); GFX_blitPill(ASSET_DARK_GRAY_PILL, screen, &(SDL_Rect){x, y, 0}); blitButton("R3", screen, PAD_isPressed(BTN_R3), x + o, y + o, 0); diff --git a/workspace/all/minui/minui.c b/workspace/all/minui/minui.c index 3fed510b..fa242200 100644 --- a/workspace/all/minui/minui.c +++ b/workspace/all/minui/minui.c @@ -1740,7 +1740,7 @@ static void openDirectory(char* path, int auto_launch) { top->start = start; top->end = end ? end - : ((top->entries->count < MAIN_ROW_COUNT) ? top->entries->count : MAIN_ROW_COUNT); + : ((top->entries->count < ui.row_count) ? top->entries->count : ui.row_count); Array_push(stack, top); } } @@ -1886,10 +1886,10 @@ static void loadLast(void) { top->selected = i; if (i >= top->end) { top->start = i; - top->end = top->start + MAIN_ROW_COUNT; + top->end = top->start + ui.row_count; if (top->end > top->entries->count) { top->end = top->entries->count; - top->start = top->end - MAIN_ROW_COUNT; + top->start = top->end - ui.row_count; } } if (last->count == 0 && !exactMatch(entry->path, FAUX_RECENT_PATH) && @@ -2055,7 +2055,7 @@ int main(int argc, char* argv[]) { selected -= 1; if (selected < 0) { selected = total - 1; - int start = total - MAIN_ROW_COUNT; + int start = total - ui.row_count; top->start = (start < 0) ? 0 : start; top->end = total; } else if (selected < top->start) { @@ -2071,7 +2071,7 @@ int main(int argc, char* argv[]) { if (selected >= total) { selected = 0; top->start = 0; - top->end = (total < MAIN_ROW_COUNT) ? total : MAIN_ROW_COUNT; + top->end = (total < ui.row_count) ? total : ui.row_count; } else if (selected >= top->end) { top->start += 1; top->end += 1; @@ -2079,29 +2079,29 @@ int main(int argc, char* argv[]) { } } if (PAD_justRepeated(BTN_LEFT)) { - selected -= MAIN_ROW_COUNT; + selected -= ui.row_count; if (selected < 0) { selected = 0; top->start = 0; - top->end = (total < MAIN_ROW_COUNT) ? total : MAIN_ROW_COUNT; + top->end = (total < ui.row_count) ? total : ui.row_count; } else if (selected < top->start) { - top->start -= MAIN_ROW_COUNT; + top->start -= ui.row_count; if (top->start < 0) top->start = 0; - top->end = top->start + MAIN_ROW_COUNT; + top->end = top->start + ui.row_count; } } else if (PAD_justRepeated(BTN_RIGHT)) { - selected += MAIN_ROW_COUNT; + selected += ui.row_count; if (selected >= total) { selected = total - 1; - int start = total - MAIN_ROW_COUNT; + int start = total - ui.row_count; top->start = (start < 0) ? 0 : start; top->end = total; } else if (selected >= top->end) { - top->end += MAIN_ROW_COUNT; + top->end += ui.row_count; if (top->end > total) top->end = total; - top->start = top->end - MAIN_ROW_COUNT; + top->start = top->end - ui.row_count; } } } @@ -2113,12 +2113,12 @@ int main(int argc, char* argv[]) { int i = entry->alpha - 1; if (i >= 0) { selected = top->alphas->items[i]; - if (total > MAIN_ROW_COUNT) { + if (total > ui.row_count) { top->start = selected; - top->end = top->start + MAIN_ROW_COUNT; + top->end = top->start + ui.row_count; if (top->end > total) top->end = total; - top->start = top->end - MAIN_ROW_COUNT; + top->start = top->end - ui.row_count; } } } else if (PAD_justRepeated(BTN_R1) && !PAD_isPressed(BTN_L1) && @@ -2127,12 +2127,12 @@ int main(int argc, char* argv[]) { int i = entry->alpha + 1; if (i < top->alphas->count) { selected = top->alphas->items[i]; - if (total > MAIN_ROW_COUNT) { + if (total > ui.row_count) { top->start = selected; - top->end = top->start + MAIN_ROW_COUNT; + top->end = top->start + ui.row_count; if (top->end > total) top->end = total; - top->start = top->end - MAIN_ROW_COUNT; + top->start = top->end - ui.row_count; } } } @@ -2254,22 +2254,22 @@ int main(int argc, char* argv[]) { r_width = val_txt->w; #define VERSION_LINE_HEIGHT 24 - int x = l_width + SCALE1(8); + int x = l_width + DP(8); int w = x + r_width; - int h = SCALE1(VERSION_LINE_HEIGHT * 4); + int h = DP(VERSION_LINE_HEIGHT * 4); version = SDL_CreateRGBSurface(0, w, h, 16, 0, 0, 0, 0); SDL_BlitSurface(release_txt, NULL, version, &(SDL_Rect){0, 0, 0, 0}); SDL_BlitSurface(version_txt, NULL, version, &(SDL_Rect){x, 0, 0, 0}); SDL_BlitSurface(commit_txt, NULL, version, - &(SDL_Rect){0, SCALE1(VERSION_LINE_HEIGHT), 0, 0}); + &(SDL_Rect){0, DP(VERSION_LINE_HEIGHT), 0, 0}); SDL_BlitSurface(hash_txt, NULL, version, - &(SDL_Rect){x, SCALE1(VERSION_LINE_HEIGHT), 0, 0}); + &(SDL_Rect){x, DP(VERSION_LINE_HEIGHT), 0, 0}); SDL_BlitSurface(key_txt, NULL, version, - &(SDL_Rect){0, SCALE1(VERSION_LINE_HEIGHT * 3), 0, 0}); + &(SDL_Rect){0, DP(VERSION_LINE_HEIGHT * 3), 0, 0}); SDL_BlitSurface(val_txt, NULL, version, - &(SDL_Rect){x, SCALE1(VERSION_LINE_HEIGHT * 3), 0, 0}); + &(SDL_Rect){x, DP(VERSION_LINE_HEIGHT * 3), 0, 0}); SDL_FreeSurface(release_txt); SDL_FreeSurface(version_txt); @@ -2300,7 +2300,7 @@ int main(int argc, char* argv[]) { char* entry_name = entry->name; char* entry_unique = entry->unique; int available_width = - (had_thumb && j != selected_row ? ox : screen->w) - SCALE1(PADDING * 2); + (had_thumb && j != selected_row ? ox : screen->w) - DP(ui.padding * 2); if (i == top->start && !(had_thumb && j != selected_row)) available_width -= ow; // @@ -2311,40 +2311,40 @@ int main(int argc, char* argv[]) { char display_name[256]; int text_width = GFX_truncateText( font.large, entry_unique ? entry_unique : entry_name, display_name, - available_width, SCALE1(BUTTON_PADDING * 2)); + available_width, DP(ui.button_padding * 2)); int max_width = MIN(available_width, text_width); if (j == selected_row) { GFX_blitPill(ASSET_WHITE_PILL, screen, - &(SDL_Rect){SCALE1(PADDING), - SCALE1(PADDING + (j * PILL_SIZE)), max_width, - SCALE1(PILL_SIZE)}); + &(SDL_Rect){DP(ui.padding), + DP(ui.padding + (j * ui.pill_height)), max_width, + DP(ui.pill_height)}); text_color = COLOR_BLACK; } else if (entry->unique) { trimSortingMeta(&entry_unique); char unique_name[256]; GFX_truncateText(font.large, entry_unique, unique_name, available_width, - SCALE1(BUTTON_PADDING * 2)); + DP(ui.button_padding * 2)); SDL_Surface* text = TTF_RenderUTF8_Blended(font.large, unique_name, COLOR_DARK_TEXT); SDL_BlitSurface( text, - &(SDL_Rect){0, 0, max_width - SCALE1(BUTTON_PADDING * 2), text->h}, + &(SDL_Rect){0, 0, max_width - DP(ui.button_padding * 2), text->h}, screen, - &(SDL_Rect){SCALE1(PADDING + BUTTON_PADDING), - SCALE1(PADDING + (j * PILL_SIZE) + 4), 0, 0}); + &(SDL_Rect){DP(ui.padding + ui.button_padding), + DP(ui.padding + (j * ui.pill_height) + ui.text_baseline), 0, 0}); GFX_truncateText(font.large, entry_name, display_name, available_width, - SCALE1(BUTTON_PADDING * 2)); + DP(ui.button_padding * 2)); } SDL_Surface* text = TTF_RenderUTF8_Blended(font.large, display_name, text_color); SDL_BlitSurface( text, - &(SDL_Rect){0, 0, max_width - SCALE1(BUTTON_PADDING * 2), text->h}, + &(SDL_Rect){0, 0, max_width - DP(ui.button_padding * 2), text->h}, screen, - &(SDL_Rect){SCALE1(PADDING + BUTTON_PADDING), - SCALE1(PADDING + (j * PILL_SIZE) + 4), 0, 0}); + &(SDL_Rect){DP(ui.padding + ui.button_padding), + DP(ui.padding + (j * ui.pill_height) + ui.text_baseline), 0, 0}); SDL_FreeSurface(text); } } else { diff --git a/workspace/all/say/say.c b/workspace/all/say/say.c index 09167228..40cba823 100644 --- a/workspace/all/say/say.c +++ b/workspace/all/say/say.c @@ -61,7 +61,7 @@ int main(int argc, const char* argv[]) { // Display message centered, leaving room for button at bottom GFX_blitMessage( font.large, msg, screen, - &(SDL_Rect){0, 0, screen->w, screen->h - SCALE1(PADDING + PILL_SIZE + PADDING)}); + &(SDL_Rect){0, 0, screen->w, screen->h - DP(ui.padding + ui.pill_height + ui.padding)}); GFX_blitButtonGroup((char*[]){"A", "OKAY", NULL}, 1, screen, 1); GFX_flip(screen); diff --git a/workspace/desktop/platform/platform.h b/workspace/desktop/platform/platform.h index 2d0356eb..9e77126e 100644 --- a/workspace/desktop/platform/platform.h +++ b/workspace/desktop/platform/platform.h @@ -129,6 +129,8 @@ // Display Specifications /////////////////////////////// +// Desktop uses a virtual 4" screen at VGA resolution to get dp_scale ≈ 2.0 +#define SCREEN_DIAGONAL 4.0f // Virtual screen diagonal for consistent scaling #define FIXED_SCALE 2 // 2x scaling factor for UI #define FIXED_WIDTH 640 // Screen width in pixels #define FIXED_HEIGHT 480 // Screen height in pixels (VGA) @@ -148,8 +150,7 @@ // UI Layout Configuration /////////////////////////////// -#define MAIN_ROW_COUNT 6 // Number of rows visible in menu -#define PADDING 10 // Padding for UI elements in pixels +// MAIN_ROW_COUNT and PADDING are now calculated automatically via DP system /////////////////////////////// // Platform-Specific Paths and Settings diff --git a/workspace/m17/platform/platform.h b/workspace/m17/platform/platform.h index e35989d7..301df9d9 100644 --- a/workspace/m17/platform/platform.h +++ b/workspace/m17/platform/platform.h @@ -131,6 +131,7 @@ // Display Specifications /////////////////////////////// +#define SCREEN_DIAGONAL 7.0f // Physical screen diagonal in inches (estimated) #define FIXED_SCALE 1 // No scaling factor needed #define FIXED_WIDTH 480 // Screen width in pixels #define FIXED_HEIGHT 273 // Screen height in pixels (16:9 widescreen) @@ -147,7 +148,7 @@ #define MUTE_VOLUME_RAW 0 // Raw value for muted volume #define HAS_NEON // May have NEON SIMD support (uncertain) -#define MAIN_ROW_COUNT 7 // Number of rows visible in menu +// MAIN_ROW_COUNT is now calculated automatically via DP system /////////////////////////////// diff --git a/workspace/magicmini/platform/platform.h b/workspace/magicmini/platform/platform.h index 4ef0894b..b42b36ba 100644 --- a/workspace/magicmini/platform/platform.h +++ b/workspace/magicmini/platform/platform.h @@ -128,6 +128,7 @@ // Display Specifications /////////////////////////////// +#define SCREEN_DIAGONAL 2.8f // Physical screen diagonal in inches #define FIXED_SCALE 2 // 2x scaling factor for UI #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 27db4a0d..57ca242c 100644 --- a/workspace/miyoomini/platform/platform.h +++ b/workspace/miyoomini/platform/platform.h @@ -139,6 +139,7 @@ extern int is_560p; // Set to 1 for 560p screen variant // Runtime-configurable for 560p variant /////////////////////////////// +#define SCREEN_DIAGONAL 2.8f // Physical screen diagonal in inches #define FIXED_SCALE 2 // 2x scaling factor for UI #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) @@ -152,8 +153,7 @@ extern int is_560p; // Set to 1 for 560p screen variant // Adjusted for 560p variant /////////////////////////////// -#define MAIN_ROW_COUNT (is_560p ? 8 : 6) // Number of rows visible: 8 (560p) or 6 (standard) -#define PADDING (is_560p ? 5 : 10) // UI padding: 5px (560p) or 10px (standard) +// MAIN_ROW_COUNT and PADDING are now calculated automatically via DP system #define PAGE_SCALE (is_560p ? 2 : 3) // Memory scaling: tighter on 560p to reduce usage /////////////////////////////// diff --git a/workspace/my282/platform/platform.h b/workspace/my282/platform/platform.h index 1d51c2ca..0447bb22 100644 --- a/workspace/my282/platform/platform.h +++ b/workspace/my282/platform/platform.h @@ -127,6 +127,7 @@ // Display Specifications /////////////////////////////// +#define SCREEN_DIAGONAL 2.8f // Physical screen diagonal in inches (estimated) #define FIXED_SCALE 2 // 2x scaling factor for UI #define FIXED_WIDTH 640 // Screen width in pixels #define FIXED_HEIGHT 480 // Screen height in pixels (VGA) diff --git a/workspace/my355/platform/platform.h b/workspace/my355/platform/platform.h index 40dc79da..a9b2b0f0 100644 --- a/workspace/my355/platform/platform.h +++ b/workspace/my355/platform/platform.h @@ -146,6 +146,7 @@ extern int on_hdmi; // Set to 1 when HDMI output is active // Display Specifications /////////////////////////////// +#define SCREEN_DIAGONAL 3.5f // Physical screen diagonal in inches (Miyoo Flip) #define FIXED_SCALE 2 // 2x scaling factor for UI #define FIXED_WIDTH 640 // Screen width in pixels #define FIXED_HEIGHT 480 // Screen height in pixels (VGA) @@ -171,8 +172,7 @@ extern int on_hdmi; // Set to 1 when HDMI output is active // Adjusted for HDMI output /////////////////////////////// -#define MAIN_ROW_COUNT (on_hdmi ? 8 : 6) // Number of rows visible: 8 (HDMI) or 6 (LCD) -#define PADDING (on_hdmi ? 40 : 10) // UI padding: 40px (HDMI) or 10px (LCD) +// MAIN_ROW_COUNT and PADDING are now calculated automatically via DP system /////////////////////////////// // Platform-Specific Paths and Settings diff --git a/workspace/rg35xx/platform/platform.h b/workspace/rg35xx/platform/platform.h index 56acd691..99675ccc 100644 --- a/workspace/rg35xx/platform/platform.h +++ b/workspace/rg35xx/platform/platform.h @@ -130,6 +130,7 @@ // Display Specifications /////////////////////////////// +#define SCREEN_DIAGONAL 3.5f // Physical screen diagonal in inches #define FIXED_SCALE 2 // 2x scaling factor for UI #define FIXED_WIDTH 640 // Screen width in pixels #define FIXED_HEIGHT 480 // Screen height in pixels (VGA) diff --git a/workspace/rg35xxplus/platform/platform.h b/workspace/rg35xxplus/platform/platform.h index 5bf34a1d..c83fa973 100644 --- a/workspace/rg35xxplus/platform/platform.h +++ b/workspace/rg35xxplus/platform/platform.h @@ -141,6 +141,7 @@ extern int on_hdmi; // Set to 1 when HDMI output is active // Runtime-configurable for device variants /////////////////////////////// +#define SCREEN_DIAGONAL (is_cubexx ? 3.95f : (is_rg34xx ? 3.4f : 3.5f)) // Diagonal: 3.95" (Cube) / 3.4" (34XX) / 3.5" (Plus) #define FIXED_SCALE 2 // 2x scaling factor for UI #define FIXED_WIDTH (is_cubexx ? 720 : (is_rg34xx ? 720 : 640)) // Width: 720 (H/SP) or 640 (Plus) #define FIXED_HEIGHT (is_cubexx ? 720 : 480) // Height: 720 (H) or 480 (Plus/SP) @@ -166,8 +167,7 @@ extern int on_hdmi; // Set to 1 when HDMI output is active // Adjusted for device variant and HDMI /////////////////////////////// -#define MAIN_ROW_COUNT (is_cubexx || on_hdmi ? 8 : 6) // Rows: 8 (H/HDMI) or 6 (Plus/SP) -#define PADDING (is_cubexx || on_hdmi ? 40 : 10) // Padding: 40px (H/HDMI) or 10px (Plus/SP) +// MAIN_ROW_COUNT and PADDING are now calculated automatically via DP system /////////////////////////////// // Platform-Specific Paths and Settings diff --git a/workspace/rgb30/platform/platform.h b/workspace/rgb30/platform/platform.h index 0c7a1764..70e7120a 100644 --- a/workspace/rgb30/platform/platform.h +++ b/workspace/rgb30/platform/platform.h @@ -133,6 +133,7 @@ // Display Specifications /////////////////////////////// +#define SCREEN_DIAGONAL 4.0f // Physical screen diagonal in inches #define FIXED_SCALE 2 // 2x scaling factor for UI #define FIXED_WIDTH 720 // Screen width in pixels (square display) #define FIXED_HEIGHT 720 // Screen height in pixels (1:1 aspect ratio) @@ -156,8 +157,7 @@ // Larger values for square display /////////////////////////////// -#define MAIN_ROW_COUNT 8 // Number of rows visible in menu -#define PADDING 40 // Padding for UI elements in pixels +// MAIN_ROW_COUNT and PADDING are now calculated automatically via DP system /////////////////////////////// // Platform-Specific Paths and Settings diff --git a/workspace/tg5040/platform/platform.h b/workspace/tg5040/platform/platform.h index 752e7eb1..365fc068 100644 --- a/workspace/tg5040/platform/platform.h +++ b/workspace/tg5040/platform/platform.h @@ -155,6 +155,7 @@ extern int is_brick; // Set to 1 for Brick variant (1024x768 display) // Runtime-configurable for Brick variant /////////////////////////////// +#define SCREEN_DIAGONAL (is_brick ? 3.2f : 4.95f) // Diagonal: 3.2" (Brick) or 4.95" (Smart Pro) #define FIXED_SCALE (is_brick ? 3 : 2) // Scaling: 3x (Brick) or 2x (standard) #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) @@ -168,8 +169,7 @@ extern int is_brick; // Set to 1 for Brick variant (1024x768 display) // Adjusted for Brick variant /////////////////////////////// -#define MAIN_ROW_COUNT (is_brick ? 7 : 8) // Number of rows: 7 (Brick) or 8 (standard) -#define PADDING (is_brick ? 5 : 40) // UI padding: 5px (Brick) or 40px (standard) +// MAIN_ROW_COUNT and PADDING are now calculated automatically via DP system /////////////////////////////// // Platform-Specific Paths and Settings diff --git a/workspace/trimuismart/platform/platform.h b/workspace/trimuismart/platform/platform.h index 106b0482..572f9291 100644 --- a/workspace/trimuismart/platform/platform.h +++ b/workspace/trimuismart/platform/platform.h @@ -128,6 +128,7 @@ // Display Specifications /////////////////////////////// +#define SCREEN_DIAGONAL 2.4f // Physical screen diagonal in inches #define FIXED_SCALE 1 // No scaling (1:1 pixel mapping) #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 a273abc6..28038e15 100644 --- a/workspace/zero28/platform/platform.h +++ b/workspace/zero28/platform/platform.h @@ -142,6 +142,7 @@ // Display Specifications /////////////////////////////// +#define SCREEN_DIAGONAL 2.8f // Physical screen diagonal in inches #define FIXED_SCALE 2 // 2x scaling factor for UI #define FIXED_WIDTH 640 // Screen width in pixels #define FIXED_HEIGHT 480 // Screen height in pixels (VGA) @@ -154,8 +155,7 @@ // UI Layout Configuration /////////////////////////////// -#define MAIN_ROW_COUNT 6 // Number of rows visible in menu -#define PADDING 10 // Padding for UI elements in pixels +// MAIN_ROW_COUNT and PADDING are now calculated automatically via DP system /////////////////////////////// // Platform-Specific Paths and Settings From 50e4fc0a66a19f981aa2048f5ffc6728f052908b Mon Sep 17 00:00:00 2001 From: Nick Chapman Date: Tue, 25 Nov 2025 21:28:09 -0800 Subject: [PATCH 02/17] Bilinear scaled assets. --- workspace/all/clock/clock.c | 10 +- workspace/all/common/api.c | 310 ++++++++++++++++++----- workspace/all/common/api.h | 8 +- workspace/all/minarch/minarch.c | 50 ++-- workspace/all/minui/minui.c | 19 +- workspace/all/say/say.c | 6 +- workspace/rg35xxplus/platform/platform.h | 4 +- 7 files changed, 300 insertions(+), 107 deletions(-) diff --git a/workspace/all/clock/clock.c b/workspace/all/clock/clock.c index d495742a..3f6a1f1f 100644 --- a/workspace/all/clock/clock.c +++ b/workspace/all/clock/clock.c @@ -75,10 +75,9 @@ int main(int argc, char* argv[]) { digit = TTF_RenderUTF8_Blended(font.large, c, COLOR_WHITE); // Colon sits too low naturally, adjust vertically int y = i == CHAR_COLON ? DP(-1.5) : 0; - SDL_BlitSurface( - digit, NULL, digits, - &(SDL_Rect){(i * DP(DIGIT_WIDTH)) + (DP(DIGIT_WIDTH) - digit->w) / 2, - y + (DP(DIGIT_HEIGHT) - digit->h) / 2}); + SDL_BlitSurface(digit, NULL, digits, + &(SDL_Rect){(i * DP(DIGIT_WIDTH)) + (DP(DIGIT_WIDTH) - digit->w) / 2, + y + (DP(DIGIT_HEIGHT) - digit->h) / 2}); SDL_FreeSurface(digit); i += 1; } @@ -111,8 +110,7 @@ int main(int argc, char* argv[]) { * @return New x position after blitting (x + digit width) */ int blit(int i, int x, int y) { - SDL_BlitSurface(digits, &(SDL_Rect){i * DP(10), 0, DP2(10, 16)}, screen, - &(SDL_Rect){x, y}); + SDL_BlitSurface(digits, &(SDL_Rect){i * DP(10), 0, DP2(10, 16)}, screen, &(SDL_Rect){x, y}); return x + DP(10); } diff --git a/workspace/all/common/api.c b/workspace/all/common/api.c index 27a91dda..43debbea 100644 --- a/workspace/all/common/api.c +++ b/workspace/all/common/api.c @@ -76,26 +76,77 @@ UI_Layout ui = { }; /** - * Initializes the DP scaling system and UI layout. + * Initializes the resolution-independent UI scaling system. * - * Calculates dp_scale from screen PPI using the formula: - * ppi = sqrt(width² + height²) / diagonal_inches - * dp_scale = ppi / 160.0 + * Calculates dp_scale from screen PPI, snaps to favorable ratios for clean + * asset scaling, then determines optimal pill height to fill the screen. * - * Then computes optimal pill height to perfectly fill the screen - * with 6-8 rows of menu items. + * DP Scale Calculation: + * 1. Calculate PPI: sqrt(width² + height²) / diagonal_inches + * 2. Calculate raw dp_scale: ppi / 160.0 (Android MDPI baseline) + * 3. Snap to favorable fraction (4/3, 3/2, 5/3, etc.) for cleaner rounding + * 4. Apply optional SCALE_MODIFIER if defined in platform.h + * + * Row Fitting Algorithm: + * - Try 8→6 rows (prefer more content) + * - For each row count, calculate pill height to fill available space + * - Select first configuration where pill fits 28-32dp range + * - Adjust pill_height to produce even physical pixels (for centering) + * + * @param screen_width Screen width in physical pixels + * @param screen_height Screen height in physical pixels + * @param diagonal_inches Physical screen diagonal in inches (from platform.h) + * + * @note Sets global gfx_dp_scale and ui struct values + * @note Must be called before any DP() macro usage */ 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; - gfx_dp_scale = ppi / 160.0f; + float raw_dp_scale = ppi / 160.0f; // Apply platform scale modifier if defined #ifdef SCALE_MODIFIER - gfx_dp_scale *= SCALE_MODIFIER; + raw_dp_scale *= SCALE_MODIFIER; #endif + // Snap dp_scale to favorable integer ratios for cleaner asset scaling + // These ratios minimize rounding errors when scaling from @1x, @2x, @3x, @4x assets + struct { + int num, den; + } favorable_ratios[] = { + {1, 1}, // 1.0 - @1x: ×1.0 + {4, 3}, // 1.333 - @1x: ×1.333, @2x: ×0.666 + {3, 2}, // 1.5 - @1x: ×1.5, @2x: ×0.75 + {5, 3}, // 1.666 - @2x: ×0.833 + {2, 1}, // 2.0 - @2x: ×1.0 + {5, 2}, // 2.5 - @2x: ×1.25, @3x: ×0.833 + {3, 1}, // 3.0 - @3x: ×1.0 + {4, 1} // 4.0 - @4x: ×1.0 + }; + int num_ratios = sizeof(favorable_ratios) / sizeof(favorable_ratios[0]); + + // Find closest favorable ratio + int best_num = favorable_ratios[0].num; + int best_den = favorable_ratios[0].den; + float min_diff = fabsf(raw_dp_scale - (float)best_num / best_den); + + for (int i = 1; i < num_ratios; i++) { + float ratio_val = (float)favorable_ratios[i].num / favorable_ratios[i].den; + float diff = fabsf(raw_dp_scale - ratio_val); + if (diff < min_diff) { + min_diff = diff; + best_num = favorable_ratios[i].num; + best_den = favorable_ratios[i].den; + } + } + + gfx_dp_scale = (float)best_num / best_den; + + LOG_info("UI_initLayout: raw_dp_scale=%.3f → snapped to %d/%d = %.3f (diff=%.3f)\n", + raw_dp_scale, best_num, best_den, gfx_dp_scale, min_diff); + // Bounds for layout calculation const int MIN_PILL = 28; const int MAX_PILL = 32; @@ -155,17 +206,17 @@ void UI_initLayout(int screen_width, int screen_height, float diagonal_inches) { } // 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 int button_px = DP(ui.button_size); if (button_px % 2 != 0) ui.button_size++; // Buttons look better even - 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 - ui.text_baseline = (4 * ui.pill_height + 15) / 30; // ~4 for 30dp pill + 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 + ui.text_baseline = (4 * ui.pill_height + 15) / 30; // ~4 for 30dp pill - 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: %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); } @@ -207,20 +258,113 @@ static struct PWR_Context { static int _; /** - * Initializes the graphics subsystem. + * Scales a surface using bilinear interpolation. + * + * Performs smooth downscaling/upscaling of RGBA8888 or RGB888 surfaces + * using bilinear filtering. Each output pixel samples the 4 nearest source + * pixels and interpolates linearly in both X and Y directions. + * + * Used for scaling UI assets (icons, pills, buttons) from @1x/@2x/@3x/@4x + * tiers to the exact dp_scale required by the current screen. Provides + * smooth edges compared to nearest-neighbor scaling. * - * Sets up SDL video, loads UI assets, initializes fonts, and prepares - * the color palette. This must be called before any other GFX_ functions. + * @param src Source surface to scale + * @param dst Destination surface (must be pre-allocated at target size) * - * Asset loading: - * - Loads platform-specific asset PNG (e.g., assets@2x.png for 2x scale) + * @note Both surfaces must be locked if SDL_MUSTLOCK is true + * @note Works with 3-byte (RGB888) and 4-byte (RGBA8888) pixel formats + * @note dst dimensions determine the output scale + */ +static void GFX_scaleBilinear(SDL_Surface* src, SDL_Surface* dst) { + if (!src || !dst) + return; + + int src_w = src->w; + int src_h = src->h; + int dst_w = dst->w; + int dst_h = dst->h; + + // Calculate scale ratios (how much to step through source for each dest pixel) + float x_ratio = ((float)src_w) / dst_w; + float y_ratio = ((float)src_h) / dst_h; + + // Determine bytes per pixel (3 for RGB, 4 for RGBA) + int bpp = src->format->BytesPerPixel; + + // Lock surfaces if needed + if (SDL_MUSTLOCK(src)) + SDL_LockSurface(src); + if (SDL_MUSTLOCK(dst)) + SDL_LockSurface(dst); + + uint8_t* src_pixels = (uint8_t*)src->pixels; + uint8_t* dst_pixels = (uint8_t*)dst->pixels; + + for (int y = 0; y < dst_h; y++) { + for (int x = 0; x < dst_w; x++) { + // Source coordinates (floating point) + float src_x = x * x_ratio; + float src_y = y * y_ratio; + + // Integer coordinates of surrounding pixels + int x1 = (int)src_x; + int y1 = (int)src_y; + int x2 = (x1 + 1 < src_w) ? x1 + 1 : x1; + int y2 = (y1 + 1 < src_h) ? y1 + 1 : y1; + + // Fractional parts for interpolation + float x_frac = src_x - x1; + float y_frac = src_y - y1; + + // Get pointers to the 4 surrounding pixels + uint8_t* p11 = src_pixels + (y1 * src->pitch) + (x1 * bpp); + uint8_t* p12 = src_pixels + (y1 * src->pitch) + (x2 * bpp); + uint8_t* p21 = src_pixels + (y2 * src->pitch) + (x1 * bpp); + uint8_t* p22 = src_pixels + (y2 * src->pitch) + (x2 * bpp); + + // Bilinear interpolation for each channel + uint8_t* dst_pixel = dst_pixels + (y * dst->pitch) + (x * bpp); + + for (int c = 0; c < bpp; c++) { + // Interpolate horizontally on top row + float top = p11[c] * (1.0f - x_frac) + p12[c] * x_frac; + // Interpolate horizontally on bottom row + float bottom = p21[c] * (1.0f - x_frac) + p22[c] * x_frac; + // Interpolate vertically + float result = top * (1.0f - y_frac) + bottom * y_frac; + dst_pixel[c] = (uint8_t)(result + 0.5f); + } + } + } + + // Unlock surfaces + if (SDL_MUSTLOCK(dst)) + SDL_UnlockSurface(dst); + if (SDL_MUSTLOCK(src)) + SDL_UnlockSurface(src); +} + +/** + * Initializes the graphics subsystem. + * + * Sets up SDL video, initializes the DP scaling system, loads and scales + * UI assets, initializes fonts, and prepares the color palette. + * + * Asset Loading and Scaling: + * - Selects asset tier (@1x, @2x, @3x, @4x) based on ceil(dp_scale) + * - If exact match: uses assets as-is (e.g., dp_scale=2.0 with @2x) + * - If fractional: extracts and individually scales each asset using bilinear filtering + * - Pill caps → exactly pill_px (for pixel-perfect GFX_blitPill rendering) + * - Buttons/holes → exactly button_px + * - Icons (brightness, volume, wifi) → proportional with even-pixel centering + * - Battery assets → proportional scaling (internal offsets handled in GFX_blitBattery) * - Defines rectangles for each asset sprite in the texture atlas * - Maps asset IDs to RGB color values for fills * - * Font initialization: + * Font Initialization: * - Opens 4 font sizes (large, medium, small, tiny) * - Applies bold style to all fonts - * - Font sizes are scaled based on FIXED_SCALE + * - Font sizes scaled based on DP() macro (resolution-independent) * * @param mode Display mode (MODE_MAIN for launcher, MODE_MENU for in-game) * @return Pointer to main SDL screen surface @@ -280,7 +424,7 @@ SDL_Surface* GFX_init(int mode) { // Define asset rectangles in the loaded sprite sheet (at asset_scale) // Base coordinates are @1x, multiply by asset_scale for actual position #define ASSET_SCALE4(x, y, w, h) \ - ((x) *asset_scale), ((y) *asset_scale), ((w) *asset_scale), ((h) *asset_scale) + ((x) * asset_scale), ((y) * asset_scale), ((w) * asset_scale), ((h) * asset_scale) asset_rects[ASSET_WHITE_PILL] = (SDL_Rect){ASSET_SCALE4(1, 1, 30, 30)}; asset_rects[ASSET_BLACK_PILL] = (SDL_Rect){ASSET_SCALE4(33, 1, 30, 30)}; @@ -308,48 +452,97 @@ 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)}; - // If dp_scale doesn't match asset_scale, scale the assets + // 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) { - // Scale down from asset_scale to dp_scale float scale_ratio = gfx_dp_scale / (float)asset_scale; - int new_w = (int)(loaded_assets->w * scale_ratio + 0.5f); - int new_h = (int)(loaded_assets->h * scale_ratio + 0.5f); + int pill_px = DP(ui.pill_height); + int button_px = DP(ui.button_size); - // Create scaled surface with same format - gfx.assets = SDL_CreateRGBSurface(0, new_w, new_h, loaded_assets->format->BitsPerPixel, - loaded_assets->format->Rmask, loaded_assets->format->Gmask, - loaded_assets->format->Bmask, loaded_assets->format->Amask); + // Calculate destination sheet dimensions (proportionally scaled) + int sheet_w = (int)(loaded_assets->w * scale_ratio + 0.5f); + int sheet_h = (int)(loaded_assets->h * scale_ratio + 0.5f); - // Scale using SDL_BlitScaled (nearest-neighbor, sharp but correctly sized) - // TODO: Implement bilinear filtering for smoother edges - SDL_BlitScaled(loaded_assets, NULL, gfx.assets, NULL); - SDL_FreeSurface(loaded_assets); + // Create destination sheet (initially empty, will be filled asset-by-asset) + gfx.assets = + SDL_CreateRGBSurface(0, sheet_w, sheet_h, loaded_assets->format->BitsPerPixel, + loaded_assets->format->Rmask, loaded_assets->format->Gmask, + loaded_assets->format->Bmask, loaded_assets->format->Amask); - // Scale asset rectangles to match the scaled surface - // Round dimensions to ensure even differences with pill size for perfect centering - int pill_px = DP(ui.pill_height); + // Process each asset individually to ensure exact sizing + // Pills/buttons get exact dimensions, other assets scale proportionally for (int i = 0; i < ASSET_COUNT; i++) { - asset_rects[i].x = (int)(asset_rects[i].x * scale_ratio + 0.5f); - asset_rects[i].y = (int)(asset_rects[i].y * scale_ratio + 0.5f); - - int w = (int)(asset_rects[i].w * scale_ratio + 0.5f); - int h = (int)(asset_rects[i].h * scale_ratio + 0.5f); - - // For standalone assets that get centered in pills, ensure even difference - // Skip battery family (outline, fill, bolt) - they have internal positioning dependencies - // Only adjust: brightness, volume, wifi - if (i == ASSET_BRIGHTNESS || i == ASSET_VOLUME_MUTE || i == ASSET_VOLUME || - i == ASSET_WIFI) { - if ((pill_px - w) % 2 != 0) w++; // Make difference even - if ((pill_px - h) % 2 != 0) h++; // Make difference even + // 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) + 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 + 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 + target_w = target_h = button_px; + } else { + // All other assets: proportional scaling with rounding + target_w = (int)(src_rect.w * scale_ratio + 0.5f); + target_h = (int)(src_rect.h * scale_ratio + 0.5f); + + // 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 } - asset_rects[i].w = w; - asset_rects[i].h = h; + // Extract this asset region from source sheet + SDL_Surface* extracted = + SDL_CreateRGBSurface(0, src_rect.w, src_rect.h, loaded_assets->format->BitsPerPixel, + loaded_assets->format->Rmask, loaded_assets->format->Gmask, + loaded_assets->format->Bmask, loaded_assets->format->Amask); + SDL_BlitSurface(loaded_assets, &src_rect, extracted, &(SDL_Rect){0, 0}); + + // Scale this specific asset to its target size + SDL_Surface* scaled = + SDL_CreateRGBSurface(0, target_w, target_h, loaded_assets->format->BitsPerPixel, + loaded_assets->format->Rmask, loaded_assets->format->Gmask, + loaded_assets->format->Bmask, loaded_assets->format->Amask); + GFX_scaleBilinear(extracted, scaled); + + // Place the scaled asset into the destination sheet + SDL_BlitSurface(scaled, NULL, gfx.assets, &dst_rect); + + // Update asset rectangle with new position and dimensions + asset_rects[i] = (SDL_Rect){dst_rect.x, dst_rect.y, target_w, target_h}; + + // Clean up temporary surfaces for this asset + SDL_FreeSurface(extracted); + SDL_FreeSurface(scaled); } - LOG_info("GFX_init: Scaled assets from @%dx to dp_scale=%.2f (nearest-neighbor)\n", - asset_scale, gfx_dp_scale); + // Done with source sheet + SDL_FreeSurface(loaded_assets); + + LOG_info("GFX_init: Scaled %d assets individually from @%dx to dp_scale=%.2f (bilinear)\n", + ASSET_COUNT, asset_scale, gfx_dp_scale); + LOG_debug(" DARK_GRAY_PILL: w=%d h=%d (target pill_px=%d)\n", + asset_rects[ASSET_DARK_GRAY_PILL].w, asset_rects[ASSET_DARK_GRAY_PILL].h, + pill_px); } else { // Perfect match, use assets as-is gfx.assets = loaded_assets; @@ -897,8 +1090,8 @@ void GFX_blitBattery(SDL_Surface* dst, const SDL_Rect* dst_rect) { // Battery fill/bolt offsets must scale with the actual battery asset size // At @1x design: fill is 3px right, 2px down from battery top-left // Scale this based on actual battery width: rect.w / 17 (17 = @1x battery width) - int fill_x_offset = (rect.w * 3 + 8) / 17; // (rect.w * 3/17), rounded - int fill_y_offset = (rect.h * 2 + 5) / 10; // (rect.h * 2/10), rounded + int fill_x_offset = (rect.w * 3 + 8) / 17; // (rect.w * 3/17), rounded + int fill_y_offset = (rect.h * 2 + 5) / 10; // (rect.h * 2/10), rounded if (pwr.is_charging) { if (!logged) { @@ -1132,9 +1325,8 @@ int GFX_blitHardwareGroup(SDL_Surface* dst, int show_setting) { float percent = ((float)(setting_value - setting_min) / (setting_max - setting_min)); if (show_setting == 1 || setting_value > 0) { - GFX_blitPill( - ASSET_BAR, dst, - &(SDL_Rect){ox, oy, DP(SETTINGS_WIDTH) * percent, DP(SETTINGS_SIZE)}); + GFX_blitPill(ASSET_BAR, dst, + &(SDL_Rect){ox, oy, DP(SETTINGS_WIDTH) * percent, DP(SETTINGS_SIZE)}); } } else { // TODO: handle wifi diff --git a/workspace/all/common/api.h b/workspace/all/common/api.h index c8ffe12c..fb87aa60 100644 --- a/workspace/all/common/api.h +++ b/workspace/all/common/api.h @@ -68,11 +68,11 @@ extern float gfx_dp_scale; * to optimally fill the display without per-platform manual configuration. */ typedef struct UI_Layout { - 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 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 text_baseline; // Vertical offset for text centering in pill - int button_size; // Size of button graphics in dp + int button_size; // Size of button graphics in dp int button_margin; // Margin around buttons in dp int button_padding; // Padding inside buttons in dp } UI_Layout; diff --git a/workspace/all/minarch/minarch.c b/workspace/all/minarch/minarch.c index ac58d17e..4520ff87 100644 --- a/workspace/all/minarch/minarch.c +++ b/workspace/all/minarch/minarch.c @@ -3053,18 +3053,16 @@ static void MSG_init(void) { int i = 0; while ((c = chars[i])) { digit = TTF_RenderUTF8_Blended(font.tiny, c, COLOR_WHITE); - SDL_BlitSurface( - digit, NULL, digits, - &(SDL_Rect){(i * DP(DIGIT_WIDTH)) + (DP(DIGIT_WIDTH) - digit->w) / 2, - (DP(DIGIT_HEIGHT) - digit->h) / 2}); + SDL_BlitSurface(digit, NULL, digits, + &(SDL_Rect){(i * DP(DIGIT_WIDTH)) + (DP(DIGIT_WIDTH) - digit->w) / 2, + (DP(DIGIT_HEIGHT) - digit->h) / 2}); SDL_FreeSurface(digit); i += 1; } } static int MSG_blitChar(int n, int x, int y) { if (n != DIGIT_SPACE) - SDL_BlitSurface(digits, - &(SDL_Rect){n * DP(DIGIT_WIDTH), 0, DP2(DIGIT_WIDTH, DIGIT_HEIGHT)}, + SDL_BlitSurface(digits, &(SDL_Rect){n * DP(DIGIT_WIDTH), 0, DP2(DIGIT_WIDTH, DIGIT_HEIGHT)}, screen, &(SDL_Rect){x, y}); return x + DP(DIGIT_WIDTH + DIGIT_TRACKING); } @@ -5312,9 +5310,9 @@ static int Menu_options(MenuList* list) { 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)}); + SDL_BlitSurface( + text, NULL, screen, + &(SDL_Rect){ox + DP(OPTION_PADDING), oy + DP((j * ui.button_size) + 1)}); SDL_FreeSurface(text); } } else if (type == MENU_FIXED) { @@ -5362,9 +5360,9 @@ static int Menu_options(MenuList* list) { 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)}); + SDL_BlitSurface( + text, NULL, screen, + &(SDL_Rect){ox + DP(OPTION_PADDING), oy + DP((j * ui.button_size) + 1)}); SDL_FreeSurface(text); } } else if (type == MENU_VAR || type == MENU_INPUT) { @@ -5426,9 +5424,9 @@ static int Menu_options(MenuList* list) { 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)}); + SDL_BlitSurface( + text, NULL, screen, + &(SDL_Rect){ox + DP(OPTION_PADDING), oy + DP((j * ui.button_size) + 1)}); SDL_FreeSurface(text); if (await_input && j == selected_row) { @@ -5455,8 +5453,8 @@ static int Menu_options(MenuList* list) { if (end < count) GFX_blitAsset( ASSET_SCROLL_DOWN, NULL, screen, - &(SDL_Rect){ox, - screen->h - DP(ui.padding + ui.pill_height + ui.button_size) + oy}); + &(SDL_Rect){ox, screen->h - + DP(ui.padding + ui.pill_height + ui.button_size) + oy}); } if (!desc && list->desc) @@ -5963,8 +5961,9 @@ static void Menu_loop(void) { GFX_blitButtonGroup((char*[]){"B", "BACK", "A", "OKAY", NULL}, 1, screen, 1); // list - oy = - (((DEVICE_HEIGHT / FIXED_SCALE) - ui.padding * 2) - (MENU_ITEM_COUNT * ui.pill_height)) / 2; + oy = (((DEVICE_HEIGHT / FIXED_SCALE) - ui.padding * 2) - + (MENU_ITEM_COUNT * ui.pill_height)) / + 2; for (int i = 0; i < MENU_ITEM_COUNT; i++) { char* item = menu.items[i]; SDL_Color text_color = COLOR_WHITE; @@ -5998,15 +5997,17 @@ static void Menu_loop(void) { text = TTF_RenderUTF8_Blended(font.large, item, COLOR_BLACK); SDL_BlitSurface(text, NULL, screen, &(SDL_Rect){DP(2 + ui.padding + ui.button_padding), - DP(1 + ui.padding + oy + (i * ui.pill_height) + ui.text_baseline)}); + DP(1 + ui.padding + oy + (i * ui.pill_height) + + ui.text_baseline)}); SDL_FreeSurface(text); } // text text = TTF_RenderUTF8_Blended(font.large, item, text_color); - SDL_BlitSurface(text, NULL, screen, - &(SDL_Rect){DP(ui.padding + ui.button_padding), - DP(oy + ui.padding + (i * ui.pill_height) + ui.text_baseline)}); + SDL_BlitSurface( + text, NULL, screen, + &(SDL_Rect){DP(ui.padding + ui.button_padding), + DP(oy + ui.padding + (i * ui.pill_height) + ui.text_baseline)}); SDL_FreeSurface(text); } @@ -6054,8 +6055,7 @@ static void Menu_loop(void) { oy += hh + DP(WINDOW_RADIUS); for (int i = 0; i < MENU_SLOT_COUNT; i++) { if (i == menu.slot) - GFX_blitAsset(ASSET_PAGE, NULL, screen, - &(SDL_Rect){ox + DP(i * 15), oy}); + GFX_blitAsset(ASSET_PAGE, NULL, screen, &(SDL_Rect){ox + DP(i * 15), oy}); else GFX_blitAsset(ASSET_DOT, NULL, screen, &(SDL_Rect){ox + DP(i * 15) + 4, oy + DP(2)}); diff --git a/workspace/all/minui/minui.c b/workspace/all/minui/minui.c index fa242200..990728d6 100644 --- a/workspace/all/minui/minui.c +++ b/workspace/all/minui/minui.c @@ -1739,8 +1739,7 @@ static void openDirectory(char* path, int auto_launch) { if (top) { top->start = start; top->end = - end ? end - : ((top->entries->count < ui.row_count) ? top->entries->count : ui.row_count); + end ? end : ((top->entries->count < ui.row_count) ? top->entries->count : ui.row_count); Array_push(stack, top); } } @@ -2316,8 +2315,8 @@ int main(int argc, char* argv[]) { if (j == selected_row) { GFX_blitPill(ASSET_WHITE_PILL, screen, &(SDL_Rect){DP(ui.padding), - DP(ui.padding + (j * ui.pill_height)), max_width, - DP(ui.pill_height)}); + DP(ui.padding + (j * ui.pill_height)), + max_width, DP(ui.pill_height)}); text_color = COLOR_BLACK; } else if (entry->unique) { trimSortingMeta(&entry_unique); @@ -2331,8 +2330,10 @@ int main(int argc, char* argv[]) { 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.padding + ui.button_padding), + DP(ui.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)); @@ -2340,11 +2341,11 @@ int main(int argc, char* argv[]) { SDL_Surface* text = TTF_RenderUTF8_Blended(font.large, display_name, text_color); SDL_BlitSurface( - text, - &(SDL_Rect){0, 0, max_width - DP(ui.button_padding * 2), text->h}, + 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}); + DP(ui.padding + (j * ui.pill_height) + ui.text_baseline), 0, + 0}); SDL_FreeSurface(text); } } else { diff --git a/workspace/all/say/say.c b/workspace/all/say/say.c index 40cba823..0f0e0e2e 100644 --- a/workspace/all/say/say.c +++ b/workspace/all/say/say.c @@ -59,9 +59,9 @@ int main(int argc, const char* argv[]) { GFX_clear(screen); // Display message centered, leaving room for button at bottom - GFX_blitMessage( - font.large, msg, screen, - &(SDL_Rect){0, 0, screen->w, screen->h - DP(ui.padding + ui.pill_height + ui.padding)}); + GFX_blitMessage(font.large, msg, screen, + &(SDL_Rect){0, 0, screen->w, + screen->h - DP(ui.padding + ui.pill_height + ui.padding)}); GFX_blitButtonGroup((char*[]){"A", "OKAY", NULL}, 1, screen, 1); GFX_flip(screen); diff --git a/workspace/rg35xxplus/platform/platform.h b/workspace/rg35xxplus/platform/platform.h index c83fa973..3c96f10c 100644 --- a/workspace/rg35xxplus/platform/platform.h +++ b/workspace/rg35xxplus/platform/platform.h @@ -141,7 +141,9 @@ extern int on_hdmi; // Set to 1 when HDMI output is active // Runtime-configurable for device variants /////////////////////////////// -#define SCREEN_DIAGONAL (is_cubexx ? 3.95f : (is_rg34xx ? 3.4f : 3.5f)) // Diagonal: 3.95" (Cube) / 3.4" (34XX) / 3.5" (Plus) +#define SCREEN_DIAGONAL \ + (is_cubexx ? 3.95f \ + : (is_rg34xx ? 3.4f : 3.5f)) // Diagonal: 3.95" (Cube) / 3.4" (34XX) / 3.5" (Plus) #define FIXED_SCALE 2 // 2x scaling factor for UI #define FIXED_WIDTH (is_cubexx ? 720 : (is_rg34xx ? 720 : 640)) // Width: 720 (H/SP) or 640 (Plus) #define FIXED_HEIGHT (is_cubexx ? 720 : 480) // Height: 720 (H) or 480 (Plus/SP) From c26897c98b720ad3244e41ec46dda08180850bd0 Mon Sep 17 00:00:00 2001 From: Nick Chapman Date: Tue, 25 Nov 2025 22:31:29 -0800 Subject: [PATCH 03/17] Simplify pixel snapping. --- workspace/all/common/api.c | 57 +++++++++++---------------------- workspace/all/minarch/minarch.c | 6 ++-- 2 files changed, 22 insertions(+), 41 deletions(-) diff --git a/workspace/all/common/api.c b/workspace/all/common/api.c index 43debbea..bba53c01 100644 --- a/workspace/all/common/api.c +++ b/workspace/all/common/api.c @@ -111,41 +111,9 @@ void UI_initLayout(int screen_width, int screen_height, float diagonal_inches) { raw_dp_scale *= SCALE_MODIFIER; #endif - // Snap dp_scale to favorable integer ratios for cleaner asset scaling - // These ratios minimize rounding errors when scaling from @1x, @2x, @3x, @4x assets - struct { - int num, den; - } favorable_ratios[] = { - {1, 1}, // 1.0 - @1x: ×1.0 - {4, 3}, // 1.333 - @1x: ×1.333, @2x: ×0.666 - {3, 2}, // 1.5 - @1x: ×1.5, @2x: ×0.75 - {5, 3}, // 1.666 - @2x: ×0.833 - {2, 1}, // 2.0 - @2x: ×1.0 - {5, 2}, // 2.5 - @2x: ×1.25, @3x: ×0.833 - {3, 1}, // 3.0 - @3x: ×1.0 - {4, 1} // 4.0 - @4x: ×1.0 - }; - int num_ratios = sizeof(favorable_ratios) / sizeof(favorable_ratios[0]); - - // Find closest favorable ratio - int best_num = favorable_ratios[0].num; - int best_den = favorable_ratios[0].den; - float min_diff = fabsf(raw_dp_scale - (float)best_num / best_den); - - for (int i = 1; i < num_ratios; i++) { - float ratio_val = (float)favorable_ratios[i].num / favorable_ratios[i].den; - float diff = fabsf(raw_dp_scale - ratio_val); - if (diff < min_diff) { - min_diff = diff; - best_num = favorable_ratios[i].num; - best_den = favorable_ratios[i].den; - } - } - - gfx_dp_scale = (float)best_num / best_den; - - LOG_info("UI_initLayout: raw_dp_scale=%.3f → snapped to %d/%d = %.3f (diff=%.3f)\n", - raw_dp_scale, best_num, best_den, gfx_dp_scale, min_diff); + // Use the calculated dp_scale directly (no snapping to preserve PPI accuracy) + // Asset-level even-pixel adjustments handle rounding where needed + gfx_dp_scale = raw_dp_scale; // Bounds for layout calculation const int MIN_PILL = 28; @@ -195,13 +163,26 @@ void UI_initLayout(int screen_width, int screen_height, float diagonal_inches) { ui.padding = DEFAULT_PADDING; // Adjust pill_height so it produces even physical pixels (for alignment) + // May need to adjust by 2dp if dp_scale is fractional (e.g., 1.5: 30→45, 31→47, 32→48) int pill_px = DP(ui.pill_height); if (pill_px % 2 != 0) { + LOG_debug(" Even-pixel adj: pill_px=%d (odd)\n", pill_px); // Try increasing first (prefer slightly larger) - if (DP(ui.pill_height + 1) <= DP(MAX_PILL)) { - ui.pill_height++; + int adjusted = ui.pill_height + 1; + while (adjusted <= MAX_PILL && DP(adjusted) % 2 != 0) { + adjusted++; + } + if (adjusted <= MAX_PILL) { + ui.pill_height = adjusted; + LOG_debug(" → Increased to %ddp (%dpx)\n", ui.pill_height, DP(ui.pill_height)); } else { - ui.pill_height--; + // Can't increase, try decreasing + adjusted = ui.pill_height - 1; + while (adjusted >= MIN_PILL && DP(adjusted) % 2 != 0) { + adjusted--; + } + ui.pill_height = adjusted; + LOG_debug(" → Decreased to %ddp (%dpx)\n", ui.pill_height, DP(ui.pill_height)); } } diff --git a/workspace/all/minarch/minarch.c b/workspace/all/minarch/minarch.c index 4520ff87..e7e9366c 100644 --- a/workspace/all/minarch/minarch.c +++ b/workspace/all/minarch/minarch.c @@ -5960,9 +5960,9 @@ static void Menu_loop(void) { 0); GFX_blitButtonGroup((char*[]){"B", "BACK", "A", "OKAY", NULL}, 1, screen, 1); - // list - oy = (((DEVICE_HEIGHT / FIXED_SCALE) - ui.padding * 2) - - (MENU_ITEM_COUNT * ui.pill_height)) / + // Vertically center the menu items + oy = (((DEVICE_HEIGHT / FIXED_SCALE) - DP(ui.padding * 2)) - + DP(MENU_ITEM_COUNT * ui.pill_height)) / 2; for (int i = 0; i < MENU_ITEM_COUNT; i++) { char* item = menu.items[i]; From 4a43763da2f04c359af98b6faa85b8b6034343ba Mon Sep 17 00:00:00 2001 From: Nick Chapman Date: Tue, 25 Nov 2025 23:03:04 -0800 Subject: [PATCH 04/17] Fix missing assets on certain devices. --- workspace/all/common/api.c | 36 +++++++++++++++--------------------- 1 file changed, 15 insertions(+), 21 deletions(-) diff --git a/workspace/all/common/api.c b/workspace/all/common/api.c index bba53c01..62efc3a4 100644 --- a/workspace/all/common/api.c +++ b/workspace/all/common/api.c @@ -166,7 +166,6 @@ void UI_initLayout(int screen_width, int screen_height, float diagonal_inches) { // May need to adjust by 2dp if dp_scale is fractional (e.g., 1.5: 30→45, 31→47, 32→48) int pill_px = DP(ui.pill_height); if (pill_px % 2 != 0) { - LOG_debug(" Even-pixel adj: pill_px=%d (odd)\n", pill_px); // Try increasing first (prefer slightly larger) int adjusted = ui.pill_height + 1; while (adjusted <= MAX_PILL && DP(adjusted) % 2 != 0) { @@ -174,7 +173,6 @@ void UI_initLayout(int screen_width, int screen_height, float diagonal_inches) { } if (adjusted <= MAX_PILL) { ui.pill_height = adjusted; - LOG_debug(" → Increased to %ddp (%dpx)\n", ui.pill_height, DP(ui.pill_height)); } else { // Can't increase, try decreasing adjusted = ui.pill_height - 1; @@ -182,7 +180,6 @@ void UI_initLayout(int screen_width, int screen_height, float diagonal_inches) { adjusted--; } ui.pill_height = adjusted; - LOG_debug(" → Decreased to %ddp (%dpx)\n", ui.pill_height, DP(ui.pill_height)); } } @@ -444,11 +441,12 @@ SDL_Surface* GFX_init(int mode) { int sheet_w = (int)(loaded_assets->w * scale_ratio + 0.5f); int sheet_h = (int)(loaded_assets->h * scale_ratio + 0.5f); - // Create destination sheet (initially empty, will be filled asset-by-asset) - gfx.assets = - SDL_CreateRGBSurface(0, sheet_w, sheet_h, loaded_assets->format->BitsPerPixel, - loaded_assets->format->Rmask, loaded_assets->format->Gmask, - loaded_assets->format->Bmask, loaded_assets->format->Amask); + // Create a blank surface and use SDL_ConvertSurface to match loaded_assets format + // This ensures SDL sets up the format correctly (masks, etc.) + SDL_Surface* blank = + SDL_CreateRGBSurface(0, sheet_w, sheet_h, 32, 0, 0, 0, 0); // Let SDL pick format + gfx.assets = SDL_ConvertSurface(blank, loaded_assets->format, 0); + SDL_FreeSurface(blank); // Process each asset individually to ensure exact sizing // Pills/buttons get exact dimensions, other assets scale proportionally @@ -492,17 +490,16 @@ SDL_Surface* GFX_init(int mode) { } // Extract this asset region from source sheet - SDL_Surface* extracted = - SDL_CreateRGBSurface(0, src_rect.w, src_rect.h, loaded_assets->format->BitsPerPixel, - loaded_assets->format->Rmask, loaded_assets->format->Gmask, - loaded_assets->format->Bmask, loaded_assets->format->Amask); + // Use SDL_ConvertSurface to ensure proper format setup + SDL_Surface* temp_extract = SDL_CreateRGBSurface(0, src_rect.w, src_rect.h, 32, 0, 0, 0, 0); + SDL_Surface* extracted = SDL_ConvertSurface(temp_extract, loaded_assets->format, 0); + SDL_FreeSurface(temp_extract); SDL_BlitSurface(loaded_assets, &src_rect, extracted, &(SDL_Rect){0, 0}); // Scale this specific asset to its target size - SDL_Surface* scaled = - SDL_CreateRGBSurface(0, target_w, target_h, loaded_assets->format->BitsPerPixel, - loaded_assets->format->Rmask, loaded_assets->format->Gmask, - loaded_assets->format->Bmask, loaded_assets->format->Amask); + SDL_Surface* temp_scaled = SDL_CreateRGBSurface(0, target_w, target_h, 32, 0, 0, 0, 0); + SDL_Surface* scaled = SDL_ConvertSurface(temp_scaled, loaded_assets->format, 0); + SDL_FreeSurface(temp_scaled); GFX_scaleBilinear(extracted, scaled); // Place the scaled asset into the destination sheet @@ -519,11 +516,8 @@ SDL_Surface* GFX_init(int mode) { // Done with source sheet SDL_FreeSurface(loaded_assets); - LOG_info("GFX_init: Scaled %d assets individually from @%dx to dp_scale=%.2f (bilinear)\n", - ASSET_COUNT, asset_scale, gfx_dp_scale); - LOG_debug(" DARK_GRAY_PILL: w=%d h=%d (target pill_px=%d)\n", - asset_rects[ASSET_DARK_GRAY_PILL].w, asset_rects[ASSET_DARK_GRAY_PILL].h, - pill_px); + LOG_info("GFX_init: Scaled %d assets from @%dx to dp_scale=%.2f (bilinear)\n", ASSET_COUNT, + asset_scale, gfx_dp_scale); } else { // Perfect match, use assets as-is gfx.assets = loaded_assets; From e404b81fee154f5cc7f215126053e74e193f1661 Mon Sep 17 00:00:00 2001 From: Nick Chapman Date: Wed, 26 Nov 2025 10:43:30 -0800 Subject: [PATCH 05/17] Improve UI layout and asset quality. - Dynamic row fitting: Calculate rows based on available screen space instead of fixed 6-8 range - +1 tier asset scaling: Load one tier higher than needed for better antialiasing quality - Use RGBA8888 for temporary surfaces during asset scaling Note: Transparency issue on rg35xxplus (RGB565) still being debugged. See docs/transparency-debugging.md for investigation history. --- docs/transparency-debugging.md | 213 +++++++++++++++++++++++++++++++++ workspace/all/common/api.c | 80 +++++++------ 2 files changed, 254 insertions(+), 39 deletions(-) create mode 100644 docs/transparency-debugging.md diff --git a/docs/transparency-debugging.md b/docs/transparency-debugging.md new file mode 100644 index 00000000..043d590d --- /dev/null +++ b/docs/transparency-debugging.md @@ -0,0 +1,213 @@ +# Asset Transparency Debugging History + +**Problem:** Assets showing black backgrounds instead of transparency on rg35xxplus. + +**Status:** Works on tg5040, fails on rg35xxplus + +**Note:** This issue is INDEPENDENT of the +1 tier scaling change. The +1 tier change just selects a larger source asset - it doesn't affect transparency handling. We discovered this transparency bug while testing the +1 tier improvement. + +--- + +## Platform Differences + +### Working Platform: tg5040 +- Resolution: 1280x720 (or 1024x768 Brick variant) +- SDL Version: SDL 1.2 +- Pixel Format: TBD (need to verify) + +### Failing Platform: rg35xxplus +- Resolution: 640x480 (or 720x720 CubeXX, 720x480 RG34XX) +- SDL Version: SDL 1.2 +- Pixel Format: **RGB565 (16-bit, no alpha channel)** +- Key difference: No alpha mask in surface format + +--- + +## Root Cause Analysis + +When surfaces don't have an alpha channel (RGB565): +- RGBA(0,0,0,0) transparent black → RGB(0,0,0) solid black +- Alpha channel is discarded during `SDL_ConvertSurface()` to RGB565 +- Transparency must be handled differently for non-alpha formats + +--- + +## Attempted Solutions + +### Attempt 0: Original +1 tier implementation (NO transparency handling) +**File:** `workspace/all/common/api.c` line 390 +**Code:** +```c +int asset_scale = (int)ceilf(gfx_dp_scale) + 1; // +1 tier for quality + +// Asset scaling code with NO special transparency handling +SDL_Surface* temp = SDL_CreateRGBSurface(0, w, h, 32, 0, 0, 0, 0); +SDL_Surface* surface = SDL_ConvertSurface(temp, loaded_assets->format, 0); +SDL_FreeSurface(temp); +// No SetAlpha, no FillRect, just basic create/convert/blit +``` +**Result:** ✅ Assets visible on rg35xxplus, ❌ but backgrounds were BLACK instead of transparent + +**Why it partially worked:** +- Assets loaded and displayed correctly +- But black backgrounds because SDL_CreateRGBSurface with (0,0,0,0) masks defaults to undefined/black pixels +- The issue: we were trying to FIX this black background problem + +**Important:** This proves the +1 tier scaling itself works, just need proper transparency + +--- + +### Attempt 1: SDL_SetSurfaceBlendMode() (SDL2 only) +**File:** `workspace/all/common/api.c` lines ~450, 457, 507, 518 +**Code:** +```c +SDL_SetSurfaceBlendMode(surface, SDL_BLENDMODE_NONE); +SDL_FillRect(surface, NULL, SDL_MapRGBA(surface->format, 0, 0, 0, 0)); +``` +**Result:** ❌ Compile error on SDL 1.2 platforms (function doesn't exist) + +--- + +### Attempt 2: Platform-specific #ifdef blocks +**File:** `workspace/all/common/api.c` lines ~450-465, 507-534 +**Code:** +```c +#ifdef USE_SDL2 + SDL_SetSurfaceBlendMode(surface, SDL_BLENDMODE_NONE); +#else + SDL_SetAlpha(surface, 0, 0); // SDL 1.2 +#endif +SDL_FillRect(surface, NULL, SDL_MapRGBA(surface->format, 0, 0, 0, 0)); +``` +**Result:** ✅ Compiles on both SDL versions, but ❌ still shows black on rg35xxplus + +**Why it failed:** +- Filling with RGBA(0,0,0,0) becomes solid black when converted to RGB565 +- The FillRect happens BEFORE conversion, so it fills with transparent black +- But after conversion to RGB565, transparent black → solid black + +--- + +### Attempt 3: Use SDLX_SetAlpha() compatibility macro +**File:** `workspace/all/common/api.c` via `workspace/all/common/sdl.h` +**Code:** +```c +SDLX_SetAlpha(surface, 0, 0); // Uses sdl.h compat layer +SDL_FillRect(surface, NULL, SDL_MapRGBA(surface->format, 0, 0, 0, 0)); +``` +**Result:** ✅ Compiles cleanly, ❌ still shows black on rg35xxplus + +**Why it failed:** Same issue as Attempt 2 - FillRect with transparent black becomes solid black in RGB565 + +--- + +### Attempt 4: Remove FillRect, rely on SDL blit behavior (CURRENT) +**File:** `workspace/all/common/api.c` lines 445-505 +**Code:** +```c +// Create RGBA8888 surface +SDL_Surface* temp = SDL_CreateRGBSurface(0, w, h, 32, RGBA_MASK_8888); +// Convert to loaded_assets format (may be RGB565) +SDL_Surface* surface = SDL_ConvertSurface(temp, loaded_assets->format, 0); +SDL_FreeSurface(temp); +// NO FillRect - let SDL handle transparency through blitting +SDL_BlitSurface(source, &src_rect, surface, &dst_rect); +``` +**Result:** ✅ Compiles, 🔄 **Testing on rg35xxplus...** + +**Theory:** +- Don't pre-fill surfaces with any color +- Let SDL's blitting preserve whatever transparency exists in source +- Avoids the RGBA→RGB565 conversion issue + +--- + +## Key Technical Details + +### SDL_ConvertSurface() Behavior +When converting RGBA8888 → RGB565: +- Alpha channel is **discarded** (no space for it in RGB565) +- RGB(0,0,0) in RGB565 = solid black +- Transparency in RGB565 requires **color keying**, not alpha + +### Color Keying vs Alpha Blending +**Alpha Blending** (RGBA formats): +- Per-pixel alpha values (0-255) +- Smooth gradients, antialiasing +- Used in: RGBA8888, some RGBA formats + +**Color Keying** (RGB formats): +- One specific RGB color = transparent (e.g., magenta RGB(255,0,255)) +- Binary transparency (either visible or invisible) +- Used in: RGB565 when alpha not available +- Set with `SDL_SetColorKey(surface, SDL_SRCCOLORKEY, color_key)` + +### The rg35xxplus Problem +rg35xxplus uses RGB565, so: +- No alpha channel available +- Our PNG assets have alpha, but it's lost during conversion +- **SDL should handle this automatically** by preserving source alpha during blit +- But pre-filling with "transparent black" becomes solid black in RGB565 + +--- + +## Next Steps If Attempt 4 Fails + +### Option A: Use color keying for RGB formats +Detect if surface has no alpha mask, and use a specific color as transparent: +```c +if (!surface->format->Amask) { + // RGB format - use color keying + uint32_t magenta = SDL_MapRGB(surface->format, 255, 0, 255); + SDL_SetColorKey(surface, SDL_SRCCOLORKEY, magenta); + SDL_FillRect(surface, NULL, magenta); +} +``` + +### Option B: Keep surfaces in RGBA until final blit to screen +Don't convert to loaded_assets format until we're done with all transformations: +```c +// Work in RGBA8888 throughout +SDL_Surface* rgba_assets = /* keep in RGBA */; +// Only convert to RGB565 when blitting to screen +``` + +### Option C: Check what worked before +1 tier change +Revert the +1 tier change temporarily and verify transparency works, then investigate what's different about the asset loading path for the higher tier. + +--- + +## Test Checklist + +When testing transparency fixes: + +- [ ] Test on rg35xxplus (RGB565, SDL 1.2) +- [ ] Test on tg5040 (working baseline) +- [ ] Test on miyoomini (RGB565, SDL 1.2) +- [ ] Test on desktop (RGBA, SDL2) +- [ ] Verify icons (battery, wifi, brightness, volume) show transparency +- [ ] Verify pill backgrounds don't have black boxes +- [ ] Check button graphics + +--- + +## Build Commands + +```bash +# Test specific platforms +make PLATFORM=rg35xxplus build +make PLATFORM=tg5040 build +make PLATFORM=miyoomini build + +# Test desktop +make dev +``` + +--- + +## References + +- RGB565 format: 16-bit color, 5 bits red, 6 bits green, 5 bits blue, **0 bits alpha** +- RGBA8888 format: 32-bit color, 8 bits per channel including alpha +- SDL_ConvertSurface: Converts pixel formats, discards alpha if destination has no Amask +- SDLX_SetAlpha macro: `workspace/all/common/sdl.h:54-67` diff --git a/workspace/all/common/api.c b/workspace/all/common/api.c index 62efc3a4..1bf28ec9 100644 --- a/workspace/all/common/api.c +++ b/workspace/all/common/api.c @@ -118,43 +118,43 @@ void UI_initLayout(int screen_width, int screen_height, float diagonal_inches) { // Bounds for layout calculation const int MIN_PILL = 28; const int MAX_PILL = 32; - const int MIN_ROWS = 6; - const int MAX_ROWS = 8; + const int PREFERRED_PILL = 30; const int DEFAULT_PADDING = 10; // Consistent padding in dp + const int FOOTER_ROWS = 2; // Header spacing + footer button hints row // Calculate available height in dp int screen_height_dp = (int)(screen_height / gfx_dp_scale + 0.5f); int available_dp = screen_height_dp - (DEFAULT_PADDING * 2); - // Find best row count where pill height fits in acceptable range - // We need: content rows + 2 (header spacing + footer row) - int best_rows = MIN_ROWS; - int best_pill = 30; - - // Try to fit as many rows as possible - for (int rows = MAX_ROWS; rows >= MIN_ROWS; rows--) { - int total_rows = rows + 2; // +1 header spacing, +1 footer - int pill = available_dp / total_rows; + // Dynamic row fitting: calculate how many rows fit in available space + // Start with preferred pill size and calculate maximum rows + int best_pill = PREFERRED_PILL; + int best_rows = (available_dp / PREFERRED_PILL) - FOOTER_ROWS; + + // Ensure we have at least some rows, adjust pill size if needed + if (best_rows < 1) { + // Screen too small even for 1 row at preferred size, use minimum pill + best_pill = MIN_PILL; + best_rows = (available_dp / MIN_PILL) - FOOTER_ROWS; + if (best_rows < 1) + best_rows = 1; // Absolute minimum + } - if (pill >= MIN_PILL && pill <= MAX_PILL) { - // Found a good fit - use it - best_rows = rows; - best_pill = pill; - break; - } else if (pill < MIN_PILL && rows > MIN_ROWS) { - // Too many rows, not enough space - try fewer rows - continue; - } else if (rows == MIN_ROWS) { - // Reached minimum rows - use closest valid pill size - if (pill < MIN_PILL) { - best_pill = MIN_PILL; - } else if (pill > MAX_PILL) { - best_pill = MAX_PILL; - } else { - best_pill = pill; - } - best_rows = rows; - } + // Recalculate actual pill height to utilize all available space + int total_rows = best_rows + FOOTER_ROWS; + best_pill = available_dp / total_rows; + + // Clamp pill size to acceptable range + if (best_pill < MIN_PILL) { + best_pill = MIN_PILL; + // Recalculate rows with minimum pill size + best_rows = (available_dp / MIN_PILL) - FOOTER_ROWS; + if (best_rows < 1) + best_rows = 1; + } else if (best_pill > MAX_PILL) { + best_pill = MAX_PILL; + // Recalculate rows with maximum pill size + best_rows = (available_dp / MAX_PILL) - FOOTER_ROWS; } // Store calculated values, adjusting DP to ensure even physical pixels @@ -384,9 +384,10 @@ SDL_Surface* GFX_init(int mode) { asset_rgbs[ASSET_DOT] = RGB_LIGHT_GRAY; asset_rgbs[ASSET_HOLE] = RGB_BLACK; - // Select asset tier based on dp_scale (round up to next available tier) + // Select asset tier based on dp_scale (use one tier higher for better quality) // Available tiers: @1x, @2x, @3x, @4x - int asset_scale = (int)ceilf(gfx_dp_scale); + // Always downscale from a higher resolution for smoother antialiasing + int asset_scale = (int)ceilf(gfx_dp_scale) + 1; if (asset_scale < 1) asset_scale = 1; if (asset_scale > 4) @@ -441,10 +442,10 @@ SDL_Surface* GFX_init(int mode) { int sheet_w = (int)(loaded_assets->w * scale_ratio + 0.5f); int sheet_h = (int)(loaded_assets->h * scale_ratio + 0.5f); - // Create a blank surface and use SDL_ConvertSurface to match loaded_assets format - // This ensures SDL sets up the format correctly (masks, etc.) - SDL_Surface* blank = - SDL_CreateRGBSurface(0, sheet_w, sheet_h, 32, 0, 0, 0, 0); // Let SDL pick format + // Create destination surface matching loaded_assets format + // Use RGBA8888 initially to preserve alpha during conversion + SDL_Surface* blank = SDL_CreateRGBSurface(0, sheet_w, sheet_h, 32, + RGBA_MASK_8888); gfx.assets = SDL_ConvertSurface(blank, loaded_assets->format, 0); SDL_FreeSurface(blank); @@ -490,14 +491,15 @@ SDL_Surface* GFX_init(int mode) { } // Extract this asset region from source sheet - // Use SDL_ConvertSurface to ensure proper format setup - SDL_Surface* temp_extract = SDL_CreateRGBSurface(0, src_rect.w, src_rect.h, 32, 0, 0, 0, 0); + SDL_Surface* temp_extract = SDL_CreateRGBSurface(0, src_rect.w, src_rect.h, 32, + RGBA_MASK_8888); SDL_Surface* extracted = SDL_ConvertSurface(temp_extract, loaded_assets->format, 0); SDL_FreeSurface(temp_extract); SDL_BlitSurface(loaded_assets, &src_rect, extracted, &(SDL_Rect){0, 0}); // Scale this specific asset to its target size - SDL_Surface* temp_scaled = SDL_CreateRGBSurface(0, target_w, target_h, 32, 0, 0, 0, 0); + SDL_Surface* temp_scaled = SDL_CreateRGBSurface(0, target_w, target_h, 32, + RGBA_MASK_8888); SDL_Surface* scaled = SDL_ConvertSurface(temp_scaled, loaded_assets->format, 0); SDL_FreeSurface(temp_scaled); GFX_scaleBilinear(extracted, scaled); From 71fe37743e1bb7777faa4736e09c10c169a86f4d Mon Sep 17 00:00:00 2001 From: Nick Chapman Date: Wed, 26 Nov 2025 11:14:07 -0800 Subject: [PATCH 06/17] Add dev-deploy scripts for faster on device testing. --- makefile | 22 +++++++++- scripts/dev-deploy.sh | 99 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 120 insertions(+), 1 deletion(-) create mode 100755 scripts/dev-deploy.sh diff --git a/makefile b/makefile index 33e69d26..ea34b68f 100644 --- a/makefile +++ b/makefile @@ -67,7 +67,7 @@ export LOG_FLAGS MINARCH_CORES_VERSION ?= 20251119 CORES_BASE = https://github.com/nchapman/minarch-cores/releases/download/$(MINARCH_CORES_VERSION) -.PHONY: build test lint format dev dev-run dev-run-4x3 dev-run-16x9 dev-clean all shell name clean setup done cores-download +.PHONY: build test lint format dev dev-run dev-run-4x3 dev-run-16x9 dev-clean all shell name clean setup done cores-download dev-deploy dev-build-deploy export MAKEFLAGS=--no-print-directory @@ -114,6 +114,26 @@ dev-run-4x3: dev-run-16x9: @make -f makefile.dev dev-run-16x9 +# Deploy to SD card for rapid dev iteration (skips zip/unzip) +# Usage: make dev-deploy - Deploy all platforms +# make dev-deploy PLATFORM=X - Deploy single platform +dev-deploy: + @if [ -n "$(PLATFORM)" ]; then \ + ./scripts/dev-deploy.sh --platform $(PLATFORM); \ + else \ + ./scripts/dev-deploy.sh; \ + fi + +# Build and deploy in one shot for dev iteration +# Usage: make dev-build-deploy - Build all platforms and deploy +# make dev-build-deploy PLATFORM=miyoomini - Build and deploy single platform +dev-build-deploy: + @if [ -n "$(PLATFORM)" ]; then \ + make common PLATFORM=$(PLATFORM) && ./scripts/dev-deploy.sh --platform $(PLATFORM); \ + else \ + make all && ./scripts/dev-deploy.sh; \ + fi + # Build all components for a specific platform (in Docker) build: # ---------------------------------------------------- diff --git a/scripts/dev-deploy.sh b/scripts/dev-deploy.sh new file mode 100755 index 00000000..dd0701df --- /dev/null +++ b/scripts/dev-deploy.sh @@ -0,0 +1,99 @@ +#!/bin/bash +# Deploy built files directly to SD card for rapid development iteration +# +# Usage: ./scripts/deploy-dev.sh [options] +# +# Options: +# --no-update Skip .tmp_update (won't trigger update on device boot) +# --platform X Only sync specific platform from .system (e.g., miyoomini) +# +# Prerequisites: +# - Run 'make all' or 'make setup && make common PLATFORM=' first +# - SD card must be mounted at /Volumes/LESSUI_DEV +# +# What this does: +# Copies build/PAYLOAD/.system and build/PAYLOAD/.tmp_update directly to +# the SD card, bypassing the zip/unzip cycle. Much faster than the full +# workflow of: make -> copy zip -> load SD -> wait for device to unpack + +set -e + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" +SD_CARD="/Volumes/LESSUI_DEV" +PAYLOAD_DIR="$PROJECT_ROOT/build/PAYLOAD" + +# Parse arguments +SYNC_UPDATE=true +PLATFORM_FILTER="" + +while [[ $# -gt 0 ]]; do + case $1 in + --no-update) + SYNC_UPDATE=false + shift + ;; + --platform) + PLATFORM_FILTER="$2" + shift 2 + ;; + *) + echo "Unknown option: $1" + echo "Usage: $0 [--no-update] [--platform ]" + exit 1 + ;; + esac +done + +# Verify SD card is mounted +if [ ! -d "$SD_CARD" ]; then + echo "Error: SD card not mounted at $SD_CARD" + echo "Insert the SD card and wait for it to mount." + exit 1 +fi + +# Verify build exists +if [ ! -d "$PAYLOAD_DIR/.system" ]; then + echo "Error: Build not found at $PAYLOAD_DIR/.system" + echo "Run 'make all' or 'make setup && make common PLATFORM=' first." + exit 1 +fi + +echo "Deploying to $SD_CARD..." + +# Common rsync options: +# -a: archive mode (preserves permissions, etc.) +# -v: verbose +# --exclude: skip macOS metadata files +RSYNC_OPTS="-av --exclude=.DS_Store --exclude=._*" + +# Sync .system directory +if [ -n "$PLATFORM_FILTER" ]; then + # Only sync specific platform + shared files + echo " Syncing .system/$PLATFORM_FILTER..." + rsync $RSYNC_OPTS --delete "$PAYLOAD_DIR/.system/$PLATFORM_FILTER/" "$SD_CARD/.system/$PLATFORM_FILTER/" + + # Also sync shared resources + echo " Syncing shared resources..." + rsync $RSYNC_OPTS "$PAYLOAD_DIR/.system/res/" "$SD_CARD/.system/res/" + rsync $RSYNC_OPTS "$PAYLOAD_DIR/.system/common/" "$SD_CARD/.system/common/" + rsync $RSYNC_OPTS "$PAYLOAD_DIR/.system/cores/" "$SD_CARD/.system/cores/" + [ -f "$PAYLOAD_DIR/.system/version.txt" ] && cp "$PAYLOAD_DIR/.system/version.txt" "$SD_CARD/.system/" +else + # Sync entire .system directory + echo " Syncing .system/..." + rsync $RSYNC_OPTS --delete "$PAYLOAD_DIR/.system/" "$SD_CARD/.system/" +fi + +# Optionally sync .tmp_update (triggers update on boot) +if [ "$SYNC_UPDATE" = true ]; then + echo " Syncing .tmp_update/..." + rsync $RSYNC_OPTS --delete "$PAYLOAD_DIR/.tmp_update/" "$SD_CARD/.tmp_update/" +else + echo " Skipping .tmp_update (--no-update specified)" +fi + +# Eject the SD card +echo "" +echo "Ejecting SD card..." +diskutil eject "$SD_CARD" && echo "Done! SD card ejected." || echo "Warning: Failed to eject. You may need to eject manually." From 9227fa48e7edd33d1c6a40c76011886c6e4bf3ae Mon Sep 17 00:00:00 2001 From: Nick Chapman Date: Wed, 26 Nov 2025 11:14:50 -0800 Subject: [PATCH 07/17] Fix asset transparency on RGB565 devices. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Keep asset surfaces in RGBA8888 format throughout the scaling pipeline instead of converting to loaded_assets->format. Disable SDL_SRCALPHA before intermediate blits to copy RGBA data directly rather than alpha-blending (which causes transparent pixels to blend to black when the destination is uninitialized). SDL handles RGBA→RGB565 conversion with proper alpha blending at final screen blit time. --- workspace/all/common/api.c | 32 +++++++++++++++----------------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/workspace/all/common/api.c b/workspace/all/common/api.c index 1bf28ec9..ef051e3c 100644 --- a/workspace/all/common/api.c +++ b/workspace/all/common/api.c @@ -442,12 +442,9 @@ SDL_Surface* GFX_init(int mode) { int sheet_w = (int)(loaded_assets->w * scale_ratio + 0.5f); int sheet_h = (int)(loaded_assets->h * scale_ratio + 0.5f); - // Create destination surface matching loaded_assets format - // Use RGBA8888 initially to preserve alpha during conversion - SDL_Surface* blank = SDL_CreateRGBSurface(0, sheet_w, sheet_h, 32, - RGBA_MASK_8888); - gfx.assets = SDL_ConvertSurface(blank, loaded_assets->format, 0); - SDL_FreeSurface(blank); + // 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 @@ -490,21 +487,22 @@ SDL_Surface* GFX_init(int mode) { // Their internal positioning is handled by proportional offsets in GFX_blitBattery } - // Extract this asset region from source sheet - SDL_Surface* temp_extract = SDL_CreateRGBSurface(0, src_rect.w, src_rect.h, 32, - RGBA_MASK_8888); - SDL_Surface* extracted = SDL_ConvertSurface(temp_extract, loaded_assets->format, 0); - SDL_FreeSurface(temp_extract); + // Extract this asset region from source sheet into RGBA8888 surface + SDL_Surface* extracted = SDL_CreateRGBSurface(0, src_rect.w, src_rect.h, 32, + RGBA_MASK_8888); + // Disable alpha-blending to copy RGBA data directly (not blend with dst) + SDLX_SetAlpha(loaded_assets, 0, 0); SDL_BlitSurface(loaded_assets, &src_rect, extracted, &(SDL_Rect){0, 0}); + SDLX_SetAlpha(loaded_assets, SDL_SRCALPHA, 0); // Re-enable for later - // Scale this specific asset to its target size - SDL_Surface* temp_scaled = SDL_CreateRGBSurface(0, target_w, target_h, 32, - RGBA_MASK_8888); - SDL_Surface* scaled = SDL_ConvertSurface(temp_scaled, loaded_assets->format, 0); - SDL_FreeSurface(temp_scaled); - GFX_scaleBilinear(extracted, scaled); + // Scale this specific asset to its target size (keep in RGBA8888) + SDL_Surface* scaled = SDL_CreateRGBSurface(0, target_w, target_h, 32, + RGBA_MASK_8888); + GFX_scaleBilinear(extracted, scaled); // Direct pixel copy preserves alpha // Place the scaled asset into the destination sheet + // Disable alpha-blending to copy RGBA data directly + SDLX_SetAlpha(scaled, 0, 0); SDL_BlitSurface(scaled, NULL, gfx.assets, &dst_rect); // Update asset rectangle with new position and dimensions From 567d30a3182dba830d0fc971bd69c7602cad3d32 Mon Sep 17 00:00:00 2001 From: Nick Chapman Date: Wed, 26 Nov 2025 12:03:54 -0800 Subject: [PATCH 08/17] Adjust DP baseline from 160 to 144 PPI for handheld gaming devices. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The previous 160 PPI baseline (Android MDPI) was designed for smartphones held at arm's length. Retro handhelds are held closer (12-18") and prioritize readability for game lists over information density. The 144 PPI baseline (12²) provides: - ~11% larger UI elements across all devices - Better readability for gaming-focused interfaces - Typographically favorable number with excellent divisibility - Single constant works perfectly across 47+ device configurations Also adjusted desktop platform's virtual screen diagonal from 4.0" to 2.78" to maintain dp_scale ≈ 2.0 for consistent development environment. --- workspace/all/common/api.c | 9 ++++----- workspace/all/common/api.h | 4 ++-- workspace/desktop/platform/platform.h | 4 ++-- 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/workspace/all/common/api.c b/workspace/all/common/api.c index ef051e3c..df671774 100644 --- a/workspace/all/common/api.c +++ b/workspace/all/common/api.c @@ -61,7 +61,7 @@ uint32_t RGB_DARK_GRAY; // Display Points (DP) scaling system /////////////////////////////// -// Global display scale factor (PPI / 160) +// Global display scale factor (PPI / 144) float gfx_dp_scale = 2.0f; // Default to 2x until UI_initLayout is called // Runtime-calculated UI layout parameters @@ -83,9 +83,8 @@ UI_Layout ui = { * * DP Scale Calculation: * 1. Calculate PPI: sqrt(width² + height²) / diagonal_inches - * 2. Calculate raw dp_scale: ppi / 160.0 (Android MDPI baseline) - * 3. Snap to favorable fraction (4/3, 3/2, 5/3, etc.) for cleaner rounding - * 4. Apply optional SCALE_MODIFIER if defined in platform.h + * 2. Calculate raw dp_scale: ppi / 144.0 (handheld gaming device baseline) + * 3. Apply optional SCALE_MODIFIER if defined in platform.h * * Row Fitting Algorithm: * - Try 8→6 rows (prefer more content) @@ -104,7 +103,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 / 160.0f; + float raw_dp_scale = ppi / 144.0f; // Apply platform scale modifier if defined #ifdef SCALE_MODIFIER diff --git a/workspace/all/common/api.h b/workspace/all/common/api.h index fb87aa60..2eabc54f 100644 --- a/workspace/all/common/api.h +++ b/workspace/all/common/api.h @@ -32,9 +32,9 @@ * * Core formula: * ppi = sqrt(width² + height²) / diagonal_inches - * dp_scale = ppi / 160.0 (160 = Android MDPI baseline) + * dp_scale = ppi / 144.0 (144 = handheld gaming device baseline) * - * Example: Miyoo Mini (640x480, 2.8") → 286 PPI → dp_scale ≈ 1.79 + * Example: Miyoo Mini (640x480, 2.8") → 286 PPI → dp_scale ≈ 1.99 * * Usage: * DP(30) // Convert 30dp to physical pixels diff --git a/workspace/desktop/platform/platform.h b/workspace/desktop/platform/platform.h index 9e77126e..fb17e164 100644 --- a/workspace/desktop/platform/platform.h +++ b/workspace/desktop/platform/platform.h @@ -129,8 +129,8 @@ // Display Specifications /////////////////////////////// -// Desktop uses a virtual 4" screen at VGA resolution to get dp_scale ≈ 2.0 -#define SCREEN_DIAGONAL 4.0f // Virtual screen diagonal for consistent scaling +// Desktop uses a virtual 2.78" screen at VGA resolution to get dp_scale ≈ 2.0 (with 144 PPI baseline) +#define SCREEN_DIAGONAL 2.78f // Virtual screen diagonal for consistent scaling #define FIXED_SCALE 2 // 2x scaling factor for UI #define FIXED_WIDTH 640 // Screen width in pixels #define FIXED_HEIGHT 480 // Screen height in pixels (VGA) From 475670e0a810c33faf5a15bbefa02408a936bd75 Mon Sep 17 00:00:00 2001 From: Nick Chapman Date: Wed, 26 Nov 2025 12:55:56 -0800 Subject: [PATCH 09/17] Fix row fitting algorithm to properly fill vertical space. The previous algorithm had several issues: 1. Used FOOTER_ROWS constant that double-counted padding 2. Had arbitrary 8-row maximum limit 3. Required even pixels as hard constraint, causing failures New algorithm: - Treats footer as another row with same height (simpler model) - Calculates max possible rows from MIN_PILL (no arbitrary limit) - Prefers even pixels but accepts odd as fallback - Early exit optimization when pill > MAX_PILL Results across 33 devices: - 98.5% average fill (was ~88% with gaps) - 5-13 rows depending on screen size - Only 7.7px average waste Also fixed desktop development environment: - Changed PLATFORM from "macos" to "desktop" to match .pak directory - Added keyboard controls help to dev-run output - Fixed FAKESD_PATH to use pwd -P for symlink resolution Added scripts/analyze-row-fitting.py for testing algorithm against all supported handheld devices before implementation. --- makefile.dev | 12 ++- scripts/analyze-row-fitting.py | 191 +++++++++++++++++++++++++++++++++ workspace/all/common/api.c | 104 +++++++++--------- 3 files changed, 254 insertions(+), 53 deletions(-) create mode 100755 scripts/analyze-row-fitting.py diff --git a/makefile.dev b/makefile.dev index 44fc00da..dd864e09 100644 --- a/makefile.dev +++ b/makefile.dev @@ -30,10 +30,10 @@ help: @echo " brew install sdl2 sdl2_image sdl2_ttf" # macOS native build configuration -PLATFORM = macos +PLATFORM = desktop SDL_INCLUDES = -I/opt/homebrew/include SDL_LIBS = -L/opt/homebrew/lib -FAKESD_PATH = $(shell pwd)/workspace/desktop/FAKESD +FAKESD_PATH = $(shell pwd -P)/workspace/desktop/FAKESD # Aspect ratio configuration (4x3 or 16x9) # Can be overridden: ASPECT_RATIO=16x9 make dev-run @@ -111,6 +111,14 @@ dev-run: dev @echo "Launching minui..." @echo "FAKESD path: $(FAKESD_PATH)" @echo "" + @echo "Keyboard controls:" + @echo " Arrow keys - D-pad navigation" + @echo " Q/W/A/S - Y/X/B/A buttons" + @echo " Enter - Start" + @echo " 4 - Select" + @echo " Space - Menu" + @echo " Backspace - Power (hold to quit)" + @echo "" @cd workspace/all/minui && ./build/desktop/minui # Clean build artifacts diff --git a/scripts/analyze-row-fitting.py b/scripts/analyze-row-fitting.py new file mode 100755 index 00000000..e9fc9af1 --- /dev/null +++ b/scripts/analyze-row-fitting.py @@ -0,0 +1,191 @@ +#!/usr/bin/env python3 +""" +Analyze row fitting across all retro handheld devices. + +Simulates the UI_initLayout algorithm to see how well different MIN/MAX_PILL +ranges work across all supported devices. +""" + +import math +import re + +# Device configurations: (name, width, height, diagonal_inches) +DEVICES = [ + # Cortex-A7 (ARMv7) + ("Miyoo Mini", 640, 480, 2.8), + ("Miyoo Mini Plus", 640, 480, 2.8), + ("Miyoo A30", 640, 480, 2.8), + + # Cortex-A35 (RK3326) + ("RG-351P/M", 480, 320, 3.5), + ("RG-351V/MP", 640, 480, 3.5), + ("RGB10/10S/20", 480, 320, 3.5), + ("RGB20S", 640, 480, 3.5), + ("RGB10 Max/Max2", 854, 480, 5.0), + ("GameForce Chi", 640, 480, 3.5), + ("Odroid Go Advance", 320, 480, 3.5), + ("Odroid Go Super", 854, 480, 5.0), + ("GKD Pixel 2", 640, 480, 2.4), + + # Cortex-A53 (H700) + ("RG-28XX", 640, 480, 2.8), + ("RG-34XX/SP", 720, 480, 3.4), + ("RG-35XX H/Plus/SP", 640, 480, 3.5), + ("RG-40XX H/V", 640, 480, 4.0), + ("RG Cube XX", 720, 720, 3.95), + ("GKD Bubble", 640, 480, 3.5), + + # Cortex-A53 (A133 Plus) + ("Trimui Brick", 1024, 768, 3.2), + ("Trimui Smart Pro", 1280, 720, 4.95), + ("MagicX Mini Zero 28", 640, 480, 2.8), + + # Cortex-A55 (RK3566) + ("Miyoo Flip", 640, 480, 3.5), + ("RG353M/V/P", 640, 480, 3.5), + ("RG-503", 960, 544, 4.95), + ("RG ARC-D/S", 640, 480, 4.0), + ("RGB30", 720, 720, 4.0), + ("PowKiddy X55", 1280, 720, 5.5), + ("PowKiddy RK2023", 640, 480, 3.5), + ("GKD Mini Plus", 640, 480, 3.5), + + # Cortex-A76 (RK3588) + ("RG406H/V", 960, 540, 4.0), + ("RG556", 1920, 1152, 5.48), + + # Snapdragon + ("Retroid Pocket 5", 1920, 1080, 5.5), + ("Retroid Pocket Mini", 960, 640, 3.7), +] + +def simulate_layout(width, height, diagonal, min_pill, max_pill, baseline_ppi=144): + """Simulate the UI_initLayout algorithm.""" + # Calculate PPI and dp_scale + diagonal_px = math.sqrt(width**2 + height**2) + ppi = diagonal_px / diagonal + dp_scale = ppi / baseline_ppi + + # Available space (screen - top/bottom padding) + screen_height_dp = int(height / dp_scale + 0.5) + padding = 10 + available_dp = screen_height_dp - (padding * 2) + + # Try different row counts (content + 1 footer) + # Start from maximum possible rows (when pill = MIN_PILL) and work down + # Prefer even pixels but accept odd as fallback + max_possible_rows = (available_dp // min_pill) - 1 # -1 for footer + if max_possible_rows < 1: + max_possible_rows = 1 + + best_pill = 0 + best_rows = 0 + best_is_even = False + + for content_rows in range(max_possible_rows, 0, -1): + total_rows = content_rows + 1 # +1 for footer + pill = available_dp // total_rows + + # Early exit: pills only get larger as rows decrease + if pill > max_pill: + break + + pill_px = int(pill * dp_scale + 0.5) + is_even = (pill_px % 2 == 0) + + if pill >= min_pill: + if is_even: + # Perfect: in range AND even + best_pill = pill + best_rows = content_rows + best_is_even = True + break + elif best_rows == 0: + # Acceptable but odd - keep as backup + best_pill = pill + best_rows = content_rows + + # Emergency fallback + if best_rows == 0: + best_pill = min_pill + best_rows = 1 + + # Calculate actual usage + used_dp = (best_rows + 1) * best_pill + wasted_dp = available_dp - used_dp + wasted_px = int(wasted_dp * dp_scale + 0.5) + fill_pct = (used_dp / available_dp) * 100 if available_dp > 0 else 0 + pill_px = int(best_pill * dp_scale + 0.5) + font_px = int(16 * dp_scale + 0.5) # FONT_LARGE = 16dp + + return { + 'ppi': ppi, + 'dp_scale': dp_scale, + 'screen_dp': screen_height_dp, + 'available_dp': available_dp, + 'rows': best_rows, + 'pill_dp': best_pill, + 'pill_px': pill_px, + 'font_px': font_px, + 'font_ratio': (font_px / pill_px) if pill_px > 0 else 0, + 'used_dp': used_dp, + 'wasted_dp': wasted_dp, + 'wasted_px': wasted_px, + 'fill_pct': fill_pct, + } + +def analyze_range(min_pill, max_pill): + """Analyze how well a pill range works across all devices.""" + print(f"\n{'='*100}") + print(f"ANALYZING RANGE: {min_pill}-{max_pill}dp") + print(f"{'='*100}\n") + + results = [] + for name, width, height, diagonal in DEVICES: + result = simulate_layout(width, height, diagonal, min_pill, max_pill) + results.append((name, width, height, result)) + + # Print detailed results + print(f"{'Device':<25} {'Screen':<12} {'PPI':>5} {'Scale':>5} {'Rows':>4} {'Pill':>8} {'Font':>5} {'F/P':>5} {'Waste':>6} {'Fill':>5}") + print(f"{'-'*25} {'-'*12} {'-'*5} {'-'*5} {'-'*4} {'-'*8} {'-'*5} {'-'*5} {'-'*6} {'-'*5}") + + for name, width, height, r in results: + print(f"{name:<25} {width:>4}x{height:<4} {r['ppi']:>5.0f} {r['dp_scale']:>5.2f} " + f"{r['rows']:>4} {r['pill_dp']:>3}dp/{r['pill_px']:>2}px " + f"{r['font_px']:>3}px {r['font_ratio']*100:>4.0f}% " + f"{r['wasted_px']:>4}px {r['fill_pct']:>4.1f}%") + + # Summary statistics + print(f"\n{'Summary Statistics:':<25}") + print(f"{'-'*50}") + + row_counts = [r['rows'] for _, _, _, r in results] + fill_pcts = [r['fill_pct'] for _, _, _, r in results] + wasted_pxs = [r['wasted_px'] for _, _, _, r in results] + font_ratios = [r['font_ratio'] for _, _, _, r in results] + + print(f"{'Row counts:':<25} {min(row_counts)}-{max(row_counts)} (avg {sum(row_counts)/len(row_counts):.1f})") + print(f"{'Fill percentage:':<25} {min(fill_pcts):.1f}%-{max(fill_pcts):.1f}% (avg {sum(fill_pcts)/len(fill_pcts):.1f}%)") + print(f"{'Wasted pixels:':<25} {min(wasted_pxs)}-{max(wasted_pxs)}px (avg {sum(wasted_pxs)/len(wasted_pxs):.1f}px)") + print(f"{'Font/Pill ratio:':<25} {min(font_ratios)*100:.0f}%-{max(font_ratios)*100:.0f}% (avg {sum(font_ratios)/len(font_ratios)*100:.0f}%)") + + # Identify problematic devices + poor_fill = [(name, r['fill_pct']) for name, _, _, r in results if r['fill_pct'] < 95] + if poor_fill: + print(f"\n{'Devices with <95% fill:':<25}") + for name, pct in poor_fill: + print(f" {name:<25} {pct:.1f}%") + + high_waste = [(name, r['wasted_px']) for name, _, _, r in results if r['wasted_px'] > 20] + if high_waste: + print(f"\n{'Devices wasting >20px:':<25}") + for name, px in high_waste: + print(f" {name:<25} {px}px") + +if __name__ == '__main__': + # Current range + analyze_range(28, 38) + + # Alternatives for comparison + analyze_range(28, 32) # Original narrow range + analyze_range(30, 40) # Shifted up diff --git a/workspace/all/common/api.c b/workspace/all/common/api.c index df671774..bb00c15f 100644 --- a/workspace/all/common/api.c +++ b/workspace/all/common/api.c @@ -114,73 +114,75 @@ void UI_initLayout(int screen_width, int screen_height, float diagonal_inches) { // Asset-level even-pixel adjustments handle rounding where needed gfx_dp_scale = raw_dp_scale; - // Bounds for layout calculation + // Layout calculation: treat everything as uniform rows + // Screen layout: top_padding + content_rows + footer_row + bottom_padding + // All rows (content + footer) use the same pill_height for visual consistency const int MIN_PILL = 28; const int MAX_PILL = 32; - const int PREFERRED_PILL = 30; - const int DEFAULT_PADDING = 10; // Consistent padding in dp - const int FOOTER_ROWS = 2; // Header spacing + footer button hints row + const int DEFAULT_PADDING = 10; - // Calculate available height in dp int screen_height_dp = (int)(screen_height / gfx_dp_scale + 0.5f); int available_dp = screen_height_dp - (DEFAULT_PADDING * 2); - // Dynamic row fitting: calculate how many rows fit in available space - // Start with preferred pill size and calculate maximum rows - int best_pill = PREFERRED_PILL; - int best_rows = (available_dp / PREFERRED_PILL) - FOOTER_ROWS; + // Calculate maximum possible rows (no arbitrary limit) + int max_possible_rows = (available_dp / MIN_PILL) - 1; // -1 for footer + if (max_possible_rows < 1) + max_possible_rows = 1; - // Ensure we have at least some rows, adjust pill size if needed - if (best_rows < 1) { - // Screen too small even for 1 row at preferred size, use minimum pill - best_pill = MIN_PILL; - best_rows = (available_dp / MIN_PILL) - FOOTER_ROWS; - if (best_rows < 1) - best_rows = 1; // Absolute minimum - } + // Try different row counts, starting from maximum (prefer more content) + // Prefer even pixels but accept odd if needed + int best_pill = 0; + int best_rows = 0; + int best_is_even = 0; + + for (int content_rows = max_possible_rows; content_rows >= 1; content_rows--) { + int total_rows = content_rows + 1; // +1 for footer + int pill = available_dp / total_rows; + + // Early exit: pills only get larger as rows decrease + if (pill > MAX_PILL) + break; + + int pill_px = DP(pill); + int is_even = (pill_px % 2 == 0); - // Recalculate actual pill height to utilize all available space - int total_rows = best_rows + FOOTER_ROWS; - best_pill = available_dp / total_rows; + if (pill >= MIN_PILL) { + if (is_even) { + // Perfect: in range AND even pixels + best_pill = pill; + best_rows = content_rows; + best_is_even = 1; + LOG_info("Row calc: %d rows → pill=%ddp (%dpx even) ✓\n", content_rows, pill, pill_px); + break; + } else if (best_rows == 0) { + // Acceptable but odd pixels - keep as backup + best_pill = pill; + best_rows = content_rows; + LOG_info("Row calc: %d rows → pill=%ddp (%dpx odd) - backup\n", content_rows, pill, + pill_px); + } + } + } - // Clamp pill size to acceptable range - if (best_pill < MIN_PILL) { + if (best_is_even) { + LOG_info("Row calc: Using even-pixel configuration\n"); + } else if (best_rows > 0) { + LOG_info("Row calc: Using odd-pixel fallback (no even option in range)\n"); + } else { + // Emergency fallback (should never happen with reasonable MIN_PILL) best_pill = MIN_PILL; - // Recalculate rows with minimum pill size - best_rows = (available_dp / MIN_PILL) - FOOTER_ROWS; - if (best_rows < 1) - best_rows = 1; - } else if (best_pill > MAX_PILL) { - best_pill = MAX_PILL; - // Recalculate rows with maximum pill size - best_rows = (available_dp / MAX_PILL) - FOOTER_ROWS; + best_rows = 1; + LOG_info("Row calc: EMERGENCY FALLBACK to %ddp, 1 row\n", MIN_PILL); } - // Store calculated values, adjusting DP to ensure even physical pixels ui.pill_height = best_pill; ui.row_count = best_rows; ui.padding = DEFAULT_PADDING; - // Adjust pill_height so it produces even physical pixels (for alignment) - // May need to adjust by 2dp if dp_scale is fractional (e.g., 1.5: 30→45, 31→47, 32→48) - int pill_px = DP(ui.pill_height); - if (pill_px % 2 != 0) { - // Try increasing first (prefer slightly larger) - int adjusted = ui.pill_height + 1; - while (adjusted <= MAX_PILL && DP(adjusted) % 2 != 0) { - adjusted++; - } - if (adjusted <= MAX_PILL) { - ui.pill_height = adjusted; - } else { - // Can't increase, try decreasing - adjusted = ui.pill_height - 1; - while (adjusted >= MIN_PILL && DP(adjusted) % 2 != 0) { - adjusted--; - } - ui.pill_height = adjusted; - } - } + int used_dp = (ui.row_count + 1) * ui.pill_height; + LOG_info("Row calc: FINAL rows=%d, pill=%ddp (%dpx), using %d/%d dp (%.1f%%)\n", ui.row_count, + ui.pill_height, DP(ui.pill_height), used_dp, available_dp, + (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 From 6177b313ee72211eaca902ba03f90bb60cc9e279 Mon Sep 17 00:00:00 2001 From: Nick Chapman Date: Wed, 26 Nov 2025 13:44:58 -0800 Subject: [PATCH 10/17] Fix minarch main menu centering. --- workspace/all/minarch/minarch.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/workspace/all/minarch/minarch.c b/workspace/all/minarch/minarch.c index e7e9366c..5ef618ba 100644 --- a/workspace/all/minarch/minarch.c +++ b/workspace/all/minarch/minarch.c @@ -5104,7 +5104,7 @@ static int Menu_options(MenuList* list) { // dependent on option list offset top and bottom, eg. the gray triangles int max_visible_options = - (screen->h - ((DP(ui.padding + ui.pill_height) * 2) + DP(ui.button_size))) / + (screen->h - DP((ui.padding + ui.pill_height) * 2)) / DP(ui.button_size); // 7 for 480, 10 for 720 int count; @@ -5961,9 +5961,9 @@ static void Menu_loop(void) { GFX_blitButtonGroup((char*[]){"B", "BACK", "A", "OKAY", NULL}, 1, screen, 1); // Vertically center the menu items - oy = (((DEVICE_HEIGHT / FIXED_SCALE) - DP(ui.padding * 2)) - - DP(MENU_ITEM_COUNT * ui.pill_height)) / - 2; + int menu_height_px = DP((ui.padding * 2) + (MENU_ITEM_COUNT * ui.pill_height)); + oy = (screen->h - menu_height_px) / 2; + for (int i = 0; i < MENU_ITEM_COUNT; i++) { char* item = menu.items[i]; SDL_Color text_color = COLOR_WHITE; From 67309221004a0a282bb8bfd85a7aa6017dbc6749 Mon Sep 17 00:00:00 2001 From: Nick Chapman Date: Wed, 26 Nov 2025 14:51:02 -0800 Subject: [PATCH 11/17] Fix menu centering in minarch. --- workspace/all/minarch/minarch.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/workspace/all/minarch/minarch.c b/workspace/all/minarch/minarch.c index 5ef618ba..532c4776 100644 --- a/workspace/all/minarch/minarch.c +++ b/workspace/all/minarch/minarch.c @@ -5961,8 +5961,8 @@ static void Menu_loop(void) { GFX_blitButtonGroup((char*[]){"B", "BACK", "A", "OKAY", NULL}, 1, screen, 1); // Vertically center the menu items - int menu_height_px = DP((ui.padding * 2) + (MENU_ITEM_COUNT * ui.pill_height)); - oy = (screen->h - menu_height_px) / 2; + int menu_height_dp = ui.padding + (MENU_ITEM_COUNT * ui.pill_height); + oy = (screen->h - DP(menu_height_dp)) / 2 / DP(1); for (int i = 0; i < MENU_ITEM_COUNT; i++) { char* item = menu.items[i]; From 608272dfadc451a1b34dbb1634330e46c9d2f512 Mon Sep 17 00:00:00 2001 From: Nick Chapman Date: Wed, 26 Nov 2025 16:07:25 -0800 Subject: [PATCH 12/17] Add lower level dp helpers and implement throughout. --- docs/dp-best-practices.md | 562 ++++++++++++++++++++++++++++++++ docs/transparency-debugging.md | 52 ++- workspace/all/clock/clock.c | 16 +- workspace/all/common/api.c | 10 +- workspace/all/common/api.h | 161 ++++++++- workspace/all/minarch/minarch.c | 35 +- workspace/all/minui/minui.c | 17 +- workspace/all/say/say.c | 7 +- 8 files changed, 812 insertions(+), 48 deletions(-) create mode 100644 docs/dp-best-practices.md diff --git a/docs/dp-best-practices.md b/docs/dp-best-practices.md new file mode 100644 index 00000000..1ee2381d --- /dev/null +++ b/docs/dp-best-practices.md @@ -0,0 +1,562 @@ +# DP (Display Points) Best Practices + +## Quick Reference + +| Need | Use | +|------|-----| +| Screen dimensions for UI | `ui.screen_width`, `ui.screen_height` (DP) | +| Screen dimensions in pixels | `ui.screen_width_px`, `ui.screen_height_px` | +| Convert DP to pixels | `DP(value)` or `DP_RECT(x,y,w,h)` | +| Convert pixels to DP | `PX_TO_DP(value)` | +| Draw pill at DP coords | `GFX_blitPill_DP(asset, dst, x_dp, y_dp, w_dp, h_dp)` | +| Draw rect at DP coords | `GFX_blitRect_DP(asset, dst, x_dp, y_dp, w_dp, h_dp)` | +| Draw message at DP coords | `GFX_blitMessage_DP(font, msg, dst, x, y, w, h)` | +| Get text size in DP | `TTF_SizeUTF8_DP(font, text, &w_dp, &h_dp)` | +| Blit at DP position | `SDL_BlitSurface_DP(src, srcrect, dst, x_dp, y_dp)` | + +## Overview + +LessUI uses a DP (display points) system similar to Android's density-independent pixel system to ensure consistent UI sizing across devices with different screen densities. This document establishes best practices for working with DP correctly. + +## Core Concepts + +### What are DP? + +- **DP (Display Points)**: Abstract units representing a consistent physical size across devices +- **Pixels**: Physical screen pixels that vary by device density +- **Conversion**: `pixels = dp × scale_factor` (via `DP()` macro) + +### Why DP? + +- A 30dp button looks the same physical size on a 480p screen and a 720p screen +- UI layouts work consistently across all supported devices (12+ platforms) +- Eliminates per-device manual tuning + +### Why 144 PPI as Baseline? + +The baseline density of 144 PPI was chosen for two key reasons: + +**1. Optimal readability for handheld devices** + +At typical handheld viewing distances (25-30cm), 144 PPI produces ideal text sizes: + +| 16dp text height | Visual angle @ 27cm | +|------------------|---------------------| +| 2.83mm | ~36 arcmin | + +ISO recommends 20-22 arcmin minimum for comfortable reading. 36 arcmin is well within the "super readable" zone while remaining compact for small screens. + +**2. Perfect typographic relationship** + +144 PPI creates an elegant mapping to traditional typography: + +- Typography defines 1 point = 1/72 inch +- At 144 PPI: **1 pt = 2 px** (in DP terms) +- Therefore: **1 dp = 0.5 pt** + +This yields clean conversions to standard typographic sizes: + +| DP | Points | Common Use | +|-----|--------|-------------------| +| 16 | 8 pt | Small text | +| 24 | 12 pt | Body text | +| 32 | 16 pt | Headers | +| 48 | 24 pt | Large titles | + +## The Fundamental Rule + +**If it needs to scale across devices, it MUST use DP. Otherwise the scaling will be wrong.** + +This is non-negotiable. Any UI element that should maintain consistent physical size across devices (buttons, padding, text size, menu items, etc.) must be defined and calculated in DP. + +## The Rules + +### Rule 1: UI Layout MUST Use DP +**If it needs to scale across devices, it MUST use DP-based values.** + +- ✅ Use `ui.screen_width` and `ui.screen_height` (in DP) +- ❌ Don't use `screen->w` and `screen->h` (raw pixels) for UI layout + +### Rule 2: Pixels and DP are Compatible +**DP() converts to pixels, so you can mix them in calculations.** + +```c +int total_px = DP(ui.padding) + text_width_px; // Both are pixels ✓ +``` + +### Rule 3: Mark Your Units Clearly +**Use variable naming to show what units you're working in.** + +- `menu_height_dp` - In DP units +- `text_width_px` - In pixels +- `centered_x_px` - In pixels (result of pixel-based calculation) + +### Rule 4: Screen Dimensions +**For UI layout:** +- ✅ Use `ui.screen_width` and `ui.screen_height` (DP) +- ❌ Don't use `screen->w` and `screen->h` (raw pixels) + +**Exception:** Emulation rendering, video scaling, DEVICE_* constants can use raw pixels. + +## Correct Patterns + +### ✅ Pattern 1: Use DP-native wrapper functions (PREFERRED) + +```c +// Calculate in DP +int menu_height_dp = ui.padding + (MENU_ITEM_COUNT * ui.pill_height); +int y_offset_dp = (ui.screen_height - menu_height_dp) / 2; +int item_y_dp = y_offset_dp + ui.padding + (i * ui.pill_height); + +// Use DP wrapper - clean and clear! +GFX_blitPill_DP(ASSET_WHITE_PILL, screen, ui.padding, item_y_dp, width_dp, ui.pill_height); +``` + +**Why this is best:** +- Clearest code - no manual DP() conversions +- Matches Android/iOS pattern +- Impossible to accidentally mix units +- All arithmetic stays in DP units + +### ✅ Pattern 2: Manual DP conversion (when wrapper not available) + +```c +// Calculate in DP +int item_y_dp = y_offset_dp + ui.padding + (i * ui.pill_height); + +// Convert to pixels when passing to SDL (if no DP wrapper exists) +SDL_Rect rect = {DP(ui.padding), DP(item_y_dp), DP(width_dp), DP(ui.pill_height)}; +GFX_blitPill(asset, screen, &rect); +``` + +**When to use:** +- Wrapper function doesn't exist yet +- Working with direct SDL calls + +### ✅ Pattern 2: Use ui.screen_width and ui.screen_height + +```c +// CORRECT - work in DP +int centered_x_dp = (ui.screen_width - element_width_dp) / 2; +int centered_y_dp = (ui.screen_height - element_height_dp) / 2; + +// Convert to pixels when rendering +SDL_Rect rect = {DP(centered_x_dp), DP(centered_y_dp), DP(element_width_dp), DP(element_height_dp)}; +``` + +**Why this works:** +- `ui.screen_width` and `ui.screen_height` are provided in DP +- All centering math stays in DP +- Clean separation of concerns + +### ✅ Pattern 3: Mixing pixels with DP (when necessary) + +```c +// Get pixel measurement from SDL_TTF +int text_width_px; +TTF_SizeUTF8(font, text, &text_width_px, NULL); + +// DP converts to pixels, so we can mix them +int item_width_px = text_width_px + DP(ui.button_padding * 2); // pixels + pixels = OK + +// Use DP-based screen dimensions +int centered_x_px = (DP(ui.screen_width) - item_width_px) / 2; // pixels - pixels = OK + +// Use in rendering +SDL_Rect rect = {centered_x_px, DP(y_dp), item_width_px, DP(ui.button_size)}; +``` + +**Why this works:** +- `DP()` converts to pixels, so `DP(ui.button_padding * 2)` is pixels +- Variables clearly marked with `_px` suffix +- All pixel math is valid (pixels + pixels, pixels - pixels) +- Layout is still driven by DP values (ui.button_padding, ui.screen_width, etc.) + +**Key principle:** DP values define the layout, but we can convert them to pixels early if needed for calculations involving pixel-based measurements (like text width). + +## Incorrect Patterns (What NOT to Do) + +### ❌ Anti-Pattern 1: Using raw screen dimensions for UI layout + +```c +// WRONG - screen->w and screen->h are raw pixels, not scaled for density +int available_width = screen->w - DP(ui.padding * 2); +int centered_y = (screen->h - menu_height) / 2; + +// CORRECT - use DP-based screen dimensions +int available_width_px = DP(ui.screen_width - ui.padding * 2); +int centered_y_dp = (ui.screen_height - menu_height_dp) / 2; +``` + +**Why the first is wrong:** +- `screen->w` and `screen->h` are raw pixel dimensions +- They don't scale properly - a calculation using raw screen pixels will give different results on 480p vs 720p +- **The fundamental rule is violated**: UI layout must scale, so it must use DP +- Use `ui.screen_width` and `ui.screen_height` instead (these are in DP) + +### ❌ Anti-Pattern 2: Converting pixels back to DP + +```c +// WRONG - converting pixels to DP via division +int offset_px = (screen->h - DP(menu_height_dp)) / 2; +int offset_dp = offset_px / DP(1); // Hacky! + +// CORRECT - stay in DP +int offset_dp = (ui.screen_height - menu_height_dp) / 2; +``` + +**Why the first is wrong:** +- Introduces rounding errors +- Convoluted logic +- Signal that you started with wrong units + +### ❌ Anti-Pattern 3: Wrapping already-converted values + +```c +// WRONG - double conversion +int oy_px = DP(ui.padding); // Now in pixels +SDL_Rect rect = {0, DP(oy_px + ui.padding), ...}; // Treats pixels as DP! + +// CORRECT - convert once at the end +int oy_dp = ui.padding; +SDL_Rect rect = {0, DP(oy_dp + ui.padding), ...}; +``` + +**Why the first is wrong:** +- Double conversion causes incorrect scaling +- Variables with `_px` suffix shouldn't go inside DP() + +## Variable Naming Convention + +To avoid confusion, use clear suffixes: + +```c +int menu_height_dp; // In DP units +int menu_height_px; // In pixels +int centered_x_dp; // In DP units +int text_width_px; // In pixels (from TTF_SizeUTF8) +``` + +**When suffix is omitted**, the default assumption should be DP for layout calculations. + +## Special Cases + +### Text Rendering (SDL_TTF) + +TTF functions return pixel values, not DP: + +```c +SDL_Surface* text = TTF_RenderUTF8_Blended(font, str, color); +int text_width_px = text->w; // This is in pixels! +int text_height_px = text->h; // This is in pixels! + +// When positioning text, need to be careful +int x_dp = ui.padding; +SDL_BlitSurface(text, NULL, screen, &(SDL_Rect){DP(x_dp), DP(y_dp), 0, 0}); +// text->w and text->h are used by SDL_BlitSurface internally (already pixels) +``` + +### Centering Text + +When you need to center text that's measured in pixels: + +```c +// Option 1: Convert DP container to pixels, center in pixel space +int container_width_px = DP(container_width_dp); +int text_x_px = (container_width_px - text->w) / 2; +SDL_BlitSurface(text, NULL, screen, &(SDL_Rect){text_x_px, DP(y_dp), 0, 0}); + +// Option 2: Convert text width to DP (less common) +// Note: This introduces rounding, may not center perfectly +int text_width_dp = (int)(text->w / gfx_dp_scale + 0.5f); +int text_x_dp = (container_width_dp - text_width_dp) / 2; +SDL_BlitSurface(text, NULL, screen, &(SDL_Rect){DP(text_x_dp), DP(y_dp), 0, 0}); +``` + +**Recommendation**: When interfacing with SDL_TTF, it's acceptable to work in pixels for that specific calculation block, but clearly document it with variable names. + +### Emulation Rendering vs UI Layout + +**Important distinction in minarch.c:** + +```c +// EMULATION RENDERING - Uses pixels directly (core video output) +void video_refresh_callback(const void* data, unsigned width, unsigned height, size_t pitch) { + // width, height are in pixels - this is core video, not UI + int dst_x = (screen->w - scaled_width) / 2; // Pixels are correct here + int dst_y = (screen->h - scaled_height) / 2; + // ... blit emulation video ... +} + +// UI LAYOUT - Should use DP +void renderMenu() { + // UI elements should use DP + int menu_y_dp = (ui.screen_height - menu_height_dp) / 2; + SDL_Rect rect = {DP(ui.padding), DP(menu_y_dp), DP(width_dp), DP(height_dp)}; +} +``` + +**Rule**: If it's rendering game/emulation content, pixels are fine. If it's UI layout (menus, buttons, text), use DP. + +## Migration Strategy + +When converting existing code: + +1. **Identify the variable's purpose**: Is it for UI layout or emulation rendering? +2. **For UI layout**: + - Replace `screen->w` with `ui.screen_width` (DP) + - Replace `screen->h` with `ui.screen_height` (DP) + - Work through all calculations in DP + - Only wrap final values in `DP()` for SDL +3. **For emulation rendering**: Keep using `screen->w` and `screen->h` (pixels) +4. **Add comments**: Document which variables are DP vs pixels if it's not obvious + +## Available DP Resources + +### UI_Layout Struct (api.h) + +```c +extern UI_Layout ui; + +// Screen dimensions in DP +ui.screen_width // Screen width in DP +ui.screen_height // Screen height in DP + +// Screen dimensions in pixels (cached for convenience) +ui.screen_width_px // Screen width in pixels +ui.screen_height_px // Screen height in pixels + +// Layout parameters in DP +ui.pill_height // Menu pill height in DP +ui.padding // Edge padding in DP +ui.text_baseline // Text vertical offset in DP +ui.button_size // Button size in DP +ui.button_padding // Button padding in DP +``` + +All values in `ui` are in DP units except the `*_px` variants. Use DP values directly in calculations. + +### DP Conversion Macros + +```c +// DP to pixels (most common) +DP(x) // Convert single DP value to pixels +DP2(a, b) // Convert two DP values: DP(a), DP(b) +DP3(a, b, c) // Convert three DP values +DP4(a, b, c, d) // Convert four DP values +DP_RECT(x, y, w, h) // Create SDL_Rect from DP coordinates + +// Pixels to DP (for SDL_TTF results) +PX_TO_DP(px) // Convert pixels to DP + +// Raw scale factor +gfx_dp_scale // e.g., 1.5 for 720p, 1.0 for 480p +``` + +### DP-Native Wrapper Functions + +```c +// Use these instead of manual DP() conversions (cleaner code) +GFX_blitPill_DP(asset, dst, x_dp, y_dp, w_dp, h_dp); +GFX_blitRect_DP(asset, dst, x_dp, y_dp, w_dp, h_dp); +GFX_blitAsset_DP(asset, src_rect, dst, x_dp, y_dp); +GFX_blitBattery_DP(dst, x_dp, y_dp); +GFX_blitMessage_DP(font, msg, dst, x_dp, y_dp, w_dp, h_dp); + +// SDL helpers +TTF_SizeUTF8_DP(font, text, &w_dp, &h_dp); // Get text size in DP +SDL_BlitSurface_DP(src, srcrect, dst, x_dp, y_dp); // Blit at DP position +``` + +## Examples + +### Example 1: Centering a menu + +```c +// Calculate menu dimensions in DP +int menu_width_dp = ui.screen_width - (ui.padding * 2); +int menu_height_dp = ui.padding + (item_count * ui.pill_height); + +// Center in DP space +int menu_x_dp = ui.padding; // Left-aligned with padding +int menu_y_dp = (ui.screen_height - menu_height_dp) / 2; + +// Render items +for (int i = 0; i < item_count; i++) { + int item_y_dp = menu_y_dp + ui.padding + (i * ui.pill_height); + + SDL_Rect rect = { + DP(menu_x_dp), + DP(item_y_dp), + DP(menu_width_dp), + DP(ui.pill_height) + }; + + GFX_blitPill(asset, screen, &rect); +} +``` + +### Example 2: Calculating available space + +```c +// WRONG +int available_h = screen->h - DP(ui.padding * 2); + +// CORRECT +int available_h_dp = ui.screen_height - (ui.padding * 2); +int available_h_px = DP(available_h_dp); +``` + +### Example 3: Positioning text with dynamic width + +```c +// Text width comes from SDL_TTF (pixels) +SDL_Surface* text = TTF_RenderUTF8_Blended(font, str, color); +int text_width_px = text->w; + +// Option A: Work in pixel space for this calculation +int container_width_px = DP(ui.screen_width - ui.padding * 2); +int text_x_px = ui.padding + (container_width_px - text_width_px) / 2; +SDL_BlitSurface(text, NULL, screen, &(SDL_Rect){text_x_px, DP(y_dp), 0, 0}); + +// Option B: Convert text width to DP (may have rounding issues) +int text_width_dp = (int)(text_width_px / gfx_dp_scale + 0.5f); +int container_width_dp = ui.screen_width - ui.padding * 2; +int text_x_dp = ui.padding + (container_width_dp - text_width_dp) / 2; +SDL_BlitSurface(text, NULL, screen, &(SDL_Rect){DP(text_x_dp), DP(y_dp), 0, 0}); +``` + +**Recommendation**: Option A is cleaner when dealing with SDL_TTF. + +## Common Mistakes and Fixes + +### Mistake 1: Screen dimensions in calculations + +```c +// BEFORE (wrong) +int oy = (screen->h - DP(menu_height_dp)) / 2 / DP(1); + +// AFTER (correct) +int oy_dp = (ui.screen_height - menu_height_dp) / 2; +``` + +### Mistake 2: Double-wrapping DP values + +```c +// BEFORE (wrong) +int height = DP((ui.padding * 2) + (count * ui.pill_height)); + +// AFTER (correct) +int height_dp = (ui.padding * 2) + (count * ui.pill_height); +int height_px = DP(height_dp); +``` + +### Mistake 3: Inconsistent units in expressions + +```c +// BEFORE (wrong - mixes pixel screen->w with DP-converted padding) +int width = screen->w - DP(ui.padding * 2); +int centered = (screen->w - width) / 2; // What units is width in? + +// AFTER (correct - stay in DP) +int width_dp = ui.screen_width - (ui.padding * 2); +int centered_dp = (ui.screen_width - width_dp) / 2; +// Convert to pixels when rendering +SDL_Rect rect = {DP(centered_dp), DP(y_dp), DP(width_dp), DP(height_dp)}; +``` + +## When to Use `screen->w` and `screen->h` (Raw Pixels) + +**ONLY use raw screen pixel dimensions for:** + +1. **Emulation video rendering** - Core output is in native pixels, not UI +2. **Backing up/restoring screen state** - Saving actual pixel dimensions +3. **Setting DEVICE_WIDTH/DEVICE_HEIGHT** - Platform constants +4. **Low-level pixel operations** - Direct framebuffer access, scaling operations + +**For ALL UI layout, use `ui.screen_width` and `ui.screen_height` instead.** + +## When Working in Pixels is OK + +You can work in pixels for calculations when: + +1. **You have pixel measurements** from SDL_TTF, surface->w/h, etc. +2. **The calculation involves only pixel values** (no DP mixing) +3. **The layout is still driven by DP** (DP values converted to pixels early) + +**Example:** +```c +// Layout driven by DP, calculations in pixels (acceptable) +int text_width_px; +TTF_SizeUTF8(font, text, &text_width_px, NULL); +int item_width_px = text_width_px + DP(ui.button_padding * 2); // DP converted to pixels +int x_px = (DP(ui.screen_width) - item_width_px) / 2; // All pixels now + +// DP values (ui.button_padding, ui.screen_width) drive the layout ✓ +``` + +**Key**: If you're working in pixels, clearly mark variables with `_px` and ensure the layout is still fundamentally driven by DP values. + +## Implementation Checklist + +When writing or reviewing UI code: + +- [ ] Are all layout dimensions calculated in DP? +- [ ] Do you use `ui.screen_width` and `ui.screen_height` instead of `screen->w/h`? +- [ ] Are `DP()` calls only at the final rendering step? +- [ ] Are variable names clear about their units (`_dp` or `_px` suffix)? +- [ ] If mixing pixels (e.g., from SDL_TTF), is the boundary clearly documented? + +## FAQ + +### Q: When should I use `screen->w` and `screen->h`? + +**A:** Only for: +- Emulation video positioning/scaling +- Backing up/restoring screen state +- Setting `DEVICE_WIDTH`/`DEVICE_HEIGHT` constants +- Any non-UI rendering operations + +For all UI layout, use `ui.screen_width` and `ui.screen_height`. + +### Q: What if I need the screen size in pixels? + +**A:** Convert it: +```c +int screen_width_px = DP(ui.screen_width); +int screen_height_px = DP(ui.screen_height); +``` + +### Q: Can I do arithmetic inside DP()? + +**A:** Yes! These are equivalent: +```c +// Both valid +int total_px = DP(ui.padding) + DP(ui.pill_height); +int total_px = DP(ui.padding + ui.pill_height); +``` + +However, the second is preferred (fewer conversions, cleaner). + +### Q: What about constants like SCROLL_WIDTH? + +**A:** If they represent physical UI sizes, they should be in DP: +```c +#define SCROLL_WIDTH_DP 24 +int x_px = DP((ui.screen_width - SCROLL_WIDTH_DP) / 2); +``` + +If they're pixel-specific (rare), document clearly: +```c +#define WINDOW_RADIUS_PX 4 // Pixel-specific, not scaled +``` + +## Summary + +**The mantra: "DP in, DP throughout, pixels out"** + +1. Start with DP values (`ui.*`, constants) +2. Do all calculations in DP +3. Convert to pixels with `DP()` only when passing to SDL functions +4. Use `ui.screen_width` and `ui.screen_height` for screen dimensions +5. Document clearly when you must work in pixels (SDL_TTF, video rendering) diff --git a/docs/transparency-debugging.md b/docs/transparency-debugging.md index 043d590d..d4695f02 100644 --- a/docs/transparency-debugging.md +++ b/docs/transparency-debugging.md @@ -2,7 +2,7 @@ **Problem:** Assets showing black backgrounds instead of transparency on rg35xxplus. -**Status:** Works on tg5040, fails on rg35xxplus +**Status:** ✅ **SOLVED** - Works on all platforms (tg5040, rg35xxplus, desktop) **Note:** This issue is INDEPENDENT of the +1 tier scaling change. The +1 tier change just selects a larger source asset - it doesn't affect transparency handling. We discovered this transparency bug while testing the +1 tier improvement. @@ -101,7 +101,7 @@ SDL_FillRect(surface, NULL, SDL_MapRGBA(surface->format, 0, 0, 0, 0)); --- -### Attempt 4: Remove FillRect, rely on SDL blit behavior (CURRENT) +### Attempt 4: Remove FillRect, rely on SDL blit behavior **File:** `workspace/all/common/api.c` lines 445-505 **Code:** ```c @@ -113,12 +113,50 @@ SDL_FreeSurface(temp); // NO FillRect - let SDL handle transparency through blitting SDL_BlitSurface(source, &src_rect, surface, &dst_rect); ``` -**Result:** ✅ Compiles, 🔄 **Testing on rg35xxplus...** +**Result:** ❌ Still shows black backgrounds -**Theory:** -- Don't pre-fill surfaces with any color -- Let SDL's blitting preserve whatever transparency exists in source -- Avoids the RGBA→RGB565 conversion issue +**Why it failed:** +- Still converting intermediate surfaces to `loaded_assets->format` +- SDL's default alpha-blending causes `src * alpha + dst * (1-alpha)` +- When dst is uninitialized/black, transparent areas blend to black + +--- + +### Attempt 5: Keep gfx.assets in RGBA8888 format ✅ SUCCESS +**File:** `workspace/all/common/api.c` lines 445-520 +**Code:** +```c +// Keep everything in RGBA8888 - don't convert to loaded_assets->format +SDL_Surface* gfx.assets = SDL_CreateRGBSurface(0, sheet_w, sheet_h, 32, RGBA_MASK_8888); + +// For each asset extraction and scaling: +SDL_Surface* extracted = SDL_CreateRGBSurface(0, src_w, src_h, 32, RGBA_MASK_8888); +SDLX_SetAlpha(loaded_assets, 0, 0); // Disable alpha-blend, copy RGBA directly +SDL_BlitSurface(loaded_assets, &src_rect, extracted, NULL); +SDLX_SetAlpha(loaded_assets, SDL_SRCALPHA, 0); // Re-enable for later + +SDL_Surface* scaled = SDL_CreateRGBSurface(0, target_w, target_h, 32, RGBA_MASK_8888); +GFX_scaleBilinear(extracted, scaled); // Direct pixel copy, preserves alpha + +SDLX_SetAlpha(scaled, 0, 0); // Disable alpha-blend for copy to sheet +SDL_BlitSurface(scaled, NULL, gfx.assets, &dst_rect); +``` +**Result:** ✅ **Works on all platforms!** (tg5040, rg35xxplus, desktop) + +**Theory (based on research):** +1. `IMG_Load` returns PNG in native RGBA format with `SDL_SRCALPHA` enabled +2. With `SDL_SRCALPHA` enabled, blitting does alpha-blending: `result = src*alpha + dst*(1-alpha)` +3. When dst is black/uninitialized, transparent pixels (alpha=0) blend to black +4. **Fix:** Call `SDL_SetAlpha(surface, 0, 0)` before blitting to COPY RGBA data directly +5. Keep `gfx.assets` in RGBA8888 - SDL handles RGBA→RGB565 at final screen blit time + +**Key insight from SDL 1.2 docs:** +> "When SDL_SRCALPHA is set, alpha-blending is performed. If SDL_SRCALPHA is not set, +> the RGBA data is copied to the destination surface." + +**References:** +- [SDL 1.2 PNG Alpha Blit Issue](https://github.com/libsdl-org/SDL-1.2/issues/51) +- [SDL_DisplayFormatAlpha docs](https://www.libsdl.org/release/SDL-1.2.15/docs/html/sdldisplayformatalpha.html) --- diff --git a/workspace/all/clock/clock.c b/workspace/all/clock/clock.c index 3f6a1f1f..443d54aa 100644 --- a/workspace/all/clock/clock.c +++ b/workspace/all/clock/clock.c @@ -298,12 +298,17 @@ int main(int argc, char* argv[]) { GFX_blitButtonGroup((char*[]){"B", "CANCEL", "A", "SET", NULL}, 1, screen, 1); // Center the date/time display - // Width is 188 pixels (@1x) in 24-hour mode, 223 pixels in 12-hour mode - int ox = (screen->w - (show_24hour ? DP(188) : DP(223))) / 2; + // Width: 188dp (24-hour) or 223dp (12-hour) + int content_width_dp = show_24hour ? 188 : 223; + int ox_dp = (ui.screen_width - content_width_dp) / 2; // Render date/time in format: YYYY/MM/DD HH:MM:SS [AM/PM] - int x = ox; - int y = DP((((FIXED_HEIGHT / FIXED_SCALE) - ui.pill_height - DIGIT_HEIGHT) / 2)); + // Vertically center in available space (above footer pill) + int available_height_dp = ui.screen_height - ui.padding - ui.pill_height; + int oy_dp = (available_height_dp - DIGIT_HEIGHT) / 2; + + int x = DP(ox_dp); + int y = DP(oy_dp); x = blitNumber(year_selected, x, y); x = blit(CHAR_SLASH, x, y); @@ -336,7 +341,8 @@ int main(int argc, char* argv[]) { } // Draw selection cursor underline - x = ox; + // Reset to start position (ox_dp converted to pixels) + x = DP(ox_dp); y += DP(19); if (select_cursor != CURSOR_YEAR) { x += DP(50); // Width of "YYYY/" diff --git a/workspace/all/common/api.c b/workspace/all/common/api.c index bb00c15f..9dcb8fbc 100644 --- a/workspace/all/common/api.c +++ b/workspace/all/common/api.c @@ -175,6 +175,10 @@ void UI_initLayout(int screen_width, int screen_height, float diagonal_inches) { LOG_info("Row calc: EMERGENCY FALLBACK to %ddp, 1 row\n", MIN_PILL); } + ui.screen_width = (int)(screen_width / gfx_dp_scale + 0.5f); + ui.screen_height = screen_height_dp; + ui.screen_width_px = screen_width; + ui.screen_height_px = screen_height; ui.pill_height = best_pill; ui.row_count = best_rows; ui.padding = DEFAULT_PADDING; @@ -1270,7 +1274,7 @@ int GFX_blitHardwareGroup(SDL_Surface* dst, int show_setting) { if (show_setting && !GetHDMI()) { ow = DP(ui.pill_height + SETTINGS_WIDTH + 10 + 4); - ox = dst->w - DP(ui.padding) - ow; + ox = ui.screen_width_px - DP(ui.padding) - ow; oy = DP(ui.padding); GFX_blitPill(gfx.mode == MODE_MAIN ? ASSET_DARK_GRAY_PILL : ASSET_BLACK_PILL, dst, &(SDL_Rect){ox, oy, ow, DP(ui.pill_height)}); @@ -1313,7 +1317,7 @@ int GFX_blitHardwareGroup(SDL_Surface* dst, int show_setting) { if (show_wifi) ow += ww; - ox = dst->w - DP(ui.padding) - ow; + ox = ui.screen_width_px - DP(ui.padding) - ow; oy = DP(ui.padding); GFX_blitPill(gfx.mode == MODE_MAIN ? ASSET_DARK_GRAY_PILL : ASSET_BLACK_PILL, dst, &(SDL_Rect){ox, oy, ow, DP(ui.pill_height)}); @@ -2618,7 +2622,7 @@ void PWR_powerOff(void) { // TODO: for some reason screen's dimensions end up being 0x0 in GFX_blitMessage... PLAT_clearVideo(gfx.screen); GFX_blitMessage(font.large, msg, gfx.screen, - &(SDL_Rect){0, 0, gfx.screen->w, gfx.screen->h}); //, NULL); + &(SDL_Rect){0, 0, ui.screen_width, ui.screen_height}); //, NULL); GFX_flip(gfx.screen); PLAT_powerOff(); } diff --git a/workspace/all/common/api.h b/workspace/all/common/api.h index 2eabc54f..53d87c8c 100644 --- a/workspace/all/common/api.h +++ b/workspace/all/common/api.h @@ -61,6 +61,30 @@ extern float gfx_dp_scale; #define DP3(a, b, c) DP(a), DP(b), DP(c) #define DP4(a, b, c, d) DP(a), DP(b), DP(c), DP(d) +/** + * Convert physical pixels to display points. + * + * Use this when you have pixel measurements (e.g., from SDL_TTF) and need + * to work in DP space for layout calculations. + * + * @param px Value in physical pixels + * @return Value in display points (rounded) + */ +#define PX_TO_DP(px) ((int)((px) / gfx_dp_scale + 0.5f)) + +/** + * Create SDL_Rect from DP coordinates. + * + * Use this for inline SDL_Rect creation when DP wrapper functions aren't available. + * + * @param x_dp X coordinate in DP + * @param y_dp Y coordinate in DP + * @param w_dp Width in DP (0 for auto) + * @param h_dp Height in DP (0 for auto) + * @return SDL_Rect with pixel-converted coordinates + */ +#define DP_RECT(x_dp, y_dp, w_dp, h_dp) ((SDL_Rect){DP(x_dp), DP(y_dp), DP(w_dp), DP(h_dp)}) + /** * Runtime-calculated UI layout parameters. * @@ -68,13 +92,17 @@ extern float gfx_dp_scale; * to optimally fill the display without per-platform manual configuration. */ typedef struct UI_Layout { - 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 text_baseline; // Vertical offset for text centering in pill - int button_size; // Size of button graphics in dp - int button_margin; // Margin around buttons in dp - int button_padding; // Padding inside buttons in dp + int screen_width; // Screen width in dp + int screen_height; // Screen height in dp + int screen_width_px; // Screen width in pixels (cached for convenience) + 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 text_baseline; // Vertical offset for text centering in pill + int button_size; // Size of button graphics in dp + int button_margin; // Margin around buttons in dp + int button_padding; // Padding inside buttons in dp } UI_Layout; extern UI_Layout ui; @@ -589,6 +617,125 @@ void GFX_sizeText(TTF_Font* font, char* str, int leading, int* w, int* h); void GFX_blitText(TTF_Font* font, char* str, int leading, SDL_Color color, SDL_Surface* dst, SDL_Rect* dst_rect); +/////////////////////////////// +// DP-Native Wrapper Functions +/////////////////////////////// + +/** + * DP-native wrappers for GFX functions. + * + * These wrappers accept DP coordinates and convert to pixels internally, + * following the pattern used by Android, iOS, and other cross-platform frameworks. + * Use these for all UI layout code. + */ + +/** + * Blits a pill-shaped background using DP coordinates. + * + * @param asset ASSET_WHITE_PILL, ASSET_BLACK_PILL, or ASSET_DARK_GRAY_PILL + * @param dst Destination surface + * @param x_dp X coordinate in DP + * @param y_dp Y coordinate in DP + * @param w_dp Width in DP + * @param h_dp Height in DP + */ +static inline void GFX_blitPill_DP(int asset, SDL_Surface* dst, int x_dp, int y_dp, int w_dp, int h_dp) { + SDL_Rect rect = {DP(x_dp), DP(y_dp), DP(w_dp), DP(h_dp)}; + GFX_blitPill(asset, dst, &rect); +} + +/** + * Blits a rectangular asset using DP coordinates. + * + * @param asset Asset ID + * @param dst Destination surface + * @param x_dp X coordinate in DP + * @param y_dp Y coordinate in DP + * @param w_dp Width in DP + * @param h_dp Height in DP + */ +static inline void GFX_blitRect_DP(int asset, SDL_Surface* dst, int x_dp, int y_dp, int w_dp, int h_dp) { + SDL_Rect rect = {DP(x_dp), DP(y_dp), DP(w_dp), DP(h_dp)}; + GFX_blitRect(asset, dst, &rect); +} + +/** + * Blits an asset using DP coordinates. + * + * @param asset Asset ID + * @param src_rect Source rectangle (in pixels, or NULL for full asset) + * @param dst Destination surface + * @param x_dp X coordinate in DP + * @param y_dp Y coordinate in DP + */ +static inline void GFX_blitAsset_DP(int asset, const SDL_Rect* src_rect, SDL_Surface* dst, int x_dp, int y_dp) { + SDL_Rect dst_rect = {DP(x_dp), DP(y_dp), 0, 0}; + GFX_blitAsset(asset, src_rect, dst, &dst_rect); +} + +/** + * Blits the battery indicator using DP coordinates. + * + * @param dst Destination surface + * @param x_dp X coordinate in DP + * @param y_dp Y coordinate in DP + */ +static inline void GFX_blitBattery_DP(SDL_Surface* dst, int x_dp, int y_dp) { + SDL_Rect rect = {DP(x_dp), DP(y_dp), 0, 0}; + GFX_blitBattery(dst, &rect); +} + +/** + * Blits centered message text using DP rectangle. + * + * @param ttf_font Font to use + * @param msg Message text + * @param dst Destination surface + * @param x_dp X coordinate in DP + * @param y_dp Y coordinate in DP + * @param w_dp Width in DP + * @param h_dp Height in DP + */ +static inline void GFX_blitMessage_DP(TTF_Font* ttf_font, char* msg, SDL_Surface* dst, + int x_dp, int y_dp, int w_dp, int h_dp) { + SDL_Rect rect = {DP(x_dp), DP(y_dp), DP(w_dp), DP(h_dp)}; + GFX_blitMessage(ttf_font, msg, dst, &rect); +} + +/////////////////////////////// +// SDL Helper Functions +/////////////////////////////// + +/** + * Gets text size in DP units. + * + * @param ttf_font Font to use + * @param text Text to measure + * @param w_dp Output: width in DP (may be NULL) + * @param h_dp Output: height in DP (may be NULL) + */ +static inline void TTF_SizeUTF8_DP(TTF_Font* ttf_font, const char* text, int* w_dp, int* h_dp) { + int w_px = 0, h_px = 0; + TTF_SizeUTF8(ttf_font, text, &w_px, &h_px); + if (w_dp) *w_dp = PX_TO_DP(w_px); + if (h_dp) *h_dp = PX_TO_DP(h_px); +} + +/** + * Blits surface at DP coordinates. + * + * @param src Source surface + * @param srcrect Source rectangle (NULL for full surface) + * @param dst Destination surface + * @param x_dp X coordinate in DP + * @param y_dp Y coordinate in DP + */ +static inline void SDL_BlitSurface_DP(SDL_Surface* src, SDL_Rect* srcrect, + SDL_Surface* dst, int x_dp, int y_dp) { + SDL_Rect dstrect = {DP(x_dp), DP(y_dp), 0, 0}; + SDL_BlitSurface(src, srcrect, dst, &dstrect); +} + /////////////////////////////// // Sound (SND) API /////////////////////////////// diff --git a/workspace/all/minarch/minarch.c b/workspace/all/minarch/minarch.c index 532c4776..32416d87 100644 --- a/workspace/all/minarch/minarch.c +++ b/workspace/all/minarch/minarch.c @@ -4553,8 +4553,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), screen->w, - screen->h - DP(ui.pill_height + ui.padding)}); + &(SDL_Rect){0, DP(ui.padding), DP(ui.screen_width), + DP(ui.screen_height - ui.pill_height - ui.padding)}); GFX_blitButtonGroup(pairs, 0, screen, 1); GFX_flip(screen); dirty = 0; @@ -5104,8 +5104,8 @@ static int Menu_options(MenuList* list) { // dependent on option list offset top and bottom, eg. the gray triangles int max_visible_options = - (screen->h - DP((ui.padding + ui.pill_height) * 2)) / - DP(ui.button_size); // 7 for 480, 10 for 720 + (ui.screen_height - (ui.padding + ui.pill_height) * 2) / + ui.button_size; // 7 for 480, 10 for 720 int count; for (count = 0; items[count].name; count++) @@ -5284,10 +5284,10 @@ static int Menu_options(MenuList* list) { mw = w; } // cache the result - list->max_width = mw = MIN(mw, screen->w - DP(ui.padding * 2)); + list->max_width = mw = MIN(mw, DP(ui.screen_width - ui.padding * 2)); } - int ox = (screen->w - mw) / 2; + int ox = (DP(ui.screen_width) - mw) / 2; int oy = DP(ui.padding + ui.pill_height); int selected_row = selected - start; for (int i = start, j = 0; i < end; i++, j++) { @@ -5317,7 +5317,7 @@ static int Menu_options(MenuList* list) { } } else if (type == MENU_FIXED) { // NOTE: no need to calculate max width - int mw = screen->w - DP(ui.padding * 2); + int mw = DP(ui.screen_width - ui.padding * 2); // int lw,rw; // lw = rw = mw / 2; int ox, oy; @@ -5395,10 +5395,10 @@ static int Menu_options(MenuList* list) { mw = w; } // cache the result - list->max_width = mw = MIN(mw, screen->w - DP(ui.padding * 2)); + list->max_width = mw = MIN(mw, DP(ui.screen_width - ui.padding * 2)); } - int ox = (screen->w - mw) / 2; + int ox = (DP(ui.screen_width) - mw) / 2; int oy = DP(ui.padding + ui.pill_height); int selected_row = selected - start; for (int i = start, j = 0; i < end; i++, j++) { @@ -5445,7 +5445,7 @@ static int Menu_options(MenuList* list) { if (count > max_visible_options) { #define SCROLL_WIDTH 24 #define SCROLL_HEIGHT 4 - int ox = (screen->w - DP(SCROLL_WIDTH)) / 2; + int ox = (DP(ui.screen_width) - DP(SCROLL_WIDTH)) / 2; int oy = DP((ui.pill_height - SCROLL_HEIGHT) / 2); if (start > 0) GFX_blitAsset(ASSET_SCROLL_UP, NULL, screen, @@ -5453,8 +5453,7 @@ static int Menu_options(MenuList* list) { if (end < count) GFX_blitAsset( ASSET_SCROLL_DOWN, NULL, screen, - &(SDL_Rect){ox, screen->h - - DP(ui.padding + ui.pill_height + ui.button_size) + oy}); + &(SDL_Rect){ox, DP(ui.screen_height - ui.padding - ui.pill_height - ui.button_size) + oy}); } if (!desc && list->desc) @@ -5465,7 +5464,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){(screen->w - w) / 2, screen->h - DP(ui.padding) - h, w, h}); + &(SDL_Rect){(DP(ui.screen_width) - w) / 2, DP(ui.screen_height) - DP(ui.padding) - h, w, h}); } GFX_flip(screen); @@ -5935,7 +5934,7 @@ static void Menu_loop(void) { int ox, oy; int ow = GFX_blitHardwareGroup(screen, show_setting); - int max_width = screen->w - DP(ui.padding * 2) - ow; + int max_width = DP(ui.screen_width) - DP(ui.padding * 2) - ow; char display_name[256]; int text_width = GFX_truncateText(font.large, rom_name, display_name, max_width, @@ -5960,9 +5959,9 @@ static void Menu_loop(void) { 0); GFX_blitButtonGroup((char*[]){"B", "BACK", "A", "OKAY", NULL}, 1, screen, 1); - // Vertically center the menu items + // Vertically center the menu items (oy is in DP units) int menu_height_dp = ui.padding + (MENU_ITEM_COUNT * ui.pill_height); - oy = (screen->h - DP(menu_height_dp)) / 2 / DP(1); + oy = (ui.screen_height - menu_height_dp) / 2; for (int i = 0; i < MENU_ITEM_COUNT; i++) { char* item = menu.items[i]; @@ -5973,12 +5972,12 @@ static void Menu_loop(void) { if (menu.total_discs > 1 && i == ITEM_CONT) { GFX_blitPill(ASSET_DARK_GRAY_PILL, screen, &(SDL_Rect){DP(ui.padding), DP(oy + ui.padding), - screen->w - DP(ui.padding * 2), + DP(ui.screen_width - ui.padding * 2), DP(ui.pill_height)}); text = TTF_RenderUTF8_Blended(font.large, disc_name, COLOR_WHITE); SDL_BlitSurface( text, NULL, screen, - &(SDL_Rect){screen->w - DP(ui.padding + ui.button_padding) - text->w, + &(SDL_Rect){DP(ui.screen_width - ui.padding - ui.button_padding) - text->w, DP(oy + ui.padding + ui.text_baseline)}); SDL_FreeSurface(text); } diff --git a/workspace/all/minui/minui.c b/workspace/all/minui/minui.c index 990728d6..79373206 100644 --- a/workspace/all/minui/minui.c +++ b/workspace/all/minui/minui.c @@ -2277,9 +2277,14 @@ int main(int argc, char* argv[]) { SDL_FreeSurface(key_txt); SDL_FreeSurface(val_txt); } + // Version splash centering - work in DP space + int version_w_dp = (int)(version->w / gfx_dp_scale + 0.5f); + int version_h_dp = (int)(version->h / gfx_dp_scale + 0.5f); + int center_x_dp = (ui.screen_width - version_w_dp) / 2; + int center_y_dp = (ui.screen_height - version_h_dp) / 2; SDL_BlitSurface( version, NULL, screen, - &(SDL_Rect){(screen->w - version->w) / 2, (screen->h - version->h) / 2, 0, 0}); + &(SDL_Rect){DP(center_x_dp), DP(center_y_dp), 0, 0}); // buttons (duped and trimmed from below) if (show_setting && !GetHDMI()) @@ -2298,8 +2303,10 @@ int main(int argc, char* argv[]) { Entry* entry = top->entries->items[i]; char* entry_name = entry->name; char* entry_unique = entry->unique; + // Calculate available width in pixels + // ox is in pixels (thumbnail offset), screen width converted from DP to pixels int available_width = - (had_thumb && j != selected_row ? ox : screen->w) - DP(ui.padding * 2); + (had_thumb && j != selected_row ? ox : DP(ui.screen_width)) - DP(ui.padding * 2); if (i == top->start && !(had_thumb && j != selected_row)) available_width -= ow; // @@ -2349,9 +2356,9 @@ int main(int argc, char* argv[]) { SDL_FreeSurface(text); } } else { - // TODO: for some reason screen's dimensions end up being 0x0 in GFX_blitMessage... - GFX_blitMessage(font.large, "Empty folder", screen, - &(SDL_Rect){0, 0, screen->w, screen->h}); //, NULL); + // Use DP-based wrapper for proper scaling + GFX_blitMessage_DP(font.large, "Empty folder", screen, + 0, 0, ui.screen_width, ui.screen_height); } // buttons diff --git a/workspace/all/say/say.c b/workspace/all/say/say.c index 0f0e0e2e..4b5d8262 100644 --- a/workspace/all/say/say.c +++ b/workspace/all/say/say.c @@ -59,9 +59,10 @@ int main(int argc, const char* argv[]) { GFX_clear(screen); // Display message centered, leaving room for button at bottom - GFX_blitMessage(font.large, msg, screen, - &(SDL_Rect){0, 0, screen->w, - screen->h - DP(ui.padding + ui.pill_height + ui.padding)}); + // Use DP dimensions: ui.screen_width/height in DP, calculate message area in DP + int message_height = ui.screen_height - (ui.padding + ui.pill_height + ui.padding); + GFX_blitMessage_DP(font.large, msg, screen, + 0, 0, ui.screen_width, message_height); GFX_blitButtonGroup((char*[]){"A", "OKAY", NULL}, 1, screen, 1); GFX_flip(screen); From d1fed216d30688174a4883b78cce4f2707887f50 Mon Sep 17 00:00:00 2001 From: Nick Chapman Date: Wed, 26 Nov 2025 17:19:56 -0800 Subject: [PATCH 13/17] Improved text centering. --- workspace/all/common/api.c | 24 ++++++++++++++---------- workspace/all/common/api.h | 15 +++++++++++++++ workspace/all/minarch/minarch.c | 6 +++--- 3 files changed, 32 insertions(+), 13 deletions(-) diff --git a/workspace/all/common/api.c b/workspace/all/common/api.c index 9dcb8fbc..660c1ed1 100644 --- a/workspace/all/common/api.c +++ b/workspace/all/common/api.c @@ -69,7 +69,7 @@ UI_Layout ui = { .pill_height = 30, .row_count = 6, .padding = 10, - .text_baseline = 4, + .text_baseline = 4, // (30 * 2) / 15 = 4 for 30dp pill .button_size = 20, .button_margin = 5, .button_padding = 12, @@ -196,7 +196,11 @@ 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 - ui.text_baseline = (4 * ui.pill_height + 15) / 30; // ~4 for 30dp pill + + // 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 ~4dp for 30dp pill, scales proportionally with pill height + ui.text_baseline = (ui.pill_height * 2) / 15; LOG_info("UI_initLayout: %dx%d @ %.2f\" → PPI=%.0f, dp_scale=%.2f\n", screen_width, screen_height, diagonal_inches, ppi, gfx_dp_scale); @@ -1163,11 +1167,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 + // label - center text in button using DP-aware centering + // Bias vertical position up slightly to account for visual weight of glyphs 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 SDL_BlitSurface(text, NULL, dst, - &(SDL_Rect){dst_rect->x + (DP(ui.button_size) - text->w) / 2, - dst_rect->y + (DP(ui.button_size) - text->h) / 2}); + &(SDL_Rect){dst_rect->x + DP_CENTER_PX(ui.button_size, text->w), + dst_rect->y + offset_y}); ox += DP(ui.button_size); SDL_FreeSurface(text); } else { @@ -1181,7 +1187,7 @@ void GFX_blitButton(char* hint, char* button, SDL_Surface* dst, SDL_Rect* dst_re int oy = special_case ? DP(-2) : 0; SDL_BlitSurface(text, NULL, dst, &(SDL_Rect){ox + dst_rect->x, - oy + dst_rect->y + (DP(ui.button_size) - text->h) / 2, text->w, + oy + dst_rect->y + DP_CENTER_PX(ui.button_size, text->h), text->w, text->h}); ox += text->w; ox += DP(ui.button_size) / 4; @@ -1193,7 +1199,7 @@ void GFX_blitButton(char* hint, char* button, SDL_Surface* dst, SDL_Rect* dst_re // hint text text = TTF_RenderUTF8_Blended(font.small, hint, COLOR_WHITE); SDL_BlitSurface(text, NULL, dst, - &(SDL_Rect){ox + dst_rect->x, dst_rect->y + (DP(ui.button_size) - text->h) / 2, + &(SDL_Rect){ox + dst_rect->x, dst_rect->y + DP_CENTER_PX(ui.button_size, text->h), text->w, text->h}); SDL_FreeSurface(text); } @@ -2619,10 +2625,8 @@ void PWR_powerOff(void) { // LOG_info("PWR_powerOff %s (%ix%i)\n", gfx.screen, gfx.screen->w, gfx.screen->h); - // TODO: for some reason screen's dimensions end up being 0x0 in GFX_blitMessage... PLAT_clearVideo(gfx.screen); - GFX_blitMessage(font.large, msg, gfx.screen, - &(SDL_Rect){0, 0, ui.screen_width, ui.screen_height}); //, NULL); + GFX_blitMessage_DP(font.large, msg, gfx.screen, 0, 0, ui.screen_width, ui.screen_height); GFX_flip(gfx.screen); PLAT_powerOff(); } diff --git a/workspace/all/common/api.h b/workspace/all/common/api.h index 53d87c8c..f29cfe42 100644 --- a/workspace/all/common/api.h +++ b/workspace/all/common/api.h @@ -61,6 +61,21 @@ extern float gfx_dp_scale; #define DP3(a, b, c) DP(a), DP(b), DP(c) #define DP4(a, b, c, d) DP(a), DP(b), DP(c), DP(d) +/** + * Center pixel content within DP container. + * + * Handles the common case where container size is in DP (converted to pixels + * with rounding) and content size is measured in pixels (e.g., from SDL_TTF). + * Uses round-to-nearest to avoid bias, ensuring good centering across all devices. + * + * @param dp_size Container size in display points + * @param px_size Content size in pixels (e.g., text->w from TTF_Render) + * @return Offset in pixels to center content (rounded to nearest) + * + * Example: DP_CENTER_PX(ui.button_size, text->w) + */ +#define DP_CENTER_PX(dp_size, px_size) (((DP(dp_size) - (px_size)) + 1) / 2) + /** * Convert physical pixels to display points. * diff --git a/workspace/all/minarch/minarch.c b/workspace/all/minarch/minarch.c index 32416d87..ddbdb35a 100644 --- a/workspace/all/minarch/minarch.c +++ b/workspace/all/minarch/minarch.c @@ -5287,7 +5287,7 @@ static int Menu_options(MenuList* list) { list->max_width = mw = MIN(mw, DP(ui.screen_width - ui.padding * 2)); } - int ox = (DP(ui.screen_width) - mw) / 2; + int ox = DP_CENTER_PX(ui.screen_width, mw); int oy = DP(ui.padding + ui.pill_height); int selected_row = selected - start; for (int i = start, j = 0; i < end; i++, j++) { @@ -5398,7 +5398,7 @@ static int Menu_options(MenuList* list) { list->max_width = mw = MIN(mw, DP(ui.screen_width - ui.padding * 2)); } - int ox = (DP(ui.screen_width) - mw) / 2; + int ox = DP_CENTER_PX(ui.screen_width, mw); int oy = DP(ui.padding + ui.pill_height); int selected_row = selected - start; for (int i = start, j = 0; i < end; i++, j++) { @@ -5464,7 +5464,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(ui.screen_width) - w) / 2, DP(ui.screen_height) - DP(ui.padding) - h, w, h}); + &(SDL_Rect){DP_CENTER_PX(ui.screen_width, w), DP(ui.screen_height) - DP(ui.padding) - h, w, h}); } GFX_flip(screen); From 8df5671c5504385b9da31bffe24711f3889ca94b Mon Sep 17 00:00:00 2001 From: Nick Chapman Date: Wed, 26 Nov 2025 18:07:54 -0800 Subject: [PATCH 14/17] Clean up old fixed scale UI code. --- workspace/all/common/api.c | 42 +++++++++++-------- workspace/all/common/api.h | 49 +++++++++++++---------- workspace/all/common/defines.h | 35 +--------------- workspace/all/minarch/minarch.c | 25 ++++++------ workspace/all/minput/minput.c | 14 +++---- workspace/all/minui/minui.c | 12 +++--- workspace/all/say/say.c | 3 +- workspace/desktop/platform/platform.c | 8 ++-- workspace/desktop/platform/platform.h | 7 ---- workspace/m17/platform/platform.c | 9 ++--- workspace/m17/platform/platform.h | 3 -- workspace/magicmini/platform/platform.c | 8 ++-- workspace/magicmini/platform/platform.h | 1 - workspace/miyoomini/platform/platform.c | 8 ++-- workspace/miyoomini/platform/platform.h | 9 ----- workspace/my282/platform/platform.c | 8 ++-- workspace/my282/platform/platform.h | 1 - workspace/my355/platform/platform.c | 8 ++-- workspace/my355/platform/platform.h | 10 ----- workspace/rg35xx/platform/platform.c | 15 ++++--- workspace/rg35xx/platform/platform.h | 1 - workspace/rg35xxplus/platform/platform.c | 9 ++--- workspace/rg35xxplus/platform/platform.h | 10 ----- workspace/rgb30/platform/platform.c | 9 ++--- workspace/rgb30/platform/platform.h | 8 ---- workspace/tg5040/platform/platform.c | 8 ++-- workspace/tg5040/platform/platform.h | 8 ---- workspace/trimuismart/platform/platform.c | 8 ++-- workspace/trimuismart/platform/platform.h | 1 - workspace/zero28/platform/platform.c | 10 ++--- workspace/zero28/platform/platform.h | 7 ---- 31 files changed, 121 insertions(+), 233 deletions(-) diff --git a/workspace/all/common/api.c b/workspace/all/common/api.c index 660c1ed1..d66f90c1 100644 --- a/workspace/all/common/api.c +++ b/workspace/all/common/api.c @@ -152,7 +152,8 @@ void UI_initLayout(int screen_width, int screen_height, float diagonal_inches) { best_pill = pill; best_rows = content_rows; best_is_even = 1; - LOG_info("Row calc: %d rows → pill=%ddp (%dpx even) ✓\n", content_rows, pill, pill_px); + LOG_info("Row calc: %d rows → pill=%ddp (%dpx even) ✓\n", content_rows, pill, + pill_px); break; } else if (best_rows == 0) { // Acceptable but odd pixels - keep as backup @@ -184,6 +185,7 @@ void UI_initLayout(int screen_width, int screen_height, float diagonal_inches) { ui.padding = DEFAULT_PADDING; int used_dp = (ui.row_count + 1) * ui.pill_height; + (void)used_dp; // Used in LOG_info below LOG_info("Row calc: FINAL rows=%d, pill=%ddp (%dpx), using %d/%d dp (%.1f%%)\n", ui.row_count, ui.pill_height, DP(ui.pill_height), used_dp, available_dp, (used_dp * 100.0f) / available_dp); @@ -202,6 +204,10 @@ void UI_initLayout(int screen_width, int screen_height, float diagonal_inches) { // Gives ~4dp for 30dp pill, scales proportionally with pill height ui.text_baseline = (ui.pill_height * 2) / 15; + // Settings indicators + ui.settings_size = ui.pill_height / 8; // ~4dp for 30dp pill + ui.settings_width = 80; // Fixed width in dp + 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, @@ -497,17 +503,16 @@ SDL_Surface* GFX_init(int mode) { } // Extract this asset region from source sheet into RGBA8888 surface - SDL_Surface* extracted = SDL_CreateRGBSurface(0, src_rect.w, src_rect.h, 32, - RGBA_MASK_8888); + SDL_Surface* extracted = + SDL_CreateRGBSurface(0, src_rect.w, src_rect.h, 32, RGBA_MASK_8888); // Disable alpha-blending to copy RGBA data directly (not blend with dst) SDLX_SetAlpha(loaded_assets, 0, 0); SDL_BlitSurface(loaded_assets, &src_rect, extracted, &(SDL_Rect){0, 0}); - SDLX_SetAlpha(loaded_assets, SDL_SRCALPHA, 0); // Re-enable for later + SDLX_SetAlpha(loaded_assets, SDL_SRCALPHA, 0); // Re-enable for later // Scale this specific asset to its target size (keep in RGBA8888) - SDL_Surface* scaled = SDL_CreateRGBSurface(0, target_w, target_h, 32, - RGBA_MASK_8888); - GFX_scaleBilinear(extracted, scaled); // Direct pixel copy preserves alpha + SDL_Surface* scaled = SDL_CreateRGBSurface(0, target_w, target_h, 32, RGBA_MASK_8888); + GFX_scaleBilinear(extracted, scaled); // Direct pixel copy preserves alpha // Place the scaled asset into the destination sheet // Disable alpha-blending to copy RGBA data directly @@ -1170,7 +1175,8 @@ void GFX_blitButton(char* hint, char* button, SDL_Surface* dst, SDL_Rect* dst_re // label - center text in button using DP-aware centering // Bias vertical position up slightly to account for visual weight of glyphs 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_y = + DP_CENTER_PX(ui.button_size, text->h) - DP(1); // Move up ~1dp for visual balance SDL_BlitSurface(text, NULL, dst, &(SDL_Rect){dst_rect->x + DP_CENTER_PX(ui.button_size, text->w), dst_rect->y + offset_y}); @@ -1187,8 +1193,8 @@ void GFX_blitButton(char* hint, char* button, SDL_Surface* dst, SDL_Rect* dst_re int oy = special_case ? DP(-2) : 0; SDL_BlitSurface(text, NULL, dst, &(SDL_Rect){ox + dst_rect->x, - oy + dst_rect->y + DP_CENTER_PX(ui.button_size, text->h), text->w, - text->h}); + oy + dst_rect->y + DP_CENTER_PX(ui.button_size, text->h), + text->w, text->h}); ox += text->w; ox += DP(ui.button_size) / 4; SDL_FreeSurface(text); @@ -1199,8 +1205,9 @@ void GFX_blitButton(char* hint, char* button, SDL_Surface* dst, SDL_Rect* dst_re // hint text text = TTF_RenderUTF8_Blended(font.small, hint, COLOR_WHITE); SDL_BlitSurface(text, NULL, dst, - &(SDL_Rect){ox + dst_rect->x, dst_rect->y + DP_CENTER_PX(ui.button_size, text->h), - text->w, text->h}); + &(SDL_Rect){ox + dst_rect->x, + dst_rect->y + DP_CENTER_PX(ui.button_size, text->h), text->w, + text->h}); SDL_FreeSurface(text); } @@ -1279,7 +1286,7 @@ int GFX_blitHardwareGroup(SDL_Surface* dst, int show_setting) { int ow = 0; if (show_setting && !GetHDMI()) { - ow = DP(ui.pill_height + SETTINGS_WIDTH + 10 + 4); + ow = DP(ui.pill_height + ui.settings_width + ui.padding + 4); ox = ui.screen_width_px - DP(ui.padding) - ow; oy = DP(ui.padding); GFX_blitPill(gfx.mode == MODE_MAIN ? ASSET_DARK_GRAY_PILL : ASSET_BLACK_PILL, dst, @@ -1305,14 +1312,15 @@ int GFX_blitHardwareGroup(SDL_Surface* dst, int show_setting) { GFX_blitAsset(asset, NULL, dst, &(SDL_Rect){ax, ay}); ox += DP(ui.pill_height); - oy += DP((ui.pill_height - SETTINGS_SIZE) / 2); + oy += (DP(ui.pill_height) - DP(ui.settings_size)) / 2; GFX_blitPill(gfx.mode == MODE_MAIN ? ASSET_BAR_BG : ASSET_BAR_BG_MENU, dst, - &(SDL_Rect){ox, oy, DP(SETTINGS_WIDTH), DP(SETTINGS_SIZE)}); + &(SDL_Rect){ox, oy, DP(ui.settings_width), DP(ui.settings_size)}); float percent = ((float)(setting_value - setting_min) / (setting_max - setting_min)); if (show_setting == 1 || setting_value > 0) { - GFX_blitPill(ASSET_BAR, dst, - &(SDL_Rect){ox, oy, DP(SETTINGS_WIDTH) * percent, DP(SETTINGS_SIZE)}); + GFX_blitPill( + ASSET_BAR, dst, + &(SDL_Rect){ox, oy, DP(ui.settings_width) * percent, DP(ui.settings_size)}); } } else { // TODO: handle wifi diff --git a/workspace/all/common/api.h b/workspace/all/common/api.h index f29cfe42..d0ff7144 100644 --- a/workspace/all/common/api.h +++ b/workspace/all/common/api.h @@ -107,17 +107,19 @@ extern float gfx_dp_scale; * to optimally fill the display without per-platform manual configuration. */ typedef struct UI_Layout { - int screen_width; // Screen width in dp - int screen_height; // Screen height in dp - int screen_width_px; // Screen width in pixels (cached for convenience) + int screen_width; // Screen width in dp + int screen_height; // Screen height in dp + int screen_width_px; // Screen width in pixels (cached for convenience) 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 text_baseline; // Vertical offset for text centering in pill - int button_size; // Size of button graphics in dp - int button_margin; // Margin around buttons in dp - int button_padding; // Padding inside buttons in dp + 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 text_baseline; // Vertical offset for text centering in pill + int button_size; // Size of button graphics in dp + int button_margin; // Margin around buttons in dp + 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 } UI_Layout; extern UI_Layout ui; @@ -519,9 +521,7 @@ void GFX_freeAAScaler(void); * @param asset Asset ID (ASSET_* enum) * @param src_rect Source rectangle (NULL for full asset) * @param dst Destination surface - * @param dst_rect Destination rectangle - * - * @note All dimensions should be pre-scaled by FIXED_SCALE + * @param dst_rect Destination rectangle (dimensions in physical pixels) */ void GFX_blitAsset(int asset, const SDL_Rect* src_rect, SDL_Surface* dst, SDL_Rect* dst_rect); @@ -654,7 +654,8 @@ void GFX_blitText(TTF_Font* font, char* str, int leading, SDL_Color color, SDL_S * @param w_dp Width in DP * @param h_dp Height in DP */ -static inline void GFX_blitPill_DP(int asset, SDL_Surface* dst, int x_dp, int y_dp, int w_dp, int h_dp) { +static inline void GFX_blitPill_DP(int asset, SDL_Surface* dst, int x_dp, int y_dp, int w_dp, + int h_dp) { SDL_Rect rect = {DP(x_dp), DP(y_dp), DP(w_dp), DP(h_dp)}; GFX_blitPill(asset, dst, &rect); } @@ -669,7 +670,8 @@ static inline void GFX_blitPill_DP(int asset, SDL_Surface* dst, int x_dp, int y_ * @param w_dp Width in DP * @param h_dp Height in DP */ -static inline void GFX_blitRect_DP(int asset, SDL_Surface* dst, int x_dp, int y_dp, int w_dp, int h_dp) { +static inline void GFX_blitRect_DP(int asset, SDL_Surface* dst, int x_dp, int y_dp, int w_dp, + int h_dp) { SDL_Rect rect = {DP(x_dp), DP(y_dp), DP(w_dp), DP(h_dp)}; GFX_blitRect(asset, dst, &rect); } @@ -683,7 +685,8 @@ static inline void GFX_blitRect_DP(int asset, SDL_Surface* dst, int x_dp, int y_ * @param x_dp X coordinate in DP * @param y_dp Y coordinate in DP */ -static inline void GFX_blitAsset_DP(int asset, const SDL_Rect* src_rect, SDL_Surface* dst, int x_dp, int y_dp) { +static inline void GFX_blitAsset_DP(int asset, const SDL_Rect* src_rect, SDL_Surface* dst, int x_dp, + int y_dp) { SDL_Rect dst_rect = {DP(x_dp), DP(y_dp), 0, 0}; GFX_blitAsset(asset, src_rect, dst, &dst_rect); } @@ -711,8 +714,8 @@ static inline void GFX_blitBattery_DP(SDL_Surface* dst, int x_dp, int y_dp) { * @param w_dp Width in DP * @param h_dp Height in DP */ -static inline void GFX_blitMessage_DP(TTF_Font* ttf_font, char* msg, SDL_Surface* dst, - int x_dp, int y_dp, int w_dp, int h_dp) { +static inline void GFX_blitMessage_DP(TTF_Font* ttf_font, char* msg, SDL_Surface* dst, int x_dp, + int y_dp, int w_dp, int h_dp) { SDL_Rect rect = {DP(x_dp), DP(y_dp), DP(w_dp), DP(h_dp)}; GFX_blitMessage(ttf_font, msg, dst, &rect); } @@ -732,8 +735,10 @@ static inline void GFX_blitMessage_DP(TTF_Font* ttf_font, char* msg, SDL_Surface static inline void TTF_SizeUTF8_DP(TTF_Font* ttf_font, const char* text, int* w_dp, int* h_dp) { int w_px = 0, h_px = 0; TTF_SizeUTF8(ttf_font, text, &w_px, &h_px); - if (w_dp) *w_dp = PX_TO_DP(w_px); - if (h_dp) *h_dp = PX_TO_DP(h_px); + if (w_dp) + *w_dp = PX_TO_DP(w_px); + if (h_dp) + *h_dp = PX_TO_DP(h_px); } /** @@ -745,8 +750,8 @@ static inline void TTF_SizeUTF8_DP(TTF_Font* ttf_font, const char* text, int* w_ * @param x_dp X coordinate in DP * @param y_dp Y coordinate in DP */ -static inline void SDL_BlitSurface_DP(SDL_Surface* src, SDL_Rect* srcrect, - SDL_Surface* dst, int x_dp, int y_dp) { +static inline void SDL_BlitSurface_DP(SDL_Surface* src, SDL_Rect* srcrect, SDL_Surface* dst, + int x_dp, int y_dp) { SDL_Rect dstrect = {DP(x_dp), DP(y_dp), 0, 0}; SDL_BlitSurface(src, srcrect, dst, &dstrect); } diff --git a/workspace/all/common/defines.h b/workspace/all/common/defines.h index d239f479..bc00010d 100644 --- a/workspace/all/common/defines.h +++ b/workspace/all/common/defines.h @@ -184,31 +184,10 @@ // UI layout constants (before scaling) /////////////////////////////// -/** - * UI element sizes in logical pixels (before FIXED_SCALE multiplication). - */ -#define PILL_SIZE 30 // Height of menu item pills -#define BUTTON_SIZE 20 // Size of button graphics -#define BUTTON_MARGIN 5 // Margin around buttons ((PILL_SIZE - BUTTON_SIZE) / 2) -#define BUTTON_PADDING 12 // Padding inside buttons -#define SETTINGS_SIZE 4 // Size of setting indicators -#define SETTINGS_WIDTH 80 // Width of settings panel - -/** - * Number of visible menu rows on the main screen. - * - * Default is 6 rows. Platform can override in platform.h if needed. - * Calculation: FIXED_HEIGHT / (PILL_SIZE * FIXED_SCALE) - 2 - */ -#ifndef MAIN_ROW_COUNT -#define MAIN_ROW_COUNT 6 -#endif - /** * Screen padding in logical pixels. * - * Default is 10 pixels. Platform can override in platform.h if needed. - * Calculation: PILL_SIZE / 3 (or non-integer part divided by three) + * Used for button margin calculations in minput. */ #ifndef PADDING #define PADDING 10 @@ -242,18 +221,6 @@ #define MIN(a, b) (a) < (b) ? (a) : (b) #define CEIL_DIV(a, b) ((a) + (b) - 1) / (b) // Integer ceiling division -/** - * Scaling macros for UI coordinates. - * - * These multiply logical coordinates by FIXED_SCALE to get physical screen coordinates. - * Use these when passing coordinates to SDL or GFX functions. - */ -#define SCALE1(a) ((a) * FIXED_SCALE) -#define SCALE2(a, b) ((a) * FIXED_SCALE), ((b) * FIXED_SCALE) -#define SCALE3(a, b, c) ((a) * FIXED_SCALE), ((b) * FIXED_SCALE), ((c) * FIXED_SCALE) -#define SCALE4(a, b, c, d) \ - ((a) * FIXED_SCALE), ((b) * FIXED_SCALE), ((c) * FIXED_SCALE), ((d) * FIXED_SCALE) - /////////////////////////////// // Platform capability detection /////////////////////////////// diff --git a/workspace/all/minarch/minarch.c b/workspace/all/minarch/minarch.c index ddbdb35a..3e72ed89 100644 --- a/workspace/all/minarch/minarch.c +++ b/workspace/all/minarch/minarch.c @@ -5103,9 +5103,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.padding + ui.pill_height) * 2) / + ui.button_size; // 7 for 480, 10 for 720 int count; for (count = 0; items[count].name; count++) @@ -5446,14 +5445,15 @@ static int Menu_options(MenuList* list) { #define SCROLL_WIDTH 24 #define SCROLL_HEIGHT 4 int ox = (DP(ui.screen_width) - DP(SCROLL_WIDTH)) / 2; - int oy = DP((ui.pill_height - SCROLL_HEIGHT) / 2); + int oy = (DP(ui.pill_height) - DP(SCROLL_HEIGHT)) / 2; if (start > 0) GFX_blitAsset(ASSET_SCROLL_UP, NULL, screen, &(SDL_Rect){ox, DP(ui.padding) + oy}); 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}); + GFX_blitAsset(ASSET_SCROLL_DOWN, NULL, screen, + &(SDL_Rect){ox, DP(ui.screen_height - ui.padding - + ui.pill_height - ui.button_size) + + oy}); } if (!desc && list->desc) @@ -5462,9 +5462,9 @@ static int Menu_options(MenuList* list) { if (desc) { int w, h; 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}); + 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}); } GFX_flip(screen); @@ -5962,7 +5962,7 @@ static void Menu_loop(void) { // 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; - + for (int i = 0; i < MENU_ITEM_COUNT; i++) { char* item = menu.items[i]; SDL_Color text_color = COLOR_WHITE; @@ -5977,7 +5977,8 @@ static void Menu_loop(void) { 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) - text->w, + &(SDL_Rect){DP(ui.screen_width - ui.padding - ui.button_padding) - + text->w, DP(oy + ui.padding + ui.text_baseline)}); SDL_FreeSurface(text); } diff --git a/workspace/all/minput/minput.c b/workspace/all/minput/minput.c index 48d3b524..f93d00a2 100644 --- a/workspace/all/minput/minput.c +++ b/workspace/all/minput/minput.c @@ -211,13 +211,13 @@ int main(int argc, char* argv[]) { // D-pad (Up, Down, Left, Right) /////////////////////////////// { - int x = DP(ui.padding + PILL_SIZE); + int x = DP(ui.padding + ui.pill_height); int y = oy + DP(ui.pill_height * 2); int o = DP(ui.button_margin); // Vertical bar connecting Up and Down buttons SDL_FillRect(screen, - &(SDL_Rect){x, y + DP(ui.pill_height / 2), DP(ui.pill_height), + &(SDL_Rect){x, y + DP(ui.pill_height) / 2, DP(ui.pill_height), DP(ui.pill_height * 2)}, RGB_DARK_GRAY); GFX_blitPill(ASSET_DARK_GRAY_PILL, screen, &(SDL_Rect){x, y, 0}); @@ -232,7 +232,7 @@ int main(int argc, char* argv[]) { // Horizontal bar connecting Left and Right buttons SDL_FillRect(screen, - &(SDL_Rect){x + DP(ui.pill_height / 2), y, DP(ui.pill_height * 2), + &(SDL_Rect){x + DP(ui.pill_height) / 2, y, DP(ui.pill_height * 2), DP(ui.pill_height)}, RGB_DARK_GRAY); @@ -248,7 +248,7 @@ int main(int argc, char* argv[]) { // Face buttons (A, B, X, Y) /////////////////////////////// { - int x = FIXED_WIDTH - DP(ui.padding + PILL_SIZE * 3) + DP(ui.pill_height); + int x = FIXED_WIDTH - DP(ui.padding + ui.pill_height * 3) + DP(ui.pill_height); int y = oy + DP(ui.pill_height * 2); int o = DP(ui.button_margin); @@ -295,7 +295,7 @@ int main(int argc, char* argv[]) { /////////////////////////////// if (has_power || has_menu) { int bw = 42; - int pw = has_both ? (bw * 2 + BUTTON_MARGIN * 3) : (bw + BUTTON_MARGIN * 2); + int pw = has_both ? (bw * 2 + ui.button_margin * 3) : (bw + ui.button_margin * 2); int x = (FIXED_WIDTH - DP(pw)) / 2; int y = oy + DP(ui.pill_height * 3); @@ -340,7 +340,7 @@ int main(int argc, char* argv[]) { // Analog stick buttons (if available) /////////////////////////////// if (has_L3) { - int x = DP(ui.padding + PILL_SIZE); + int x = DP(ui.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 + PILL_SIZE * 3) + DP(ui.pill_height); + int x = FIXED_WIDTH - DP(ui.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/minui/minui.c b/workspace/all/minui/minui.c index 79373206..a39a13f0 100644 --- a/workspace/all/minui/minui.c +++ b/workspace/all/minui/minui.c @@ -2282,9 +2282,8 @@ int main(int argc, char* argv[]) { int version_h_dp = (int)(version->h / gfx_dp_scale + 0.5f); int center_x_dp = (ui.screen_width - version_w_dp) / 2; int center_y_dp = (ui.screen_height - version_h_dp) / 2; - SDL_BlitSurface( - version, NULL, screen, - &(SDL_Rect){DP(center_x_dp), DP(center_y_dp), 0, 0}); + SDL_BlitSurface(version, NULL, screen, + &(SDL_Rect){DP(center_x_dp), DP(center_y_dp), 0, 0}); // buttons (duped and trimmed from below) if (show_setting && !GetHDMI()) @@ -2306,7 +2305,8 @@ int main(int argc, char* argv[]) { // Calculate available width in pixels // 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); + (had_thumb && j != selected_row ? ox : DP(ui.screen_width)) - + DP(ui.padding * 2); if (i == top->start && !(had_thumb && j != selected_row)) available_width -= ow; // @@ -2357,8 +2357,8 @@ int main(int argc, char* argv[]) { } } else { // Use DP-based wrapper for proper scaling - GFX_blitMessage_DP(font.large, "Empty folder", screen, - 0, 0, ui.screen_width, ui.screen_height); + GFX_blitMessage_DP(font.large, "Empty folder", screen, 0, 0, ui.screen_width, + ui.screen_height); } // buttons diff --git a/workspace/all/say/say.c b/workspace/all/say/say.c index 4b5d8262..14ebc7d5 100644 --- a/workspace/all/say/say.c +++ b/workspace/all/say/say.c @@ -61,8 +61,7 @@ int main(int argc, const char* argv[]) { // Display message centered, leaving room for button at bottom // Use DP dimensions: ui.screen_width/height in DP, calculate message area in DP int message_height = ui.screen_height - (ui.padding + ui.pill_height + ui.padding); - GFX_blitMessage_DP(font.large, msg, screen, - 0, 0, ui.screen_width, message_height); + GFX_blitMessage_DP(font.large, msg, screen, 0, 0, ui.screen_width, message_height); GFX_blitButtonGroup((char*[]){"A", "OKAY", NULL}, 1, screen, 1); GFX_flip(screen); diff --git a/workspace/desktop/platform/platform.c b/workspace/desktop/platform/platform.c index d688be95..d5cd1872 100644 --- a/workspace/desktop/platform/platform.c +++ b/workspace/desktop/platform/platform.c @@ -379,19 +379,17 @@ void PLAT_flip(SDL_Surface* IGNORED, int ignored) { // Overlay /////////////////////////////// -#define OVERLAY_WIDTH PILL_SIZE // unscaled -#define OVERLAY_HEIGHT PILL_SIZE // unscaled #define OVERLAY_BPP 4 #define OVERLAY_DEPTH 16 -#define OVERLAY_PITCH (OVERLAY_WIDTH * OVERLAY_BPP) // unscaled #define OVERLAY_RGBA_MASK 0x00ff0000, 0x0000ff00, 0x000000ff, 0xff000000 // ARGB static struct OVL_Context { SDL_Surface* overlay; } ovl; SDL_Surface* PLAT_initOverlay(void) { - ovl.overlay = SDL_CreateRGBSurface(SDL_SWSURFACE, SCALE2(OVERLAY_WIDTH, OVERLAY_HEIGHT), - OVERLAY_DEPTH, OVERLAY_RGBA_MASK); + int overlay_size = DP(ui.pill_height); + ovl.overlay = SDL_CreateRGBSurface(SDL_SWSURFACE, overlay_size, overlay_size, OVERLAY_DEPTH, + OVERLAY_RGBA_MASK); return ovl.overlay; } void PLAT_quitOverlay(void) { diff --git a/workspace/desktop/platform/platform.h b/workspace/desktop/platform/platform.h index fb17e164..a99c5450 100644 --- a/workspace/desktop/platform/platform.h +++ b/workspace/desktop/platform/platform.h @@ -131,7 +131,6 @@ // Desktop uses a virtual 2.78" screen at VGA resolution to get dp_scale ≈ 2.0 (with 144 PPI baseline) #define SCREEN_DIAGONAL 2.78f // Virtual screen diagonal for consistent scaling -#define FIXED_SCALE 2 // 2x scaling factor for UI #define FIXED_WIDTH 640 // Screen width in pixels #define FIXED_HEIGHT 480 // Screen height in pixels (VGA) #define FIXED_BPP 2 // Bytes per pixel (RGB565) @@ -146,12 +145,6 @@ // #define HDMI_PITCH (HDMI_WIDTH * FIXED_BPP) // #define HDMI_SIZE (HDMI_PITCH * HDMI_HEIGHT) -/////////////////////////////// -// UI Layout Configuration -/////////////////////////////// - -// MAIN_ROW_COUNT and PADDING are now calculated automatically via DP system - /////////////////////////////// // Platform-Specific Paths and Settings /////////////////////////////// diff --git a/workspace/m17/platform/platform.c b/workspace/m17/platform/platform.c index eeff6113..9f71cc20 100644 --- a/workspace/m17/platform/platform.c +++ b/workspace/m17/platform/platform.c @@ -551,20 +551,17 @@ void PLAT_flip(SDL_Surface* IGNORED, int ignored) { /////////////////////////////// -// TODO: -#define OVERLAY_WIDTH PILL_SIZE // unscaled -#define OVERLAY_HEIGHT PILL_SIZE // unscaled #define OVERLAY_BPP 4 #define OVERLAY_DEPTH 16 -#define OVERLAY_PITCH (OVERLAY_WIDTH * OVERLAY_BPP) // unscaled #define OVERLAY_RGBA_MASK 0x00ff0000, 0x0000ff00, 0x000000ff, 0xff000000 // ARGB static struct OVL_Context { SDL_Surface* overlay; } ovl; SDL_Surface* PLAT_initOverlay(void) { - ovl.overlay = SDL_CreateRGBSurface(SDL_SWSURFACE, SCALE2(OVERLAY_WIDTH, OVERLAY_HEIGHT), - OVERLAY_DEPTH, OVERLAY_RGBA_MASK); + int overlay_size = DP(ui.pill_height); + ovl.overlay = SDL_CreateRGBSurface(SDL_SWSURFACE, overlay_size, overlay_size, OVERLAY_DEPTH, + OVERLAY_RGBA_MASK); return ovl.overlay; } void PLAT_quitOverlay(void) { diff --git a/workspace/m17/platform/platform.h b/workspace/m17/platform/platform.h index 301df9d9..a162d041 100644 --- a/workspace/m17/platform/platform.h +++ b/workspace/m17/platform/platform.h @@ -132,7 +132,6 @@ /////////////////////////////// #define SCREEN_DIAGONAL 7.0f // Physical screen diagonal in inches (estimated) -#define FIXED_SCALE 1 // No scaling factor needed #define FIXED_WIDTH 480 // Screen width in pixels #define FIXED_HEIGHT 273 // Screen height in pixels (16:9 widescreen) #define FIXED_BPP 2 // Bytes per pixel (RGB565) @@ -148,8 +147,6 @@ #define MUTE_VOLUME_RAW 0 // Raw value for muted volume #define HAS_NEON // May have NEON SIMD support (uncertain) -// MAIN_ROW_COUNT is now calculated automatically via DP system - /////////////////////////////// #endif diff --git a/workspace/magicmini/platform/platform.c b/workspace/magicmini/platform/platform.c index 1ee7b158..d4607ff4 100644 --- a/workspace/magicmini/platform/platform.c +++ b/workspace/magicmini/platform/platform.c @@ -1035,11 +1035,8 @@ void PLAT_flip(SDL_Surface* IGNORED, int ignored) { // Overlay (unused on this platform) /////////////////////////////// -#define OVERLAY_WIDTH PILL_SIZE // unscaled -#define OVERLAY_HEIGHT PILL_SIZE // unscaled #define OVERLAY_BPP 4 #define OVERLAY_DEPTH 16 -#define OVERLAY_PITCH (OVERLAY_WIDTH * OVERLAY_BPP) // unscaled #define OVERLAY_RGBA_MASK 0x00ff0000, 0x0000ff00, 0x000000ff, 0xff000000 // ARGB static struct OVL_Context { SDL_Surface* overlay; @@ -1051,8 +1048,9 @@ static struct OVL_Context { * @return Overlay surface */ SDL_Surface* PLAT_initOverlay(void) { - ovl.overlay = SDL_CreateRGBSurface(SDL_SWSURFACE, SCALE2(OVERLAY_WIDTH, OVERLAY_HEIGHT), - OVERLAY_DEPTH, OVERLAY_RGBA_MASK); + int overlay_size = DP(ui.pill_height); + ovl.overlay = SDL_CreateRGBSurface(SDL_SWSURFACE, overlay_size, overlay_size, OVERLAY_DEPTH, + OVERLAY_RGBA_MASK); return ovl.overlay; } diff --git a/workspace/magicmini/platform/platform.h b/workspace/magicmini/platform/platform.h index b42b36ba..417b6b9c 100644 --- a/workspace/magicmini/platform/platform.h +++ b/workspace/magicmini/platform/platform.h @@ -129,7 +129,6 @@ /////////////////////////////// #define SCREEN_DIAGONAL 2.8f // Physical screen diagonal in inches -#define FIXED_SCALE 2 // 2x scaling factor for UI #define FIXED_WIDTH 640 // Screen width in pixels #define FIXED_HEIGHT 480 // Screen height in pixels (VGA) #define FIXED_BPP 2 // Bytes per pixel (RGB565) diff --git a/workspace/miyoomini/platform/platform.c b/workspace/miyoomini/platform/platform.c index 6af5fa44..b98578dd 100644 --- a/workspace/miyoomini/platform/platform.c +++ b/workspace/miyoomini/platform/platform.c @@ -651,11 +651,8 @@ void PLAT_flip(SDL_Surface* IGNORED, int sync) { // Overlay (On-Screen Display) /////////////////////////////// -#define OVERLAY_WIDTH PILL_SIZE -#define OVERLAY_HEIGHT PILL_SIZE #define OVERLAY_BPP 4 #define OVERLAY_DEPTH 16 -#define OVERLAY_PITCH (OVERLAY_WIDTH * OVERLAY_BPP) #define OVERLAY_RGBA_MASK 0x00ff0000, 0x0000ff00, 0x000000ff, 0xff000000 // ARGB static struct OVL_Context { @@ -668,8 +665,9 @@ static struct OVL_Context { * @return Overlay surface for rendering status indicators */ SDL_Surface* PLAT_initOverlay(void) { - ovl.overlay = SDL_CreateRGBSurface(SDL_SWSURFACE, SCALE2(OVERLAY_WIDTH, OVERLAY_HEIGHT), - OVERLAY_DEPTH, OVERLAY_RGBA_MASK); + int overlay_size = DP(ui.pill_height); + ovl.overlay = SDL_CreateRGBSurface(SDL_SWSURFACE, overlay_size, overlay_size, OVERLAY_DEPTH, + OVERLAY_RGBA_MASK); return ovl.overlay; } diff --git a/workspace/miyoomini/platform/platform.h b/workspace/miyoomini/platform/platform.h index 57ca242c..8150cc58 100644 --- a/workspace/miyoomini/platform/platform.h +++ b/workspace/miyoomini/platform/platform.h @@ -140,7 +140,6 @@ extern int is_560p; // Set to 1 for 560p screen variant /////////////////////////////// #define SCREEN_DIAGONAL 2.8f // Physical screen diagonal in inches -#define FIXED_SCALE 2 // 2x scaling factor for UI #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) #define FIXED_BPP 2 // Bytes per pixel (RGB565) @@ -148,14 +147,6 @@ extern int is_560p; // Set to 1 for 560p screen variant #define FIXED_PITCH (FIXED_WIDTH * FIXED_BPP) // Row stride in bytes #define FIXED_SIZE (FIXED_PITCH * FIXED_HEIGHT) // Total framebuffer size -/////////////////////////////// -// UI Layout Configuration -// Adjusted for 560p variant -/////////////////////////////// - -// MAIN_ROW_COUNT and PADDING are now calculated automatically via DP system -#define PAGE_SCALE (is_560p ? 2 : 3) // Memory scaling: tighter on 560p to reduce usage - /////////////////////////////// // Platform-Specific Paths and Settings /////////////////////////////// diff --git a/workspace/my282/platform/platform.c b/workspace/my282/platform/platform.c index 73ba7c30..dceacb11 100644 --- a/workspace/my282/platform/platform.c +++ b/workspace/my282/platform/platform.c @@ -843,11 +843,8 @@ void PLAT_flip(SDL_Surface* IGNORED, int ignored) { /////////////////////////////// // Overlay Handling /////////////////////////////// -#define OVERLAY_WIDTH PILL_SIZE // unscaled -#define OVERLAY_HEIGHT PILL_SIZE // unscaled #define OVERLAY_BPP 4 #define OVERLAY_DEPTH 16 -#define OVERLAY_PITCH (OVERLAY_WIDTH * OVERLAY_BPP) // unscaled #define OVERLAY_RGBA_MASK 0x00ff0000, 0x0000ff00, 0x000000ff, 0xff000000 // ARGB static struct OVL_Context { SDL_Surface* overlay; @@ -859,8 +856,9 @@ static struct OVL_Context { * @return Pointer to overlay surface */ SDL_Surface* PLAT_initOverlay(void) { - ovl.overlay = SDL_CreateRGBSurface(SDL_SWSURFACE, SCALE2(OVERLAY_WIDTH, OVERLAY_HEIGHT), - OVERLAY_DEPTH, OVERLAY_RGBA_MASK); + int overlay_size = DP(ui.pill_height); + ovl.overlay = SDL_CreateRGBSurface(SDL_SWSURFACE, overlay_size, overlay_size, OVERLAY_DEPTH, + OVERLAY_RGBA_MASK); return ovl.overlay; } diff --git a/workspace/my282/platform/platform.h b/workspace/my282/platform/platform.h index 0447bb22..955ffc13 100644 --- a/workspace/my282/platform/platform.h +++ b/workspace/my282/platform/platform.h @@ -128,7 +128,6 @@ /////////////////////////////// #define SCREEN_DIAGONAL 2.8f // Physical screen diagonal in inches (estimated) -#define FIXED_SCALE 2 // 2x scaling factor for UI #define FIXED_WIDTH 640 // Screen width in pixels #define FIXED_HEIGHT 480 // Screen height in pixels (VGA) #define FIXED_BPP 2 // Bytes per pixel (RGB565) diff --git a/workspace/my355/platform/platform.c b/workspace/my355/platform/platform.c index b58b38ea..65ca9c5b 100644 --- a/workspace/my355/platform/platform.c +++ b/workspace/my355/platform/platform.c @@ -816,11 +816,8 @@ int PLAT_supportsOverscan(void) { // Overlay (HUD Icons) /////////////////////////////// -#define OVERLAY_WIDTH PILL_SIZE // unscaled -#define OVERLAY_HEIGHT PILL_SIZE // unscaled #define OVERLAY_BPP 4 #define OVERLAY_DEPTH 16 -#define OVERLAY_PITCH (OVERLAY_WIDTH * OVERLAY_BPP) // unscaled #define OVERLAY_RGBA_MASK 0x00ff0000, 0x0000ff00, 0x000000ff, 0xff000000 // ARGB static struct OVL_Context { SDL_Surface* overlay; @@ -834,8 +831,9 @@ static struct OVL_Context { * @return Overlay surface pointer */ SDL_Surface* PLAT_initOverlay(void) { - ovl.overlay = SDL_CreateRGBSurface(SDL_SWSURFACE, SCALE2(OVERLAY_WIDTH, OVERLAY_HEIGHT), - OVERLAY_DEPTH, OVERLAY_RGBA_MASK); + int overlay_size = DP(ui.pill_height); + ovl.overlay = SDL_CreateRGBSurface(SDL_SWSURFACE, overlay_size, overlay_size, OVERLAY_DEPTH, + OVERLAY_RGBA_MASK); return ovl.overlay; } diff --git a/workspace/my355/platform/platform.h b/workspace/my355/platform/platform.h index a9b2b0f0..bd310225 100644 --- a/workspace/my355/platform/platform.h +++ b/workspace/my355/platform/platform.h @@ -147,7 +147,6 @@ extern int on_hdmi; // Set to 1 when HDMI output is active /////////////////////////////// #define SCREEN_DIAGONAL 3.5f // Physical screen diagonal in inches (Miyoo Flip) -#define FIXED_SCALE 2 // 2x scaling factor for UI #define FIXED_WIDTH 640 // Screen width in pixels #define FIXED_HEIGHT 480 // Screen height in pixels (VGA) #define FIXED_BPP 2 // Bytes per pixel (RGB565) @@ -165,15 +164,6 @@ extern int on_hdmi; // Set to 1 when HDMI output is active #define HDMI_PITCH (HDMI_WIDTH * FIXED_BPP) // HDMI row stride #define HDMI_SIZE (HDMI_PITCH * HDMI_HEIGHT) // HDMI framebuffer size -// TODO: if HDMI_HEIGHT > FIXED_HEIGHT then MAIN_ROW_COUNT will be insufficient - -/////////////////////////////// -// UI Layout Configuration -// Adjusted for HDMI output -/////////////////////////////// - -// MAIN_ROW_COUNT and PADDING are now calculated automatically via DP system - /////////////////////////////// // Platform-Specific Paths and Settings /////////////////////////////// diff --git a/workspace/rg35xx/platform/platform.c b/workspace/rg35xx/platform/platform.c index 4f59cb60..928b5a77 100644 --- a/workspace/rg35xx/platform/platform.c +++ b/workspace/rg35xx/platform/platform.c @@ -922,11 +922,8 @@ void PLAT_flip(SDL_Surface* IGNORED, int sync) { * - Independent positioning and scaling * - Can be toggled without affecting main framebuffer */ -#define OVERLAY_WIDTH PILL_SIZE // Unscaled overlay width -#define OVERLAY_HEIGHT PILL_SIZE // Unscaled overlay height #define OVERLAY_BPP 4 // 32-bit ARGB #define OVERLAY_DEPTH 32 -#define OVERLAY_PITCH (OVERLAY_WIDTH * OVERLAY_BPP) #define OVERLAY_RGBA_MASK 0x00ff0000, 0x0000ff00, 0x000000ff, 0xff000000 // ARGB format #define OVERLAY_FB 0 // Framebuffer device index #define OVERLAY_ID 1 // Overlay plane ID @@ -961,9 +958,11 @@ static struct OVL_Context { * @note Uses per-pixel alpha (not global alpha) */ SDL_Surface* PLAT_initOverlay(void) { - // Create SDL surface (scaled by SCALE2 macro) - ovl.overlay = SDL_CreateRGBSurfaceFrom(NULL, SCALE2(OVERLAY_WIDTH, OVERLAY_HEIGHT), - OVERLAY_DEPTH, SCALE1(OVERLAY_PITCH), OVERLAY_RGBA_MASK); + // Create SDL surface (DP-scaled) + int overlay_size = DP(ui.pill_height); + int overlay_pitch = overlay_size * OVERLAY_BPP; + ovl.overlay = SDL_CreateRGBSurfaceFrom(NULL, overlay_size, overlay_size, OVERLAY_DEPTH, + overlay_pitch, OVERLAY_RGBA_MASK); uint32_t size = ovl.overlay->h * ovl.overlay->pitch; // Allocate ION memory for overlay buffer @@ -982,8 +981,8 @@ SDL_Surface* PLAT_initOverlay(void) { // Calculate overlay position (top-right corner) int x, y, w, h; w = h = ovl.overlay->w; - x = FIXED_WIDTH - SCALE1(PADDING) - w; - y = SCALE1(PADDING); + x = FIXED_WIDTH - DP(ui.padding) - w; + y = DP(ui.padding); // Configure overlay info ovl.oinfo.mem_off = (uintptr_t)ovl.ov_info.padd - vid.finfo.smem_start; // Offset from FB base diff --git a/workspace/rg35xx/platform/platform.h b/workspace/rg35xx/platform/platform.h index 99675ccc..5a2d21df 100644 --- a/workspace/rg35xx/platform/platform.h +++ b/workspace/rg35xx/platform/platform.h @@ -131,7 +131,6 @@ /////////////////////////////// #define SCREEN_DIAGONAL 3.5f // Physical screen diagonal in inches -#define FIXED_SCALE 2 // 2x scaling factor for UI #define FIXED_WIDTH 640 // Screen width in pixels #define FIXED_HEIGHT 480 // Screen height in pixels (VGA) #define FIXED_BPP 2 // Bytes per pixel (RGB565) diff --git a/workspace/rg35xxplus/platform/platform.c b/workspace/rg35xxplus/platform/platform.c index ce9c7a3e..87e1b4a7 100644 --- a/workspace/rg35xxplus/platform/platform.c +++ b/workspace/rg35xxplus/platform/platform.c @@ -1089,20 +1089,17 @@ int PLAT_supportsOverscan(void) { /////////////////////////////// -// TODO: -#define OVERLAY_WIDTH PILL_SIZE // unscaled -#define OVERLAY_HEIGHT PILL_SIZE // unscaled #define OVERLAY_BPP 4 #define OVERLAY_DEPTH 16 -#define OVERLAY_PITCH (OVERLAY_WIDTH * OVERLAY_BPP) // unscaled #define OVERLAY_RGBA_MASK 0x00ff0000, 0x0000ff00, 0x000000ff, 0xff000000 // ARGB static struct OVL_Context { SDL_Surface* overlay; } ovl; SDL_Surface* PLAT_initOverlay(void) { - ovl.overlay = SDL_CreateRGBSurface(SDL_SWSURFACE, SCALE2(OVERLAY_WIDTH, OVERLAY_HEIGHT), - OVERLAY_DEPTH, OVERLAY_RGBA_MASK); + int overlay_size = DP(ui.pill_height); + ovl.overlay = SDL_CreateRGBSurface(SDL_SWSURFACE, overlay_size, overlay_size, OVERLAY_DEPTH, + OVERLAY_RGBA_MASK); return ovl.overlay; } void PLAT_quitOverlay(void) { diff --git a/workspace/rg35xxplus/platform/platform.h b/workspace/rg35xxplus/platform/platform.h index 3c96f10c..04440c78 100644 --- a/workspace/rg35xxplus/platform/platform.h +++ b/workspace/rg35xxplus/platform/platform.h @@ -144,7 +144,6 @@ extern int on_hdmi; // Set to 1 when HDMI output is active #define SCREEN_DIAGONAL \ (is_cubexx ? 3.95f \ : (is_rg34xx ? 3.4f : 3.5f)) // Diagonal: 3.95" (Cube) / 3.4" (34XX) / 3.5" (Plus) -#define FIXED_SCALE 2 // 2x scaling factor for UI #define FIXED_WIDTH (is_cubexx ? 720 : (is_rg34xx ? 720 : 640)) // Width: 720 (H/SP) or 640 (Plus) #define FIXED_HEIGHT (is_cubexx ? 720 : 480) // Height: 720 (H) or 480 (Plus/SP) #define FIXED_BPP 2 // Bytes per pixel (RGB565) @@ -162,15 +161,6 @@ extern int on_hdmi; // Set to 1 when HDMI output is active #define HDMI_PITCH (HDMI_WIDTH * FIXED_BPP) // HDMI row stride #define HDMI_SIZE (HDMI_PITCH * HDMI_HEIGHT) // HDMI framebuffer size -// TODO: if HDMI_HEIGHT > FIXED_HEIGHT then MAIN_ROW_COUNT will be insufficient - -/////////////////////////////// -// UI Layout Configuration -// Adjusted for device variant and HDMI -/////////////////////////////// - -// MAIN_ROW_COUNT and PADDING are now calculated automatically via DP system - /////////////////////////////// // Platform-Specific Paths and Settings /////////////////////////////// diff --git a/workspace/rgb30/platform/platform.c b/workspace/rgb30/platform/platform.c index f9740314..c14cfb1e 100644 --- a/workspace/rgb30/platform/platform.c +++ b/workspace/rgb30/platform/platform.c @@ -860,12 +860,8 @@ int PLAT_supportsOverscan(void) { // Overlay System /////////////////////////////// -// TODO: -#define OVERLAY_WIDTH PILL_SIZE // unscaled -#define OVERLAY_HEIGHT PILL_SIZE // unscaled #define OVERLAY_BPP 4 #define OVERLAY_DEPTH 16 -#define OVERLAY_PITCH (OVERLAY_WIDTH * OVERLAY_BPP) // unscaled #define OVERLAY_RGBA_MASK 0x00ff0000, 0x0000ff00, 0x000000ff, 0xff000000 // ARGB static struct OVL_Context { SDL_Surface* overlay; @@ -880,8 +876,9 @@ static struct OVL_Context { * @return Pointer to overlay surface */ SDL_Surface* PLAT_initOverlay(void) { - ovl.overlay = SDL_CreateRGBSurface(SDL_SWSURFACE, SCALE2(OVERLAY_WIDTH, OVERLAY_HEIGHT), - OVERLAY_DEPTH, OVERLAY_RGBA_MASK); + int overlay_size = DP(ui.pill_height); + ovl.overlay = SDL_CreateRGBSurface(SDL_SWSURFACE, overlay_size, overlay_size, OVERLAY_DEPTH, + OVERLAY_RGBA_MASK); return ovl.overlay; } diff --git a/workspace/rgb30/platform/platform.h b/workspace/rgb30/platform/platform.h index 70e7120a..a95e4ab6 100644 --- a/workspace/rgb30/platform/platform.h +++ b/workspace/rgb30/platform/platform.h @@ -134,7 +134,6 @@ /////////////////////////////// #define SCREEN_DIAGONAL 4.0f // Physical screen diagonal in inches -#define FIXED_SCALE 2 // 2x scaling factor for UI #define FIXED_WIDTH 720 // Screen width in pixels (square display) #define FIXED_HEIGHT 720 // Screen height in pixels (1:1 aspect ratio) #define FIXED_BPP 2 // Bytes per pixel (RGB565) @@ -152,13 +151,6 @@ #define HDMI_PITCH (HDMI_WIDTH * FIXED_BPP) // HDMI row stride #define HDMI_SIZE (HDMI_PITCH * HDMI_HEIGHT) // HDMI framebuffer size -/////////////////////////////// -// UI Layout Configuration -// Larger values for square display -/////////////////////////////// - -// MAIN_ROW_COUNT and PADDING are now calculated automatically via DP system - /////////////////////////////// // Platform-Specific Paths and Settings /////////////////////////////// diff --git a/workspace/tg5040/platform/platform.c b/workspace/tg5040/platform/platform.c index 78d58a02..cb3bf6f1 100644 --- a/workspace/tg5040/platform/platform.c +++ b/workspace/tg5040/platform/platform.c @@ -522,19 +522,17 @@ void PLAT_flip(SDL_Surface* IGNORED, int ignored) { // Overlay /////////////////////////////// -#define OVERLAY_WIDTH PILL_SIZE // unscaled -#define OVERLAY_HEIGHT PILL_SIZE // unscaled #define OVERLAY_BPP 4 #define OVERLAY_DEPTH 16 -#define OVERLAY_PITCH (OVERLAY_WIDTH * OVERLAY_BPP) // unscaled #define OVERLAY_RGBA_MASK 0x00ff0000, 0x0000ff00, 0x000000ff, 0xff000000 // ARGB static struct OVL_Context { SDL_Surface* overlay; } ovl; SDL_Surface* PLAT_initOverlay(void) { - ovl.overlay = SDL_CreateRGBSurface(SDL_SWSURFACE, SCALE2(OVERLAY_WIDTH, OVERLAY_HEIGHT), - OVERLAY_DEPTH, OVERLAY_RGBA_MASK); + int overlay_size = DP(ui.pill_height); + ovl.overlay = SDL_CreateRGBSurface(SDL_SWSURFACE, overlay_size, overlay_size, OVERLAY_DEPTH, + OVERLAY_RGBA_MASK); return ovl.overlay; } void PLAT_quitOverlay(void) { diff --git a/workspace/tg5040/platform/platform.h b/workspace/tg5040/platform/platform.h index 365fc068..27f2c457 100644 --- a/workspace/tg5040/platform/platform.h +++ b/workspace/tg5040/platform/platform.h @@ -156,7 +156,6 @@ 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 FIXED_SCALE (is_brick ? 3 : 2) // Scaling: 3x (Brick) or 2x (standard) #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) #define FIXED_BPP 2 // Bytes per pixel (RGB565) @@ -164,13 +163,6 @@ extern int is_brick; // Set to 1 for Brick variant (1024x768 display) #define FIXED_PITCH (FIXED_WIDTH * FIXED_BPP) // Row stride in bytes #define FIXED_SIZE (FIXED_PITCH * FIXED_HEIGHT) // Total framebuffer size -/////////////////////////////// -// UI Layout Configuration -// Adjusted for Brick variant -/////////////////////////////// - -// MAIN_ROW_COUNT and PADDING are now calculated automatically via DP system - /////////////////////////////// // Platform-Specific Paths and Settings /////////////////////////////// diff --git a/workspace/trimuismart/platform/platform.c b/workspace/trimuismart/platform/platform.c index 5f31d3fd..c22fbe17 100644 --- a/workspace/trimuismart/platform/platform.c +++ b/workspace/trimuismart/platform/platform.c @@ -814,11 +814,8 @@ void PLAT_flip(SDL_Surface* IGNORED, int sync) { * * @note Currently unused - overlay layer not enabled */ -#define OVERLAY_WIDTH PILL_SIZE // Width in pixels -#define OVERLAY_HEIGHT PILL_SIZE // Height in pixels #define OVERLAY_BPP 4 // 32-bit ARGB #define OVERLAY_DEPTH 16 // Bit depth -#define OVERLAY_PITCH (OVERLAY_WIDTH * OVERLAY_BPP) #define OVERLAY_RGBA_MASK 0x00ff0000, 0x0000ff00, 0x000000ff, 0xff000000 // ARGB static struct OVL_Context { @@ -834,8 +831,9 @@ static struct OVL_Context { * @return SDL surface for overlay rendering */ SDL_Surface* PLAT_initOverlay(void) { - ovl.overlay = SDL_CreateRGBSurface(SDL_SWSURFACE, SCALE2(OVERLAY_WIDTH, OVERLAY_HEIGHT), - OVERLAY_DEPTH, OVERLAY_RGBA_MASK); + int overlay_size = DP(ui.pill_height); + ovl.overlay = SDL_CreateRGBSurface(SDL_SWSURFACE, overlay_size, overlay_size, OVERLAY_DEPTH, + OVERLAY_RGBA_MASK); return ovl.overlay; } diff --git a/workspace/trimuismart/platform/platform.h b/workspace/trimuismart/platform/platform.h index 572f9291..de198516 100644 --- a/workspace/trimuismart/platform/platform.h +++ b/workspace/trimuismart/platform/platform.h @@ -129,7 +129,6 @@ /////////////////////////////// #define SCREEN_DIAGONAL 2.4f // Physical screen diagonal in inches -#define FIXED_SCALE 1 // No scaling (1:1 pixel mapping) #define FIXED_WIDTH 320 // Screen width in pixels #define FIXED_HEIGHT 240 // Screen height in pixels (QVGA) #define FIXED_BPP 2 // Bytes per pixel (RGB565) diff --git a/workspace/zero28/platform/platform.c b/workspace/zero28/platform/platform.c index 054f77e8..f18d78bf 100644 --- a/workspace/zero28/platform/platform.c +++ b/workspace/zero28/platform/platform.c @@ -702,11 +702,8 @@ void PLAT_flip(SDL_Surface* IGNORED, int ignored) { // Overlay (Status Icons) /////////////////////////////// -#define OVERLAY_WIDTH PILL_SIZE // Unscaled width -#define OVERLAY_HEIGHT PILL_SIZE // Unscaled height #define OVERLAY_BPP 4 // Bytes per pixel (ARGB32) #define OVERLAY_DEPTH 16 // Bit depth -#define OVERLAY_PITCH (OVERLAY_WIDTH * OVERLAY_BPP) // Row stride #define OVERLAY_RGBA_MASK 0x00ff0000, 0x0000ff00, 0x000000ff, 0xff000000 // ARGB /** @@ -721,13 +718,14 @@ static struct OVL_Context { /** * Initializes overlay surface for status icons. * - * Creates ARGB surface at 2x scale for status indicators. + * Creates ARGB surface at DP-scaled size for status indicators. * * @return Overlay surface */ SDL_Surface* PLAT_initOverlay(void) { - ovl.overlay = SDL_CreateRGBSurface(SDL_SWSURFACE, SCALE2(OVERLAY_WIDTH, OVERLAY_HEIGHT), - OVERLAY_DEPTH, OVERLAY_RGBA_MASK); + int overlay_size = DP(ui.pill_height); + ovl.overlay = SDL_CreateRGBSurface(SDL_SWSURFACE, overlay_size, overlay_size, OVERLAY_DEPTH, + OVERLAY_RGBA_MASK); return ovl.overlay; } diff --git a/workspace/zero28/platform/platform.h b/workspace/zero28/platform/platform.h index 28038e15..69a12311 100644 --- a/workspace/zero28/platform/platform.h +++ b/workspace/zero28/platform/platform.h @@ -143,7 +143,6 @@ /////////////////////////////// #define SCREEN_DIAGONAL 2.8f // Physical screen diagonal in inches -#define FIXED_SCALE 2 // 2x scaling factor for UI #define FIXED_WIDTH 640 // Screen width in pixels #define FIXED_HEIGHT 480 // Screen height in pixels (VGA) #define FIXED_BPP 2 // Bytes per pixel (RGB565) @@ -151,12 +150,6 @@ #define FIXED_PITCH (FIXED_WIDTH * FIXED_BPP) // Row stride in bytes #define FIXED_SIZE (FIXED_PITCH * FIXED_HEIGHT) // Total framebuffer size -/////////////////////////////// -// UI Layout Configuration -/////////////////////////////// - -// MAIN_ROW_COUNT and PADDING are now calculated automatically via DP system - /////////////////////////////// // Platform-Specific Paths and Settings /////////////////////////////// From 7345c77fb20c639bad710ed10fd34f19b1f33a9b Mon Sep 17 00:00:00 2001 From: Nick Chapman Date: Wed, 26 Nov 2025 18:21:31 -0800 Subject: [PATCH 15/17] Consolidate overlay code. --- workspace/all/common/api.c | 37 ++++++++++++++++ workspace/desktop/platform/platform.c | 23 ---------- workspace/m17/platform/platform.c | 21 --------- workspace/magicmini/platform/platform.c | 40 ----------------- workspace/miyoomini/platform/platform.c | 43 ------------------ workspace/my282/platform/platform.c | 35 --------------- workspace/my355/platform/platform.c | 44 ------------------ workspace/rg35xxplus/platform/platform.c | 21 +-------- workspace/rgb30/platform/platform.c | 41 ----------------- workspace/tg5040/platform/platform.c | 23 ---------- workspace/trimuismart/platform/platform.c | 54 ----------------------- workspace/zero28/platform/platform.c | 48 -------------------- 12 files changed, 38 insertions(+), 392 deletions(-) diff --git a/workspace/all/common/api.c b/workspace/all/common/api.c index d66f90c1..28f2c782 100644 --- a/workspace/all/common/api.c +++ b/workspace/all/common/api.c @@ -2341,6 +2341,43 @@ int VIB_getStrength(void) { // Power management - Battery, sleep, brightness, volume /////////////////////////////// +// Overlay surface for fallback implementation (used if platform doesn't provide its own) +static SDL_Surface* fallback_overlay = NULL; + +/** + * Fallback overlay initialization for simple platforms. + * + * Creates a standard SDL surface for the battery warning overlay. + * Platforms with hardware overlays (e.g., rg35xx) provide their own implementation. + * + * @return SDL surface for overlay + */ +FALLBACK_IMPLEMENTATION SDL_Surface* PLAT_initOverlay(void) { + int overlay_size = DP(ui.pill_height); + fallback_overlay = SDL_CreateRGBSurface(SDL_SWSURFACE, overlay_size, overlay_size, 16, + 0x00ff0000, 0x0000ff00, 0x000000ff, 0xff000000); // ARGB + return fallback_overlay; +} + +/** + * Fallback overlay cleanup. + */ +FALLBACK_IMPLEMENTATION void PLAT_quitOverlay(void) { + if (fallback_overlay) { + SDL_FreeSurface(fallback_overlay); + fallback_overlay = NULL; + } +} + +/** + * Fallback overlay enable/disable (no-op for software compositing). + * + * @param enable Unused - software overlays are always composited + */ +FALLBACK_IMPLEMENTATION void PLAT_enableOverlay(int enable) { + (void)enable; // Overlay composited in software, no hardware control needed +} + /** * Initializes the low battery warning overlay. * diff --git a/workspace/desktop/platform/platform.c b/workspace/desktop/platform/platform.c index d5cd1872..fddad618 100644 --- a/workspace/desktop/platform/platform.c +++ b/workspace/desktop/platform/platform.c @@ -375,29 +375,6 @@ void PLAT_flip(SDL_Surface* IGNORED, int ignored) { vid.blit = NULL; } -/////////////////////////////// -// Overlay -/////////////////////////////// - -#define OVERLAY_BPP 4 -#define OVERLAY_DEPTH 16 -#define OVERLAY_RGBA_MASK 0x00ff0000, 0x0000ff00, 0x000000ff, 0xff000000 // ARGB -static struct OVL_Context { - SDL_Surface* overlay; -} ovl; - -SDL_Surface* PLAT_initOverlay(void) { - int overlay_size = DP(ui.pill_height); - ovl.overlay = SDL_CreateRGBSurface(SDL_SWSURFACE, overlay_size, overlay_size, OVERLAY_DEPTH, - OVERLAY_RGBA_MASK); - return ovl.overlay; -} -void PLAT_quitOverlay(void) { - if (ovl.overlay) - SDL_FreeSurface(ovl.overlay); -} -void PLAT_enableOverlay(int enable) {} - /////////////////////////////// // Power and Hardware /////////////////////////////// diff --git a/workspace/m17/platform/platform.c b/workspace/m17/platform/platform.c index 9f71cc20..59281945 100644 --- a/workspace/m17/platform/platform.c +++ b/workspace/m17/platform/platform.c @@ -549,27 +549,6 @@ void PLAT_flip(SDL_Surface* IGNORED, int ignored) { vid.blit = NULL; } -/////////////////////////////// - -#define OVERLAY_BPP 4 -#define OVERLAY_DEPTH 16 -#define OVERLAY_RGBA_MASK 0x00ff0000, 0x0000ff00, 0x000000ff, 0xff000000 // ARGB -static struct OVL_Context { - SDL_Surface* overlay; -} ovl; - -SDL_Surface* PLAT_initOverlay(void) { - int overlay_size = DP(ui.pill_height); - ovl.overlay = SDL_CreateRGBSurface(SDL_SWSURFACE, overlay_size, overlay_size, OVERLAY_DEPTH, - OVERLAY_RGBA_MASK); - return ovl.overlay; -} -void PLAT_quitOverlay(void) { - if (ovl.overlay) - SDL_FreeSurface(ovl.overlay); -} -void PLAT_enableOverlay(int enable) {} - /////////////////////////////// // Power management /////////////////////////////// diff --git a/workspace/magicmini/platform/platform.c b/workspace/magicmini/platform/platform.c index d4607ff4..22c1f7af 100644 --- a/workspace/magicmini/platform/platform.c +++ b/workspace/magicmini/platform/platform.c @@ -1031,46 +1031,6 @@ void PLAT_flip(SDL_Surface* IGNORED, int ignored) { vid.blit = NULL; } -/////////////////////////////// -// Overlay (unused on this platform) -/////////////////////////////// - -#define OVERLAY_BPP 4 -#define OVERLAY_DEPTH 16 -#define OVERLAY_RGBA_MASK 0x00ff0000, 0x0000ff00, 0x000000ff, 0xff000000 // ARGB -static struct OVL_Context { - SDL_Surface* overlay; -} ovl; - -/** - * Initializes overlay surface (unused on this platform). - * - * @return Overlay surface - */ -SDL_Surface* PLAT_initOverlay(void) { - int overlay_size = DP(ui.pill_height); - ovl.overlay = SDL_CreateRGBSurface(SDL_SWSURFACE, overlay_size, overlay_size, OVERLAY_DEPTH, - OVERLAY_RGBA_MASK); - return ovl.overlay; -} - -/** - * Frees overlay surface. - */ -void PLAT_quitOverlay(void) { - if (ovl.overlay) - SDL_FreeSurface(ovl.overlay); -} - -/** - * Enables or disables overlay (no-op on this platform). - * - * @param enable Ignored - */ -void PLAT_enableOverlay(int enable) { - // Not implemented on this platform -} - /////////////////////////////// // Power Management /////////////////////////////// diff --git a/workspace/miyoomini/platform/platform.c b/workspace/miyoomini/platform/platform.c index b98578dd..214df07d 100644 --- a/workspace/miyoomini/platform/platform.c +++ b/workspace/miyoomini/platform/platform.c @@ -647,49 +647,6 @@ void PLAT_flip(SDL_Surface* IGNORED, int sync) { } } -/////////////////////////////// -// Overlay (On-Screen Display) -/////////////////////////////// - -#define OVERLAY_BPP 4 -#define OVERLAY_DEPTH 16 -#define OVERLAY_RGBA_MASK 0x00ff0000, 0x0000ff00, 0x000000ff, 0xff000000 // ARGB - -static struct OVL_Context { - SDL_Surface* overlay; -} ovl; - -/** - * Initializes overlay surface for on-screen indicators. - * - * @return Overlay surface for rendering status indicators - */ -SDL_Surface* PLAT_initOverlay(void) { - int overlay_size = DP(ui.pill_height); - ovl.overlay = SDL_CreateRGBSurface(SDL_SWSURFACE, overlay_size, overlay_size, OVERLAY_DEPTH, - OVERLAY_RGBA_MASK); - return ovl.overlay; -} - -/** - * Cleans up overlay surface. - */ -void PLAT_quitOverlay(void) { - if (ovl.overlay) - SDL_FreeSurface(ovl.overlay); -} - -/** - * Enables or disables overlay display. - * - * @param enable 1 to enable, 0 to disable - * - * @note Currently no-op, overlay always composited by upper layers - */ -void PLAT_enableOverlay(int enable) { - // Overlay composited in software, no hardware control needed -} - /////////////////////////////// // Power Management - AXP223 PMIC (Plus Model) /////////////////////////////// diff --git a/workspace/my282/platform/platform.c b/workspace/my282/platform/platform.c index dceacb11..5e7aa23d 100644 --- a/workspace/my282/platform/platform.c +++ b/workspace/my282/platform/platform.c @@ -840,41 +840,6 @@ void PLAT_flip(SDL_Surface* IGNORED, int ignored) { vid.blit = NULL; } -/////////////////////////////// -// Overlay Handling -/////////////////////////////// -#define OVERLAY_BPP 4 -#define OVERLAY_DEPTH 16 -#define OVERLAY_RGBA_MASK 0x00ff0000, 0x0000ff00, 0x000000ff, 0xff000000 // ARGB -static struct OVL_Context { - SDL_Surface* overlay; -} ovl; - -/** - * Initializes overlay surface for status pills. - * - * @return Pointer to overlay surface - */ -SDL_Surface* PLAT_initOverlay(void) { - int overlay_size = DP(ui.pill_height); - ovl.overlay = SDL_CreateRGBSurface(SDL_SWSURFACE, overlay_size, overlay_size, OVERLAY_DEPTH, - OVERLAY_RGBA_MASK); - return ovl.overlay; -} - -/** - * Shuts down overlay and frees resources. - */ -void PLAT_quitOverlay(void) { - if (ovl.overlay) - SDL_FreeSurface(ovl.overlay); -} - -/** - * Enables or disables overlay rendering (not implemented). - */ -void PLAT_enableOverlay(int enable) {} - /////////////////////////////// // Power Management /////////////////////////////// diff --git a/workspace/my355/platform/platform.c b/workspace/my355/platform/platform.c index 65ca9c5b..87612a2e 100644 --- a/workspace/my355/platform/platform.c +++ b/workspace/my355/platform/platform.c @@ -812,50 +812,6 @@ int PLAT_supportsOverscan(void) { return 0; } -/////////////////////////////// -// Overlay (HUD Icons) -/////////////////////////////// - -#define OVERLAY_BPP 4 -#define OVERLAY_DEPTH 16 -#define OVERLAY_RGBA_MASK 0x00ff0000, 0x0000ff00, 0x000000ff, 0xff000000 // ARGB -static struct OVL_Context { - SDL_Surface* overlay; -} ovl; - -/** - * Initializes overlay surface for HUD icons. - * - * Creates ARGB surface for battery, volume, and other status icons. - * - * @return Overlay surface pointer - */ -SDL_Surface* PLAT_initOverlay(void) { - int overlay_size = DP(ui.pill_height); - ovl.overlay = SDL_CreateRGBSurface(SDL_SWSURFACE, overlay_size, overlay_size, OVERLAY_DEPTH, - OVERLAY_RGBA_MASK); - return ovl.overlay; -} - -/** - * Cleans up overlay resources. - */ -void PLAT_quitOverlay(void) { - if (ovl.overlay) - SDL_FreeSurface(ovl.overlay); -} - -/** - * Enables or disables overlay rendering (not implemented). - * - * @param enable Whether to enable overlay - * - * @note Overlay is always composited with main screen - */ -void PLAT_enableOverlay(int enable) { - // Overlay always enabled -} - /////////////////////////////// // Power and Battery Management /////////////////////////////// diff --git a/workspace/rg35xxplus/platform/platform.c b/workspace/rg35xxplus/platform/platform.c index 87e1b4a7..dd632220 100644 --- a/workspace/rg35xxplus/platform/platform.c +++ b/workspace/rg35xxplus/platform/platform.c @@ -1088,26 +1088,7 @@ int PLAT_supportsOverscan(void) { } /////////////////////////////// - -#define OVERLAY_BPP 4 -#define OVERLAY_DEPTH 16 -#define OVERLAY_RGBA_MASK 0x00ff0000, 0x0000ff00, 0x000000ff, 0xff000000 // ARGB -static struct OVL_Context { - SDL_Surface* overlay; -} ovl; - -SDL_Surface* PLAT_initOverlay(void) { - int overlay_size = DP(ui.pill_height); - ovl.overlay = SDL_CreateRGBSurface(SDL_SWSURFACE, overlay_size, overlay_size, OVERLAY_DEPTH, - OVERLAY_RGBA_MASK); - return ovl.overlay; -} -void PLAT_quitOverlay(void) { - if (ovl.overlay) - SDL_FreeSurface(ovl.overlay); -} -void PLAT_enableOverlay(int enable) {} - +// Power Management /////////////////////////////// static int online = 0; diff --git a/workspace/rgb30/platform/platform.c b/workspace/rgb30/platform/platform.c index c14cfb1e..c263f022 100644 --- a/workspace/rgb30/platform/platform.c +++ b/workspace/rgb30/platform/platform.c @@ -856,47 +856,6 @@ int PLAT_supportsOverscan(void) { return 1; } -/////////////////////////////// -// Overlay System -/////////////////////////////// - -#define OVERLAY_BPP 4 -#define OVERLAY_DEPTH 16 -#define OVERLAY_RGBA_MASK 0x00ff0000, 0x0000ff00, 0x000000ff, 0xff000000 // ARGB -static struct OVL_Context { - SDL_Surface* overlay; -} ovl; - -/** - * Initializes overlay surface for on-screen indicators. - * - * Creates an ARGB surface for rendering UI overlays like volume - * and brightness indicators. - * - * @return Pointer to overlay surface - */ -SDL_Surface* PLAT_initOverlay(void) { - int overlay_size = DP(ui.pill_height); - ovl.overlay = SDL_CreateRGBSurface(SDL_SWSURFACE, overlay_size, overlay_size, OVERLAY_DEPTH, - OVERLAY_RGBA_MASK); - return ovl.overlay; -} - -/** - * Frees overlay surface resources. - */ -void PLAT_quitOverlay(void) { - if (ovl.overlay) - SDL_FreeSurface(ovl.overlay); -} - -/** - * Enables or disables overlay display (not implemented). - * - * @param enable 1 to enable, 0 to disable - */ -void PLAT_enableOverlay(int enable) {} - /////////////////////////////// // Power Management /////////////////////////////// diff --git a/workspace/tg5040/platform/platform.c b/workspace/tg5040/platform/platform.c index cb3bf6f1..c2177ead 100644 --- a/workspace/tg5040/platform/platform.c +++ b/workspace/tg5040/platform/platform.c @@ -518,29 +518,6 @@ void PLAT_flip(SDL_Surface* IGNORED, int ignored) { vid.blit = NULL; } -/////////////////////////////// -// Overlay -/////////////////////////////// - -#define OVERLAY_BPP 4 -#define OVERLAY_DEPTH 16 -#define OVERLAY_RGBA_MASK 0x00ff0000, 0x0000ff00, 0x000000ff, 0xff000000 // ARGB -static struct OVL_Context { - SDL_Surface* overlay; -} ovl; - -SDL_Surface* PLAT_initOverlay(void) { - int overlay_size = DP(ui.pill_height); - ovl.overlay = SDL_CreateRGBSurface(SDL_SWSURFACE, overlay_size, overlay_size, OVERLAY_DEPTH, - OVERLAY_RGBA_MASK); - return ovl.overlay; -} -void PLAT_quitOverlay(void) { - if (ovl.overlay) - SDL_FreeSurface(ovl.overlay); -} -void PLAT_enableOverlay(int enable) {} - /////////////////////////////// // Power and Hardware /////////////////////////////// diff --git a/workspace/trimuismart/platform/platform.c b/workspace/trimuismart/platform/platform.c index c22fbe17..70a2aad3 100644 --- a/workspace/trimuismart/platform/platform.c +++ b/workspace/trimuismart/platform/platform.c @@ -802,60 +802,6 @@ void PLAT_flip(SDL_Surface* IGNORED, int sync) { vid.renderer = NULL; } -/////////////////////////////// -// UI Overlay (Unused) -/////////////////////////////// - -/** - * Overlay configuration for UI elements. - * - * Reserved for future use with OVERLAY_CH (channel 2). - * Would support alpha blending for on-screen UI elements. - * - * @note Currently unused - overlay layer not enabled - */ -#define OVERLAY_BPP 4 // 32-bit ARGB -#define OVERLAY_DEPTH 16 // Bit depth -#define OVERLAY_RGBA_MASK 0x00ff0000, 0x0000ff00, 0x000000ff, 0xff000000 // ARGB - -static struct OVL_Context { - SDL_Surface* overlay; -} ovl; - -/** - * Initializes overlay surface. - * - * Creates SDL surface for UI overlay elements. Currently unused - * as overlay layer is not enabled in display configuration. - * - * @return SDL surface for overlay rendering - */ -SDL_Surface* PLAT_initOverlay(void) { - int overlay_size = DP(ui.pill_height); - ovl.overlay = SDL_CreateRGBSurface(SDL_SWSURFACE, overlay_size, overlay_size, OVERLAY_DEPTH, - OVERLAY_RGBA_MASK); - return ovl.overlay; -} - -/** - * Cleans up overlay surface. - */ -void PLAT_quitOverlay(void) { - if (ovl.overlay) - SDL_FreeSurface(ovl.overlay); -} - -/** - * Enables or disables overlay layer. - * - * Not implemented - overlay layer not used. - * - * @param enable Ignored - */ -void PLAT_enableOverlay(int enable) { - // Not implemented -} - /////////////////////////////// // Battery Monitoring (LRADC) /////////////////////////////// diff --git a/workspace/zero28/platform/platform.c b/workspace/zero28/platform/platform.c index f18d78bf..5b6a68ee 100644 --- a/workspace/zero28/platform/platform.c +++ b/workspace/zero28/platform/platform.c @@ -698,54 +698,6 @@ void PLAT_flip(SDL_Surface* IGNORED, int ignored) { vid.blit = NULL; } -/////////////////////////////// -// Overlay (Status Icons) -/////////////////////////////// - -#define OVERLAY_BPP 4 // Bytes per pixel (ARGB32) -#define OVERLAY_DEPTH 16 // Bit depth -#define OVERLAY_RGBA_MASK 0x00ff0000, 0x0000ff00, 0x000000ff, 0xff000000 // ARGB - -/** - * Overlay rendering context. - * - * Provides surface for rendering status icons (battery, wifi, etc). - */ -static struct OVL_Context { - SDL_Surface* overlay; // ARGB surface for status icons -} ovl; - -/** - * Initializes overlay surface for status icons. - * - * Creates ARGB surface at DP-scaled size for status indicators. - * - * @return Overlay surface - */ -SDL_Surface* PLAT_initOverlay(void) { - int overlay_size = DP(ui.pill_height); - ovl.overlay = SDL_CreateRGBSurface(SDL_SWSURFACE, overlay_size, overlay_size, OVERLAY_DEPTH, - OVERLAY_RGBA_MASK); - return ovl.overlay; -} - -/** - * Cleans up overlay resources. - */ -void PLAT_quitOverlay(void) { - if (ovl.overlay) - SDL_FreeSurface(ovl.overlay); -} - -/** - * Enables or disables overlay (not implemented). - * - * @param enable Ignored - */ -void PLAT_enableOverlay(int enable) { - // Not implemented -} - /////////////////////////////// // Power Management /////////////////////////////// From 27662f18c5b3ecfaa1b19855cd55ae040836b74d Mon Sep 17 00:00:00 2001 From: Nick Chapman Date: Wed, 26 Nov 2025 18:37:04 -0800 Subject: [PATCH 16/17] Centralize derived display constants in defines.h and add PLATFORM macro to all platforms. --- workspace/all/common/defines.h | 35 ++++++++++++++++++----- workspace/all/common/scaler.c | 4 +-- workspace/desktop/platform/platform.h | 17 ++++------- workspace/m17/platform/platform.h | 10 ++++--- workspace/magicmini/platform/platform.h | 10 ++++--- workspace/miyoomini/platform/platform.h | 10 ++++--- workspace/my282/platform/platform.h | 10 ++++--- workspace/my355/platform/platform.h | 12 ++++---- workspace/rg35xx/platform/platform.h | 10 ++++--- workspace/rg35xxplus/platform/platform.h | 12 ++++---- workspace/rgb30/platform/platform.h | 12 ++++---- workspace/tg5040/platform/platform.c | 30 ------------------- workspace/tg5040/platform/platform.h | 10 ++++--- workspace/trimuismart/platform/platform.h | 10 ++++--- workspace/zero28/platform/platform.h | 10 ++++--- 15 files changed, 102 insertions(+), 100 deletions(-) diff --git a/workspace/all/common/defines.h b/workspace/all/common/defines.h index bc00010d..65ffaaf5 100644 --- a/workspace/all/common/defines.h +++ b/workspace/all/common/defines.h @@ -312,20 +312,41 @@ #endif /////////////////////////////// -// HDMI output configuration +// Derived display constants +// Calculated from platform-defined values /////////////////////////////// /** - * HDMI output resolution defaults. - * - * If platform doesn't define HAS_HDMI, HDMI output uses same - * resolution as the built-in screen. + * Standard display buffer calculations. + * All platforms use RGB565 (2 bytes per pixel, 16-bit depth). + */ +#define FIXED_BPP 2 // Bytes per pixel (RGB565) +#define FIXED_DEPTH (FIXED_BPP * 8) // Bit depth (16-bit color) +#define FIXED_PITCH (FIXED_WIDTH * FIXED_BPP) // Row stride in bytes +#define FIXED_SIZE (FIXED_PITCH * FIXED_HEIGHT) // Total framebuffer size + +/** + * HDMI output buffer calculations. + * If HAS_HDMI is defined, platform must provide HDMI_WIDTH/HDMI_HEIGHT. + * Otherwise, HDMI uses the same resolution as the built-in screen. */ #ifndef HAS_HDMI #define HDMI_WIDTH FIXED_WIDTH #define HDMI_HEIGHT FIXED_HEIGHT -#define HDMI_PITCH FIXED_PITCH -#define HDMI_SIZE FIXED_SIZE +#endif +#define HDMI_PITCH (HDMI_WIDTH * FIXED_BPP) // HDMI row stride +#define HDMI_SIZE (HDMI_PITCH * HDMI_HEIGHT) // HDMI framebuffer size + +/////////////////////////////// +// Audio configuration +/////////////////////////////// + +/** + * Default audio buffer size in samples. + * Platforms can override this if needed (e.g., for latency tuning). + */ +#ifndef SAMPLES +#define SAMPLES 512 #endif /////////////////////////////// diff --git a/workspace/all/common/scaler.c b/workspace/all/common/scaler.c index 25efee55..df818ecc 100644 --- a/workspace/all/common/scaler.c +++ b/workspace/all/common/scaler.c @@ -44,8 +44,8 @@ #include #include +#include "defines.h" // for HAS_NEON, FIXED_BPP, MIN #include "log.h" -#include "platform.h" // for HAS_NEON #include "scaler.h" // for function declarations and ROTATION_ constants /** @@ -3982,7 +3982,7 @@ void scaler_c32(uint32_t xmul, uint32_t ymul, void* __restrict src, void* __rest ((((cG(B) << 1) + (cG(A) * 3)) / 5) & 0x3f) << 5 | \ ((((cB(B) << 1) + (cB(A) * 3)) / 5) & 0x1f)) -#define MIN(a, b) ((int)(a) < (int)(b) ? (a) : (b)) +// MIN is provided by defines.h /** * Deinterlaced line-doubling scaler with blending. diff --git a/workspace/desktop/platform/platform.h b/workspace/desktop/platform/platform.h index a99c5450..241a5bd8 100644 --- a/workspace/desktop/platform/platform.h +++ b/workspace/desktop/platform/platform.h @@ -14,6 +14,12 @@ #ifndef PLATFORM_H #define PLATFORM_H +/////////////////////////////// +// Platform Identification +/////////////////////////////// + +#define PLATFORM "desktop" + /////////////////////////////// // Dependencies /////////////////////////////// @@ -133,17 +139,6 @@ #define SCREEN_DIAGONAL 2.78f // Virtual screen diagonal for consistent scaling #define FIXED_WIDTH 640 // Screen width in pixels #define FIXED_HEIGHT 480 // Screen height in pixels (VGA) -#define FIXED_BPP 2 // Bytes per pixel (RGB565) -#define FIXED_DEPTH (FIXED_BPP * 8) // Bit depth (16-bit color) -#define FIXED_PITCH (FIXED_WIDTH * FIXED_BPP) // Row stride in bytes -#define FIXED_SIZE (FIXED_PITCH * FIXED_HEIGHT) // Total framebuffer size - -// HDMI output support (currently disabled) -// #define HAS_HDMI 1 -// #define HDMI_WIDTH 1280 -// #define HDMI_HEIGHT 720 -// #define HDMI_PITCH (HDMI_WIDTH * FIXED_BPP) -// #define HDMI_SIZE (HDMI_PITCH * HDMI_HEIGHT) /////////////////////////////// // Platform-Specific Paths and Settings diff --git a/workspace/m17/platform/platform.h b/workspace/m17/platform/platform.h index a162d041..1142a954 100644 --- a/workspace/m17/platform/platform.h +++ b/workspace/m17/platform/platform.h @@ -13,6 +13,12 @@ #ifndef PLATFORM_H #define PLATFORM_H +/////////////////////////////// +// Platform Identification +/////////////////////////////// + +#define PLATFORM "m17" + /////////////////////////////// // Dependencies /////////////////////////////// @@ -134,10 +140,6 @@ #define SCREEN_DIAGONAL 7.0f // Physical screen diagonal in inches (estimated) #define FIXED_WIDTH 480 // Screen width in pixels #define FIXED_HEIGHT 273 // Screen height in pixels (16:9 widescreen) -#define FIXED_BPP 2 // Bytes per pixel (RGB565) -#define FIXED_DEPTH (FIXED_BPP * 8) // Bit depth (16-bit color) -#define FIXED_PITCH (FIXED_WIDTH * FIXED_BPP) // Row stride in bytes -#define FIXED_SIZE (FIXED_PITCH * FIXED_HEIGHT) // Total framebuffer size /////////////////////////////// // Platform-Specific Paths and Settings diff --git a/workspace/magicmini/platform/platform.h b/workspace/magicmini/platform/platform.h index 417b6b9c..dd93e55c 100644 --- a/workspace/magicmini/platform/platform.h +++ b/workspace/magicmini/platform/platform.h @@ -14,6 +14,12 @@ #ifndef PLATFORM_H #define PLATFORM_H +/////////////////////////////// +// Platform Identification +/////////////////////////////// + +#define PLATFORM "magicmini" + /////////////////////////////// // Dependencies /////////////////////////////// @@ -131,10 +137,6 @@ #define SCREEN_DIAGONAL 2.8f // Physical screen diagonal in inches #define FIXED_WIDTH 640 // Screen width in pixels #define FIXED_HEIGHT 480 // Screen height in pixels (VGA) -#define FIXED_BPP 2 // Bytes per pixel (RGB565) -#define FIXED_DEPTH (FIXED_BPP * 8) // Bit depth (16-bit color) -#define FIXED_PITCH (FIXED_WIDTH * FIXED_BPP) // Row stride in bytes -#define FIXED_SIZE (FIXED_PITCH * FIXED_HEIGHT) // Total framebuffer size /////////////////////////////// // Platform-Specific Paths and Settings diff --git a/workspace/miyoomini/platform/platform.h b/workspace/miyoomini/platform/platform.h index 8150cc58..25a56895 100644 --- a/workspace/miyoomini/platform/platform.h +++ b/workspace/miyoomini/platform/platform.h @@ -15,6 +15,12 @@ #ifndef PLATFORM_H #define PLATFORM_H +/////////////////////////////// +// Platform Identification +/////////////////////////////// + +#define PLATFORM "miyoomini" + /////////////////////////////// // Dependencies /////////////////////////////// @@ -142,10 +148,6 @@ extern int is_560p; // Set to 1 for 560p screen variant #define SCREEN_DIAGONAL 2.8f // Physical screen diagonal in inches #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) -#define FIXED_BPP 2 // Bytes per pixel (RGB565) -#define FIXED_DEPTH (FIXED_BPP * 8) // Bit depth (16-bit color) -#define FIXED_PITCH (FIXED_WIDTH * FIXED_BPP) // Row stride in bytes -#define FIXED_SIZE (FIXED_PITCH * FIXED_HEIGHT) // Total framebuffer size /////////////////////////////// // Platform-Specific Paths and Settings diff --git a/workspace/my282/platform/platform.h b/workspace/my282/platform/platform.h index 955ffc13..d54655e1 100644 --- a/workspace/my282/platform/platform.h +++ b/workspace/my282/platform/platform.h @@ -13,6 +13,12 @@ #ifndef PLATFORM_H #define PLATFORM_H +/////////////////////////////// +// Platform Identification +/////////////////////////////// + +#define PLATFORM "my282" + /////////////////////////////// // Dependencies /////////////////////////////// @@ -130,10 +136,6 @@ #define SCREEN_DIAGONAL 2.8f // Physical screen diagonal in inches (estimated) #define FIXED_WIDTH 640 // Screen width in pixels #define FIXED_HEIGHT 480 // Screen height in pixels (VGA) -#define FIXED_BPP 2 // Bytes per pixel (RGB565) -#define FIXED_DEPTH (FIXED_BPP * 8) // Bit depth (16-bit color) -#define FIXED_PITCH (FIXED_WIDTH * FIXED_BPP) // Row stride in bytes -#define FIXED_SIZE (FIXED_PITCH * FIXED_HEIGHT) // Total framebuffer size /////////////////////////////// // Platform-Specific Paths and Settings diff --git a/workspace/my355/platform/platform.h b/workspace/my355/platform/platform.h index bd310225..4cc8723e 100644 --- a/workspace/my355/platform/platform.h +++ b/workspace/my355/platform/platform.h @@ -15,6 +15,12 @@ #ifndef PLATFORM_H #define PLATFORM_H +/////////////////////////////// +// Platform Identification +/////////////////////////////// + +#define PLATFORM "my355" + /////////////////////////////// // Dependencies /////////////////////////////// @@ -149,10 +155,6 @@ extern int on_hdmi; // Set to 1 when HDMI output is active #define SCREEN_DIAGONAL 3.5f // Physical screen diagonal in inches (Miyoo Flip) #define FIXED_WIDTH 640 // Screen width in pixels #define FIXED_HEIGHT 480 // Screen height in pixels (VGA) -#define FIXED_BPP 2 // Bytes per pixel (RGB565) -#define FIXED_DEPTH (FIXED_BPP * 8) // Bit depth (16-bit color) -#define FIXED_PITCH (FIXED_WIDTH * FIXED_BPP) // Row stride in bytes -#define FIXED_SIZE (FIXED_PITCH * FIXED_HEIGHT) // Total framebuffer size /////////////////////////////// // HDMI Output Specifications @@ -161,8 +163,6 @@ extern int on_hdmi; // Set to 1 when HDMI output is active #define HAS_HDMI 1 // HDMI output supported #define HDMI_WIDTH 1280 // HDMI width in pixels #define HDMI_HEIGHT 720 // HDMI height in pixels (720p) -#define HDMI_PITCH (HDMI_WIDTH * FIXED_BPP) // HDMI row stride -#define HDMI_SIZE (HDMI_PITCH * HDMI_HEIGHT) // HDMI framebuffer size /////////////////////////////// // Platform-Specific Paths and Settings diff --git a/workspace/rg35xx/platform/platform.h b/workspace/rg35xx/platform/platform.h index 5a2d21df..ea9b031d 100644 --- a/workspace/rg35xx/platform/platform.h +++ b/workspace/rg35xx/platform/platform.h @@ -16,6 +16,12 @@ #ifndef PLATFORM_H #define PLATFORM_H +/////////////////////////////// +// Platform Identification +/////////////////////////////// + +#define PLATFORM "rg35xx" + /////////////////////////////// // Dependencies /////////////////////////////// @@ -133,10 +139,6 @@ #define SCREEN_DIAGONAL 3.5f // Physical screen diagonal in inches #define FIXED_WIDTH 640 // Screen width in pixels #define FIXED_HEIGHT 480 // Screen height in pixels (VGA) -#define FIXED_BPP 2 // Bytes per pixel (RGB565) -#define FIXED_DEPTH (FIXED_BPP * 8) // Bit depth (16-bit color) -#define FIXED_PITCH (FIXED_WIDTH * FIXED_BPP) // Row stride in bytes -#define FIXED_SIZE (FIXED_PITCH * FIXED_HEIGHT) // Total framebuffer size /////////////////////////////// // Platform-Specific Paths and Settings diff --git a/workspace/rg35xxplus/platform/platform.h b/workspace/rg35xxplus/platform/platform.h index 04440c78..010065cf 100644 --- a/workspace/rg35xxplus/platform/platform.h +++ b/workspace/rg35xxplus/platform/platform.h @@ -17,6 +17,12 @@ #ifndef PLATFORM_H #define PLATFORM_H +/////////////////////////////// +// Platform Identification +/////////////////////////////// + +#define PLATFORM "rg35xxplus" + /////////////////////////////// // Dependencies /////////////////////////////// @@ -146,10 +152,6 @@ extern int on_hdmi; // Set to 1 when HDMI output is active : (is_rg34xx ? 3.4f : 3.5f)) // Diagonal: 3.95" (Cube) / 3.4" (34XX) / 3.5" (Plus) #define FIXED_WIDTH (is_cubexx ? 720 : (is_rg34xx ? 720 : 640)) // Width: 720 (H/SP) or 640 (Plus) #define FIXED_HEIGHT (is_cubexx ? 720 : 480) // Height: 720 (H) or 480 (Plus/SP) -#define FIXED_BPP 2 // Bytes per pixel (RGB565) -#define FIXED_DEPTH (FIXED_BPP * 8) // Bit depth (16-bit color) -#define FIXED_PITCH (FIXED_WIDTH * FIXED_BPP) // Row stride in bytes -#define FIXED_SIZE (FIXED_PITCH * FIXED_HEIGHT) // Total framebuffer size /////////////////////////////// // HDMI Output Specifications @@ -158,8 +160,6 @@ extern int on_hdmi; // Set to 1 when HDMI output is active #define HAS_HDMI 1 // HDMI output supported #define HDMI_WIDTH 1280 // HDMI width in pixels #define HDMI_HEIGHT 720 // HDMI height in pixels (720p) -#define HDMI_PITCH (HDMI_WIDTH * FIXED_BPP) // HDMI row stride -#define HDMI_SIZE (HDMI_PITCH * HDMI_HEIGHT) // HDMI framebuffer size /////////////////////////////// // Platform-Specific Paths and Settings diff --git a/workspace/rgb30/platform/platform.h b/workspace/rgb30/platform/platform.h index a95e4ab6..ecac7c5e 100644 --- a/workspace/rgb30/platform/platform.h +++ b/workspace/rgb30/platform/platform.h @@ -16,6 +16,12 @@ #ifndef PLATFORM_H #define PLATFORM_H +/////////////////////////////// +// Platform Identification +/////////////////////////////// + +#define PLATFORM "rgb30" + /////////////////////////////// // Dependencies /////////////////////////////// @@ -136,10 +142,6 @@ #define SCREEN_DIAGONAL 4.0f // Physical screen diagonal in inches #define FIXED_WIDTH 720 // Screen width in pixels (square display) #define FIXED_HEIGHT 720 // Screen height in pixels (1:1 aspect ratio) -#define FIXED_BPP 2 // Bytes per pixel (RGB565) -#define FIXED_DEPTH (FIXED_BPP * 8) // Bit depth (16-bit color) -#define FIXED_PITCH (FIXED_WIDTH * FIXED_BPP) // Row stride in bytes -#define FIXED_SIZE (FIXED_PITCH * FIXED_HEIGHT) // Total framebuffer size /////////////////////////////// // HDMI Output Specifications @@ -148,8 +150,6 @@ #define HAS_HDMI 1 // HDMI output supported #define HDMI_WIDTH 1280 // HDMI width in pixels #define HDMI_HEIGHT 720 // HDMI height in pixels (720p) -#define HDMI_PITCH (HDMI_WIDTH * FIXED_BPP) // HDMI row stride -#define HDMI_SIZE (HDMI_PITCH * HDMI_HEIGHT) // HDMI framebuffer size /////////////////////////////// // Platform-Specific Paths and Settings diff --git a/workspace/tg5040/platform/platform.c b/workspace/tg5040/platform/platform.c index c2177ead..67b1ede1 100644 --- a/workspace/tg5040/platform/platform.c +++ b/workspace/tg5040/platform/platform.c @@ -91,40 +91,10 @@ SDL_Surface* PLAT_initVideo(void) { // Detect Brick variant char* device = getenv("DEVICE"); is_brick = exactMatch("brick", device); - // LOG_info("DEVICE: %s is_brick: %i\n", device, is_brick); SDL_InitSubSystem(SDL_INIT_VIDEO); SDL_ShowCursor(0); - // SDL_version compiled; - // SDL_version linked; - // SDL_VERSION(&compiled); - // SDL_GetVersion(&linked); - // LOG_info("Compiled SDL version %d.%d.%d ...\n", compiled.major, compiled.minor, compiled.patch); - // LOG_info("Linked SDL version %d.%d.%d.\n", linked.major, linked.minor, linked.patch); - // - // LOG_info("Available video drivers:\n"); - // for (int i=0; i Date: Wed, 26 Nov 2025 18:44:54 -0800 Subject: [PATCH 17/17] Always use 560p on miyoomini when available. --- .../EXTRAS/Tools/miyoomini/Toggle 560p.pak/launch.sh | 9 --------- workspace/miyoomini/platform/platform.c | 6 +++--- 2 files changed, 3 insertions(+), 12 deletions(-) delete mode 100755 skeleton/EXTRAS/Tools/miyoomini/Toggle 560p.pak/launch.sh diff --git a/skeleton/EXTRAS/Tools/miyoomini/Toggle 560p.pak/launch.sh b/skeleton/EXTRAS/Tools/miyoomini/Toggle 560p.pak/launch.sh deleted file mode 100755 index 2c96a4c5..00000000 --- a/skeleton/EXTRAS/Tools/miyoomini/Toggle 560p.pak/launch.sh +++ /dev/null @@ -1,9 +0,0 @@ -#!/bin/sh - -FILE=$USERDATA_PATH/enable-560p -if [ -f $FILE ]; then - rm $FILE -else - touch $FILE -fi - diff --git a/workspace/miyoomini/platform/platform.c b/workspace/miyoomini/platform/platform.c index 214df07d..2337f984 100644 --- a/workspace/miyoomini/platform/platform.c +++ b/workspace/miyoomini/platform/platform.c @@ -350,8 +350,8 @@ static int hasMode(const char* path, const char* mode) { * * Detects hardware variant (Mini vs Plus, 480p vs 560p) and allocates * ION memory for double-buffered rendering. The Plus variant is identified - * by the presence of axp_test (AXP223 PMIC utility). The 560p mode requires - * both hardware support and user opt-in via enable-560p file. + * by the presence of axp_test (AXP223 PMIC utility). The 560p mode is + * auto-enabled when hardware supports it. * * Memory Layout: * - Allocates 2 pages (PAGE_COUNT=2) of PAGE_SIZE each @@ -365,7 +365,7 @@ static int hasMode(const char* path, const char* mode) { SDL_Surface* PLAT_initVideo(void) { // Detect hardware variants is_plus = exists("/customer/app/axp_test"); - is_560p = hasMode(MODES_PATH, "752x560p") && exists(USERDATA_PATH "/enable-560p"); + is_560p = hasMode(MODES_PATH, "752x560p"); LOG_info("is 560p: %i\n", is_560p); // Initialize SDL with custom battery handling