From 3e6d29670cecb538548c8bc42ae011a82d72dd89 Mon Sep 17 00:00:00 2001 From: markmental Date: Sat, 28 Mar 2026 00:08:52 -0400 Subject: [PATCH] More guide UI refinement --- src/theme.h | 29 +++++- src/ui.c | 251 +++++++++++++++++++++++++++++++++++++++++++--------- src/ui.o | Bin 20056 -> 24768 bytes 3 files changed, 235 insertions(+), 45 deletions(-) diff --git a/src/theme.h b/src/theme.h index 1d88983..d277ff7 100644 --- a/src/theme.h +++ b/src/theme.h @@ -18,10 +18,10 @@ static const SDL_Color COLOR_TEXT_LIGHT = {0xf5, 0xf7, 0xfa, 0xff}; static const SDL_Color COLOR_TEXT_DARK = {0x0b, 0x11, 0x1d, 0xff}; static const SDL_Color COLOR_BLACK = {0x00, 0x00, 0x00, 0xff}; static const SDL_Color COLOR_HEADER_SILVER = {0xe6, 0xe6, 0xe6, 0xff}; -static const SDL_Color COLOR_SELECTED_ROW = {0x06, 0x13, 0x2c, 0xff}; +static const SDL_Color COLOR_SELECTED_ROW = {0x14, 0x0d, 0x23, 0xff}; static const SDL_Color COLOR_UNSELECTED_ROW = {0x33, 0x44, 0x66, 0xff}; -static const SDL_Color COLOR_GUIDE_TOP = {0x00, 0x33, 0x99, 0xff}; -static const SDL_Color COLOR_GUIDE_BOTTOM = {0x00, 0x11, 0x33, 0xff}; +static const SDL_Color COLOR_GUIDE_TOP = {0x4b, 0x3d, 0x8f, 0xff}; +static const SDL_Color COLOR_GUIDE_BOTTOM = {0x2e, 0x24, 0x5e, 0xff}; static const SDL_Color COLOR_GRID_LINE = {0x89, 0xa0, 0xc5, 0xff}; static const SDL_Color COLOR_PANEL_TEXT = {0x12, 0x18, 0x24, 0xff}; static const SDL_Color COLOR_PANEL_SHADOW = {0x00, 0x00, 0x00, 0x60}; @@ -29,6 +29,29 @@ static const SDL_Color COLOR_STATUS_DIVIDER = {0xb0, 0xb8, 0xc7, 0xff}; static const SDL_Color COLOR_BLOCK_UNSELECTED = {0x1f, 0x5d, 0xc1, 0xff}; static const SDL_Color COLOR_BLOCK_SELECTED = {0xff, 0xd7, 0x00, 0xff}; static const SDL_Color COLOR_SELECTION_EDGE = {0xff, 0xf1, 0x8b, 0xff}; +static const SDL_Color COLOR_BORDER_DARK = {0x5b, 0x63, 0x72, 0xff}; +static const SDL_Color COLOR_GLOSS = {0xff, 0xff, 0xff, 0x80}; +static const SDL_Color COLOR_RIBBON_TOP = {0x44, 0x2a, 0x88, 0xff}; +static const SDL_Color COLOR_RIBBON_BOTTOM = {0x1a, 0x1f, 0x63, 0xff}; +static const SDL_Color COLOR_ROW_TOP = {0x4f, 0x5e, 0x85, 0xff}; +static const SDL_Color COLOR_ROW_MID = {0x39, 0x49, 0x72, 0xff}; +static const SDL_Color COLOR_ROW_BOTTOM = {0x25, 0x31, 0x4f, 0xff}; +static const SDL_Color COLOR_ROW_ACTIVE_TOP = {0x34, 0x24, 0x55, 0xff}; +static const SDL_Color COLOR_ROW_ACTIVE_MID = {0x18, 0x10, 0x2e, 0xff}; +static const SDL_Color COLOR_ROW_ACTIVE_BOTTOM = {0x0a, 0x07, 0x16, 0xff}; +static const SDL_Color COLOR_BLOCK_TOP = {0x66, 0x8d, 0xe8, 0xff}; +static const SDL_Color COLOR_BLOCK_MID = {0x3b, 0x69, 0xd5, 0xff}; +static const SDL_Color COLOR_BLOCK_BOTTOM = {0x1a, 0x42, 0x9c, 0xff}; +static const SDL_Color COLOR_BLOCK_ACTIVE_TOP = {0xff, 0xef, 0xa2, 0xff}; +static const SDL_Color COLOR_BLOCK_ACTIVE_MID = {0xff, 0xdb, 0x48, 0xff}; +static const SDL_Color COLOR_BLOCK_ACTIVE_BOTTOM = {0xd1, 0x9f, 0x00, 0xff}; +static const SDL_Color COLOR_ACTIVE_TEXT = {0xff, 0xcc, 0x00, 0xff}; +static const SDL_Color COLOR_ROW_LINE_HI = {0x6a, 0x5c, 0xa9, 0xff}; +static const SDL_Color COLOR_ROW_LINE_LO = {0x1f, 0x17, 0x43, 0xff}; +static const SDL_Color COLOR_BUTTON_BAR_TOP = {0xf3, 0xf3, 0xf3, 0xff}; +static const SDL_Color COLOR_BUTTON_BAR_BOTTOM = {0xc7, 0xcc, 0xd6, 0xff}; +static const SDL_Color COLOR_PILL_LIGHT = {0xfa, 0xfb, 0xff, 0xff}; +static const SDL_Color COLOR_PILL_SHADOW = {0x00, 0x00, 0x00, 0x40}; #define WINDOW_WIDTH 1280 #define WINDOW_HEIGHT 720 diff --git a/src/ui.c b/src/ui.c index 3478d2e..e612efb 100644 --- a/src/ui.c +++ b/src/ui.c @@ -51,7 +51,7 @@ static SDL_Texture *text_to_texture(SDL_Renderer *renderer, TTF_Font *font, cons return NULL; } - surface = TTF_RenderUTF8_Blended(font, text, color); + surface = TTF_RenderUTF8_Solid(font, text, color); if (!surface) { return NULL; } @@ -81,15 +81,26 @@ static void draw_cached_text(SDL_Renderer *renderer, const UiTextTexture *text_t SDL_RenderCopy(renderer, text_texture->texture, NULL, &dst); } -static void fill_vertical_gradient(SDL_Renderer *renderer, const SDL_Rect *rect, SDL_Color top, SDL_Color bottom) { +static void fill_three_stop_gradient(SDL_Renderer *renderer, + const SDL_Rect *rect, + SDL_Color top, + SDL_Color middle, + SDL_Color bottom) { + int split; + if (!rect || rect->h <= 0) { return; } + split = rect->h / 2; for (int i = 0; i < rect->h; ++i) { - Uint8 r = (Uint8) (top.r + (bottom.r - top.r) * i / SDL_max(rect->h - 1, 1)); - Uint8 g = (Uint8) (top.g + (bottom.g - top.g) * i / SDL_max(rect->h - 1, 1)); - Uint8 b = (Uint8) (top.b + (bottom.b - top.b) * i / SDL_max(rect->h - 1, 1)); + SDL_Color from = i < split ? top : middle; + SDL_Color to = i < split ? middle : bottom; + int segment_height = i < split ? SDL_max(split, 1) : SDL_max(rect->h - split, 1); + int segment_pos = i < split ? i : i - split; + Uint8 r = (Uint8) (from.r + (to.r - from.r) * segment_pos / SDL_max(segment_height - 1, 1)); + Uint8 g = (Uint8) (from.g + (to.g - from.g) * segment_pos / SDL_max(segment_height - 1, 1)); + Uint8 b = (Uint8) (from.b + (to.b - from.b) * segment_pos / SDL_max(segment_height - 1, 1)); SDL_SetRenderDrawColor(renderer, r, g, b, 255); SDL_RenderDrawLine(renderer, rect->x, rect->y + i, rect->x + rect->w, rect->y + i); } @@ -107,6 +118,99 @@ static void draw_panel_shadow(SDL_Renderer *renderer, const SDL_Rect *rect) { fill_rect_alpha(renderer, &shadow, COLOR_PANEL_SHADOW); } +static void draw_gloss_line(SDL_Renderer *renderer, const SDL_Rect *rect) { + SDL_Rect gloss = {rect->x + 4, rect->y + 1, SDL_max(rect->w - 8, 0), 2}; + if (gloss.w > 0) { + fill_rect_alpha(renderer, &gloss, COLOR_GLOSS); + } +} + +static void draw_panel_bevel(SDL_Renderer *renderer, const SDL_Rect *rect) { + SDL_Rect top_white = {rect->x + 8, rect->y, SDL_max(rect->w - 16, 0), 1}; + SDL_Rect top_black = {rect->x + 8, rect->y + 1, SDL_max(rect->w - 16, 0), 1}; + + if (top_white.w > 0) { + fill_rect_alpha(renderer, &top_white, (SDL_Color){255, 255, 255, 128}); + fill_rect_alpha(renderer, &top_black, (SDL_Color){0, 0, 0, 51}); + } +} + +static void stroke_rect_alpha(SDL_Renderer *renderer, const SDL_Rect *rect, SDL_Color color) { + SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND); + SDL_SetRenderDrawColor(renderer, color.r, color.g, color.b, color.a); + SDL_RenderDrawRect(renderer, rect); + SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_NONE); +} + +static void draw_selection_glow(SDL_Renderer *renderer, const SDL_Rect *rect) { + SDL_Rect outer = *rect; + SDL_Rect inner = {rect->x + 1, rect->y + 1, rect->w - 2, rect->h - 2}; + stroke_rect_alpha(renderer, &outer, (SDL_Color){0xff, 0xd7, 0x00, 120}); + if (inner.w > 0 && inner.h > 0) { + stroke_rect_alpha(renderer, &inner, (SDL_Color){0xff, 0xd7, 0x00, 70}); + } +} + +static void draw_rounded_top_panel(SDL_Renderer *renderer, const SDL_Rect *rect, SDL_Color fill_color) { + SDL_Rect body; + SDL_Rect top; + + if (!rect || rect->w < 8 || rect->h < 8) { + return; + } + + body = (SDL_Rect){rect->x, rect->y + 8, rect->w, rect->h - 8}; + top = (SDL_Rect){rect->x + 8, rect->y, rect->w - 16, 8}; + fill_rect(renderer, &body, fill_color); + fill_rect(renderer, &top, fill_color); + fill_rect(renderer, &(SDL_Rect){rect->x + 4, rect->y + 2, rect->w - 8, 6}, fill_color); + fill_rect(renderer, &(SDL_Rect){rect->x + 2, rect->y + 4, rect->w - 4, 4}, fill_color); + fill_rect(renderer, &(SDL_Rect){rect->x + 1, rect->y + 6, rect->w - 2, 2}, fill_color); + + set_draw_color(renderer, COLOR_BORDER_DARK); + SDL_RenderDrawLine(renderer, rect->x + 8, rect->y, rect->x + rect->w - 9, rect->y); + SDL_RenderDrawLine(renderer, rect->x + 4, rect->y + 2, rect->x + 5, rect->y + 1); + SDL_RenderDrawLine(renderer, rect->x + rect->w - 6, rect->y + 1, rect->x + rect->w - 5, rect->y + 2); + SDL_RenderDrawLine(renderer, rect->x + 1, rect->y + 6, rect->x + 1, rect->y + rect->h - 2); + SDL_RenderDrawLine(renderer, rect->x + rect->w - 2, rect->y + 6, rect->x + rect->w - 2, rect->y + rect->h - 2); + SDL_RenderDrawLine(renderer, rect->x + 1, rect->y + rect->h - 1, rect->x + rect->w - 2, rect->y + rect->h - 1); +} + +static void draw_beveled_bar(SDL_Renderer *renderer, + const SDL_Rect *rect, + SDL_Color top, + SDL_Color mid, + SDL_Color bottom, + SDL_Color top_edge, + SDL_Color bottom_edge) { + if (!rect) { + return; + } + + fill_three_stop_gradient(renderer, rect, top, mid, bottom); + SDL_SetRenderDrawColor(renderer, top_edge.r, top_edge.g, top_edge.b, 255); + SDL_RenderDrawLine(renderer, rect->x, rect->y, rect->x + rect->w - 1, rect->y); + SDL_SetRenderDrawColor(renderer, bottom_edge.r, bottom_edge.g, bottom_edge.b, 255); + SDL_RenderDrawLine(renderer, rect->x, rect->y + rect->h - 1, rect->x + rect->w - 1, rect->y + rect->h - 1); +} + +static void draw_pill_button(SDL_Renderer *renderer, const SDL_Rect *rect, SDL_Color fill, SDL_Color border) { + SDL_Rect shadow; + + if (!rect || rect->w < 10 || rect->h < 10) { + return; + } + + shadow = (SDL_Rect){rect->x + 2, rect->y + 2, rect->w, rect->h}; + fill_rect_alpha(renderer, &shadow, COLOR_PILL_SHADOW); + draw_beveled_bar(renderer, rect, COLOR_PILL_LIGHT, fill, fill, COLOR_GLOSS, border); + set_draw_color(renderer, border); + SDL_RenderDrawLine(renderer, rect->x + 4, rect->y, rect->x + rect->w - 5, rect->y); + SDL_RenderDrawLine(renderer, rect->x + 4, rect->y + rect->h - 1, rect->x + rect->w - 5, rect->y + rect->h - 1); + SDL_RenderDrawLine(renderer, rect->x, rect->y + 4, rect->x, rect->y + rect->h - 5); + SDL_RenderDrawLine(renderer, rect->x + rect->w - 1, rect->y + 4, rect->x + rect->w - 1, rect->y + rect->h - 5); +} + static void draw_text_clipped(SDL_Renderer *renderer, TTF_Font *font, const char *text, @@ -224,9 +328,18 @@ static void format_clock_label(char *buffer, size_t buffer_size, time_t now, int static void draw_timeline_header_cached(SDL_Renderer *renderer, const UiCache *cache, SDL_Rect rect) { int segments = 4; + draw_beveled_bar(renderer, + &rect, + COLOR_RIBBON_TOP, + (SDL_Color){0x36, 0x2d, 0x83, 0xff}, + COLOR_RIBBON_BOTTOM, + COLOR_GLOSS, + COLOR_BORDER_DARK); + for (int i = 0; i < segments; ++i) { int x = rect.x + (rect.w * i) / segments; - draw_cached_text(renderer, &cache->timeline_labels[i], x + 8, rect.y + 10); + int centered_x = x - cache->timeline_labels[i].width / 2; + draw_cached_text(renderer, &cache->timeline_labels[i], centered_x, rect.y + 10); set_draw_color(renderer, COLOR_GRID_LINE); SDL_RenderDrawLine(renderer, x, rect.y + rect.h - 2, x, rect.y + rect.h + 5 * 76); } @@ -244,8 +357,13 @@ static void draw_status_bar(SDL_Renderer *renderer, return; } - fill_rect(renderer, rect, COLOR_HEADER_SILVER); - stroke_rect(renderer, rect, COLOR_GRID_LINE); + draw_beveled_bar(renderer, + rect, + COLOR_BUTTON_BAR_TOP, + COLOR_HEADER_SILVER, + COLOR_BUTTON_BAR_BOTTOM, + COLOR_GLOSS, + COLOR_BORDER_DARK); format_time_compact(clock_text, sizeof(clock_text), now_wall); draw_text_clipped(renderer, font, clock_text, rect, rect->x + 12, rect->y + 10, COLOR_PANEL_TEXT); set_draw_color(renderer, COLOR_STATUS_DIVIDER); @@ -278,10 +396,11 @@ static void draw_info_panel(SDL_Renderer *renderer, } draw_panel_shadow(renderer, rect); - fill_rect(renderer, rect, COLOR_HEADER_SILVER); - accent = (SDL_Rect){rect->x, rect->y, rect->w, 36}; - fill_rect(renderer, &accent, COLOR_HIGHLIGHT_YELLOW); - stroke_rect(renderer, rect, COLOR_GRID_LINE); + draw_rounded_top_panel(renderer, rect, COLOR_HEADER_SILVER); + accent = (SDL_Rect){rect->x + 1, rect->y + 1, rect->w - 2, 38}; + fill_rect(renderer, &accent, COLOR_HEADER_SILVER); + draw_panel_bevel(renderer, rect); + stroke_rect(renderer, rect, COLOR_BORDER_DARK); live_position = channel_live_position_precise(selected_channel, app_start_ticks, now_ticks); start_time = now_wall - (time_t) live_position; @@ -305,7 +424,11 @@ static void draw_info_panel(SDL_Renderer *renderer, } static void draw_grid_background(SDL_Renderer *renderer, const SDL_Rect *grid_rect, int row_height, double pixels_per_minute) { - fill_vertical_gradient(renderer, grid_rect, COLOR_GUIDE_TOP, COLOR_GUIDE_BOTTOM); + fill_three_stop_gradient(renderer, + grid_rect, + (SDL_Color){0x34, 0x26, 0x88, 0xff}, + (SDL_Color){0x1b, 0x2f, 0x8f, 0xff}, + (SDL_Color){0x00, 0x11, 0x33, 0xff}); for (int minute = 0; minute <= 90; minute += 30) { int x = GUIDE_X_START + (int) (minute * pixels_per_minute); @@ -315,32 +438,45 @@ static void draw_grid_background(SDL_Renderer *renderer, const SDL_Rect *grid_re for (int row = 0; row <= GUIDE_VISIBLE_ROWS; ++row) { int y = grid_rect->y + row * row_height; - set_draw_color(renderer, COLOR_GRID_LINE); + set_draw_color(renderer, COLOR_ROW_LINE_HI); SDL_RenderDrawLine(renderer, grid_rect->x, y, grid_rect->x + grid_rect->w, y); + if (y + row_height - 1 <= grid_rect->y + grid_rect->h) { + set_draw_color(renderer, COLOR_ROW_LINE_LO); + SDL_RenderDrawLine(renderer, grid_rect->x, y + row_height - 1, grid_rect->x + grid_rect->w, y + row_height - 1); + } } } static void draw_footer_legend(SDL_Renderer *renderer, const UiCache *cache, int window_width, int window_height) { SDL_Rect footer = {0, window_height - 54, window_width, 54}; - SDL_Rect chip = {window_width / 2 - 170, window_height - 40, 24, 24}; + SDL_Rect chip = {window_width / 2 - 180, window_height - 38, 34, 20}; - fill_rect(renderer, &footer, COLOR_PALE_BLUE); - set_draw_color(renderer, COLOR_SLATE); - SDL_RenderDrawLine(renderer, footer.x, footer.y, footer.x + footer.w, footer.y); + fill_three_stop_gradient(renderer, &footer, COLOR_BUTTON_BAR_TOP, COLOR_HEADER_SILVER, COLOR_BUTTON_BAR_BOTTOM); + draw_gloss_line(renderer, &footer); + stroke_rect(renderer, &footer, COLOR_BORDER_DARK); - fill_rect(renderer, &chip, COLOR_HIGHLIGHT_YELLOW); - draw_cached_text(renderer, &cache->footer_a, chip.x + 7, chip.y + 2); - draw_cached_text(renderer, &cache->footer_time, chip.x + 34, chip.y + 1); + draw_pill_button(renderer, &chip, COLOR_HIGHLIGHT_YELLOW, COLOR_BORDER_DARK); + draw_cached_text(renderer, &cache->footer_a, chip.x + 11, chip.y + 1); + draw_cached_text(renderer, &cache->footer_time, chip.x + 42, chip.y - 1); - chip.x += 132; - fill_rect(renderer, &chip, COLOR_DEEP_BLUE); - draw_cached_text(renderer, &cache->footer_b, chip.x + 7, chip.y + 2); - draw_cached_text(renderer, &cache->footer_theme, chip.x + 34, chip.y + 1); + chip.x += 144; + draw_pill_button(renderer, &chip, COLOR_BLOCK_UNSELECTED, COLOR_BORDER_DARK); + draw_cached_text(renderer, &cache->footer_b, chip.x + 11, chip.y + 1); + draw_cached_text(renderer, &cache->footer_theme, chip.x + 42, chip.y - 1); - chip.x += 136; - fill_rect(renderer, &chip, COLOR_HINT_RED); - draw_cached_text(renderer, &cache->footer_c, chip.x + 7, chip.y + 2); - draw_cached_text(renderer, &cache->footer_title, chip.x + 34, chip.y + 1); + chip.x += 150; + draw_pill_button(renderer, &chip, COLOR_HINT_RED, COLOR_BORDER_DARK); + draw_cached_text(renderer, &cache->footer_c, chip.x + 11, chip.y + 1); + draw_cached_text(renderer, &cache->footer_title, chip.x + 42, chip.y - 1); +} + +static void draw_scanline_overlay(SDL_Renderer *renderer, int width, int height) { + SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND); + SDL_SetRenderDrawColor(renderer, 0, 0, 0, 26); + for (int y = 0; y < height; y += 2) { + SDL_RenderDrawLine(renderer, 0, y, width, y); + } + SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_NONE); } void ui_render_no_media(SDL_Renderer *renderer, const UiCache *cache) { @@ -384,7 +520,11 @@ void ui_render_guide(SDL_Renderer *renderer, selected_channel = &channels->items[active_channel]; } - fill_vertical_gradient(renderer, &full, COLOR_GUIDE_TOP, COLOR_GUIDE_BOTTOM); + fill_three_stop_gradient(renderer, + &full, + (SDL_Color){0x2a, 0x2e, 0x8f, 0xff}, + COLOR_GUIDE_TOP, + COLOR_GUIDE_BOTTOM); draw_info_panel(renderer, fonts, selected_channel, &info_panel, app_start_ticks, now_ticks, now_wall); draw_panel_shadow(renderer, &preview); fill_rect(renderer, &preview, COLOR_BLACK); @@ -396,7 +536,7 @@ void ui_render_guide(SDL_Renderer *renderer, for (int i = 0; i < 4; ++i) { format_clock_label(label, sizeof(label), now_wall, (int) ((TIMELINE_VISIBLE_SECONDS / 60.0 / 4) * i)); text_texture_destroy(&cache->timeline_labels[i]); - text_texture_init(&cache->timeline_labels[i], renderer, fonts->small, label, COLOR_TEXT_DARK); + text_texture_init(&cache->timeline_labels[i], renderer, fonts->small, label, COLOR_TEXT_LIGHT); } cache->timeline_label_slot = now_wall / 60; } @@ -419,9 +559,21 @@ void ui_render_guide(SDL_Renderer *renderer, int is_selected = channel_index == active_channel; SDL_Rect inset = {row_rect.x + 4, row_rect.y + 3, row_rect.w - 8, row_rect.h - 6}; - fill_rect(renderer, &row_rect, is_selected ? COLOR_SELECTED_ROW : COLOR_UNSELECTED_ROW); + draw_beveled_bar(renderer, + &row_rect, + is_selected ? COLOR_ROW_ACTIVE_TOP : COLOR_ROW_TOP, + is_selected ? COLOR_ROW_ACTIVE_MID : COLOR_ROW_MID, + is_selected ? COLOR_ROW_ACTIVE_BOTTOM : COLOR_ROW_BOTTOM, + COLOR_GLOSS, + COLOR_BORDER_DARK); fill_rect_alpha(renderer, &inset, (SDL_Color){255, 255, 255, is_selected ? 12 : 6}); - fill_rect(renderer, &sidebar, is_selected ? COLOR_SELECTED_ROW : COLOR_UNSELECTED_ROW); + draw_beveled_bar(renderer, + &sidebar, + is_selected ? COLOR_ROW_ACTIVE_TOP : COLOR_ROW_TOP, + is_selected ? COLOR_ROW_ACTIVE_MID : COLOR_ROW_MID, + is_selected ? COLOR_ROW_ACTIVE_BOTTOM : COLOR_ROW_BOTTOM, + COLOR_GLOSS, + COLOR_BORDER_DARK); if (!channels || channel_index >= channels->count) { continue; @@ -438,13 +590,27 @@ void ui_render_guide(SDL_Renderer *renderer, SDL_Rect title_rect = {block.x + 8, block.y + 8, block.w - 16, block.h - 16}; char title[128]; - draw_cached_text(renderer, &cache->channels[channel_index].name_medium, 20, row_rect.y + 12); - draw_cached_text(renderer, &cache->channels[channel_index].number_medium, 176, row_rect.y + 12); + if (is_selected) { + draw_text_clipped(renderer, fonts->medium, channel->name, &sidebar, 20, row_rect.y + 12, COLOR_ACTIVE_TEXT); + { + char number[16]; + snprintf(number, sizeof(number), "%d", channel->number); + draw_text_clipped(renderer, fonts->medium, number, &sidebar, 176, row_rect.y + 12, COLOR_ACTIVE_TEXT); + } + } else { + draw_cached_text(renderer, &cache->channels[channel_index].name_medium, 20, row_rect.y + 12); + draw_cached_text(renderer, &cache->channels[channel_index].number_medium, 176, row_rect.y + 12); + } draw_cached_text(renderer, &cache->channels[channel_index].file_small, 20, row_rect.y + 38); SDL_RenderSetClipRect(renderer, &clip); - fill_rect(renderer, &block, is_selected ? COLOR_BLOCK_SELECTED : COLOR_BLOCK_UNSELECTED); - stroke_rect(renderer, &block, is_selected ? COLOR_SELECTION_EDGE : COLOR_PALE_BLUE); + draw_beveled_bar(renderer, + &block, + is_selected ? COLOR_BLOCK_ACTIVE_TOP : COLOR_BLOCK_TOP, + is_selected ? COLOR_BLOCK_ACTIVE_MID : COLOR_BLOCK_MID, + is_selected ? COLOR_BLOCK_ACTIVE_BOTTOM : COLOR_BLOCK_BOTTOM, + is_selected ? COLOR_SELECTION_EDGE : COLOR_GLOSS, + COLOR_BORDER_DARK); fit_text_with_ellipsis(is_selected ? fonts->medium : fonts->small, channel->program_title, title_rect.w, @@ -463,11 +629,12 @@ void ui_render_guide(SDL_Renderer *renderer, if (selected_channel && active_channel >= 0 && start_index <= active_channel && active_channel < start_index + GUIDE_VISIBLE_ROWS) { SDL_Rect highlight = {0, grid.y + (active_channel - start_index) * row_height, WINDOW_WIDTH, row_height}; - stroke_rect(renderer, &highlight, COLOR_SELECTION_EDGE); - SDL_RenderDrawRect(renderer, &(SDL_Rect){GUIDE_X_START, highlight.y + 6, timeline_w, row_height - 12}); + draw_selection_glow(renderer, &highlight); + draw_selection_glow(renderer, &(SDL_Rect){GUIDE_X_START, highlight.y + 6, timeline_w, row_height - 12}); } draw_footer_legend(renderer, cache, WINDOW_WIDTH, WINDOW_HEIGHT); + draw_scanline_overlay(renderer, WINDOW_WIDTH, WINDOW_HEIGHT); } int ui_cache_init(UiCache *cache, SDL_Renderer *renderer, const UiFonts *fonts, const ChannelList *channels) { @@ -490,11 +657,11 @@ int ui_cache_init(UiCache *cache, SDL_Renderer *renderer, const UiFonts *fonts, text_texture_init(&cache->no_media_body, renderer, fonts->medium, "No channels found in ./media", COLOR_HIGHLIGHT_YELLOW) != 0 || text_texture_init(&cache->no_media_hint, renderer, fonts->small, "Add MP4 or MKV files to ./media and relaunch.", COLOR_PALE_BLUE) != 0 || text_texture_init(&cache->footer_a, renderer, fonts->medium, "A", COLOR_TEXT_DARK) != 0 || - text_texture_init(&cache->footer_time, renderer, fonts->medium, "Time", COLOR_TEXT_DARK) != 0 || + text_texture_init(&cache->footer_time, renderer, fonts->small, "TIME", COLOR_TEXT_DARK) != 0 || text_texture_init(&cache->footer_b, renderer, fonts->medium, "B", COLOR_TEXT_LIGHT) != 0 || - text_texture_init(&cache->footer_theme, renderer, fonts->medium, "Theme", COLOR_TEXT_DARK) != 0 || + text_texture_init(&cache->footer_theme, renderer, fonts->small, "THEME", COLOR_TEXT_DARK) != 0 || text_texture_init(&cache->footer_c, renderer, fonts->medium, "C", COLOR_TEXT_LIGHT) != 0 || - text_texture_init(&cache->footer_title, renderer, fonts->medium, "Title", COLOR_TEXT_DARK) != 0) { + text_texture_init(&cache->footer_title, renderer, fonts->small, "TITLE", COLOR_TEXT_DARK) != 0) { ui_cache_destroy(cache); return -1; } diff --git a/src/ui.o b/src/ui.o index 686e1e17f519f3a5f3b78707ecf00e4cd72f2f9f..4a20601d45ac95169c187b17f8aed5d2d9ef195e 100644 GIT binary patch literal 24768 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

$D8Kphttf$UBS{FVx;uG=fKsZ$%Tc3 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 SQ`I+VQAPNL9VLI$>;FF#;slQX literal 20056 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