Initial add of numeric channel switching

This commit is contained in:
markmental 2026-03-28 17:35:13 -04:00
commit 1034e31a44
6 changed files with 172 additions and 16 deletions

121
src/app.c
View file

@ -14,6 +14,7 @@
#define GUIDE_BROWSE_MAX_OFFSET_MINUTES (GUIDE_BROWSE_MAX_AHEAD_MINUTES - ((int) (TIMELINE_VISIBLE_SECONDS / 60.0) - 30)) #define GUIDE_BROWSE_MAX_OFFSET_MINUTES (GUIDE_BROWSE_MAX_AHEAD_MINUTES - ((int) (TIMELINE_VISIBLE_SECONDS / 60.0) - 30))
#define CHANNEL_BANNER_DURATION_MS 5000 #define CHANNEL_BANNER_DURATION_MS 5000
#define CHANNEL_SWITCH_LOCK_DURATION_MS 7000 #define CHANNEL_SWITCH_LOCK_DURATION_MS 7000
#define NUMERIC_INPUT_TIMEOUT_MS 1000
static void configure_runtime_environment(void) { static void configure_runtime_environment(void) {
char runtime_dir[64]; char runtime_dir[64];
@ -141,16 +142,106 @@ static void tune_relative(App *app, int delta) {
app->channel_switch_lock_until = now + CHANNEL_SWITCH_LOCK_DURATION_MS; app->channel_switch_lock_until = now + CHANNEL_SWITCH_LOCK_DURATION_MS;
next_index = app->player.current_index; next_index = app->player.current_index;
fprintf(stderr, "[DEBUG tune_relative] current_index=%d, delta=%d\n", next_index, delta);
if (next_index < 0) { if (next_index < 0) {
next_index = 0; next_index = 0;
} }
next_index = (next_index + delta + app->channels.count) % app->channels.count; next_index = (next_index + delta + app->channels.count) % app->channels.count;
fprintf(stderr, "[DEBUG tune_relative] calculated next_index=%d\n", next_index);
destroy_video_texture(app); destroy_video_texture(app);
begin_startup_handoff(app); begin_startup_handoff(app);
player_tune(&app->player, next_index); player_tune(&app->player, next_index);
fprintf(stderr, "[DEBUG tune_relative] after player_tune, current_index=%d\n", app->player.current_index);
app->channel_banner_until = SDL_GetTicks() + CHANNEL_BANNER_DURATION_MS; app->channel_banner_until = SDL_GetTicks() + CHANNEL_BANNER_DURATION_MS;
} }
static int find_channel_by_number(const ChannelList *channels, int number) {
for (int i = 0; i < channels->count; i++) {
if (channels->items[i].number == number) {
return i;
}
}
return -1;
}
static void tune_to_channel(App *app, int channel_index) {
Uint32 now;
if (app->channels.count == 0 || channel_index < 0 || channel_index >= app->channels.count) {
return;
}
fprintf(stderr, "[DEBUG tune_to_channel] requested channel_index=%d, current_index=%d\n", channel_index, app->player.current_index);
now = SDL_GetTicks();
if (now < app->channel_switch_lock_until) {
fprintf(stderr, "[DEBUG tune_to_channel] LOCKED, aborting\n");
return;
}
app->channel_switch_lock_until = now + CHANNEL_SWITCH_LOCK_DURATION_MS;
destroy_video_texture(app);
begin_startup_handoff(app);
player_tune(&app->player, channel_index);
fprintf(stderr, "[DEBUG tune_to_channel] after player_tune, current_index=%d\n", app->player.current_index);
app->channel_banner_until = SDL_GetTicks() + CHANNEL_BANNER_DURATION_MS;
}
static void process_numeric_input(App *app) {
int target_number;
int channel_index;
if (app->numeric_input_length == 0) {
return;
}
target_number = atoi(app->numeric_input_buffer);
fprintf(stderr, "[DEBUG process_numeric_input] input='%s', target_number=%d, current_index=%d\n",
app->numeric_input_buffer, target_number, app->player.current_index);
channel_index = find_channel_by_number(&app->channels, target_number);
fprintf(stderr, "[DEBUG process_numeric_input] found channel_index=%d for number %d\n", channel_index, target_number);
if (channel_index >= 0) {
tune_to_channel(app, channel_index);
app->numeric_input_invalid = 0;
} else {
app->numeric_input_invalid = 1;
app->channel_banner_until = SDL_GetTicks() + CHANNEL_BANNER_DURATION_MS;
}
app->numeric_input_length = 0;
app->numeric_input_buffer[0] = '\0';
}
static void handle_numeric_key(App *app, int digit) {
Uint32 now;
if (app->channels.count == 0) {
return;
}
now = SDL_GetTicks();
if (now < app->channel_switch_lock_until) {
return;
}
if (app->numeric_input_length == 0) {
app->numeric_input_timeout = now + NUMERIC_INPUT_TIMEOUT_MS;
app->numeric_input_invalid = 0;
}
if (app->numeric_input_length < 3) {
app->numeric_input_buffer[app->numeric_input_length] = '0' + digit;
app->numeric_input_length++;
app->numeric_input_buffer[app->numeric_input_length] = '\0';
app->channel_banner_until = SDL_GetTicks() + CHANNEL_BANNER_DURATION_MS;
}
if (app->numeric_input_length >= 3) {
process_numeric_input(app);
}
}
static void browse_guide_time(App *app, int delta_minutes) { static void browse_guide_time(App *app, int delta_minutes) {
int next_offset; int next_offset;
@ -256,10 +347,10 @@ static void handle_event(App *app, const SDL_Event *event) {
app->about_modal_open = !app->about_modal_open; app->about_modal_open = !app->about_modal_open;
break; break;
case SDLK_UP: case SDLK_UP:
tune_relative(app, -1); tune_relative(app, 1);
break; break;
case SDLK_DOWN: case SDLK_DOWN:
tune_relative(app, 1); tune_relative(app, -1);
break; break;
case SDLK_LEFT: case SDLK_LEFT:
browse_guide_time(app, -GUIDE_BROWSE_STEP_MINUTES); browse_guide_time(app, -GUIDE_BROWSE_STEP_MINUTES);
@ -267,6 +358,14 @@ static void handle_event(App *app, const SDL_Event *event) {
case SDLK_RIGHT: case SDLK_RIGHT:
browse_guide_time(app, GUIDE_BROWSE_STEP_MINUTES); browse_guide_time(app, GUIDE_BROWSE_STEP_MINUTES);
break; break;
case SDLK_0: case SDLK_1: case SDLK_2: case SDLK_3: case SDLK_4:
case SDLK_5: case SDLK_6: case SDLK_7: case SDLK_8: case SDLK_9:
handle_numeric_key(app, event->key.keysym.sym - SDLK_0);
break;
case SDLK_KP_0: case SDLK_KP_1: case SDLK_KP_2: case SDLK_KP_3: case SDLK_KP_4:
case SDLK_KP_5: case SDLK_KP_6: case SDLK_KP_7: case SDLK_KP_8: case SDLK_KP_9:
handle_numeric_key(app, event->key.keysym.sym - SDLK_KP_0);
break;
default: default:
break; break;
} }
@ -357,6 +456,10 @@ void app_run(App *app) {
handle_event(app, &event); handle_event(app, &event);
} }
if (app->numeric_input_length > 0 && SDL_GetTicks() >= app->numeric_input_timeout) {
process_numeric_input(app);
}
if (update_video_texture(app) != 0) { if (update_video_texture(app) != 0) {
app->running = 0; app->running = 0;
break; break;
@ -370,6 +473,15 @@ void app_run(App *app) {
in_blackout = player_is_in_blackout(&app->player); in_blackout = player_is_in_blackout(&app->player);
now_ticks = SDL_GetTicks64(); now_ticks = SDL_GetTicks64();
now_wall = channel_wall_time_from_ticks(app->app_start_time, app->app_start_ticks, now_ticks); now_wall = channel_wall_time_from_ticks(app->app_start_time, app->app_start_ticks, now_ticks);
static Uint32 last_debug_print = 0;
if (now_ticks - last_debug_print > 1000) {
fprintf(stderr, "[DEBUG main_loop] current_index=%d, channel_number=%d, lock_remaining=%d\n",
app->player.current_index,
app->player.current_index >= 0 ? app->channels.items[app->player.current_index].number : -1,
(int)(app->channel_switch_lock_until - SDL_GetTicks()));
last_debug_print = now_ticks;
}
if (app->last_blackout_state && !in_blackout) { if (app->last_blackout_state && !in_blackout) {
app->startup_handoff_active = 1; app->startup_handoff_active = 1;
app->startup_handoff_until = SDL_GetTicks() + 400; app->startup_handoff_until = SDL_GetTicks() + 400;
@ -428,7 +540,10 @@ void app_run(App *app) {
app->player.current_index, app->player.current_index,
app->app_start_time, app->app_start_time,
now_wall, now_wall,
SDL_GetTicks() < app->channel_banner_until); SDL_GetTicks() < app->channel_banner_until,
app->numeric_input_buffer,
app->numeric_input_length,
app->numeric_input_invalid);
} }
SDL_RenderPresent(app->renderer); SDL_RenderPresent(app->renderer);

View file

@ -29,6 +29,10 @@ typedef struct App {
time_t app_start_time; time_t app_start_time;
Uint64 app_start_ticks; Uint64 app_start_ticks;
int guide_time_offset_minutes; int guide_time_offset_minutes;
char numeric_input_buffer[4];
int numeric_input_length;
Uint32 numeric_input_timeout;
int numeric_input_invalid;
ChannelList channels; ChannelList channels;
Player player; Player player;
UiFonts fonts; UiFonts fonts;

BIN
src/app.o

Binary file not shown.

View file

@ -634,7 +634,10 @@ static void draw_channel_status_banner(SDL_Renderer *renderer,
time_t app_start_time, time_t app_start_time,
time_t now_wall, time_t now_wall,
int window_width, int window_width,
int window_height) { int window_height,
const char *numeric_input_buffer,
int numeric_input_length,
int numeric_input_invalid) {
SDL_Rect banner; SDL_Rect banner;
SDL_Rect channel_pill; SDL_Rect channel_pill;
SDL_Rect info_clip; SDL_Rect info_clip;
@ -643,6 +646,7 @@ static void draw_channel_status_banner(SDL_Renderer *renderer,
char channel_text[96]; char channel_text[96];
char time_range[64]; char time_range[64];
char end_text[32]; char end_text[32];
char numeric_display[8];
time_t start_time; time_t start_time;
time_t end_time; time_t end_time;
double program_seek = 0.0; double program_seek = 0.0;
@ -689,15 +693,39 @@ static void draw_channel_status_banner(SDL_Renderer *renderer,
color_with_alpha(theme->gloss, 42), color_with_alpha(theme->gloss, 42),
theme->panel_border); theme->panel_border);
stroke_rect(renderer, &banner, theme->panel_border); stroke_rect(renderer, &banner, theme->panel_border);
draw_pill_button(renderer, theme, &channel_pill, theme->panel_fill, theme->panel_border);
draw_text_shadowed(renderer, if (numeric_input_length > 0 || numeric_input_invalid) {
fonts->medium, SDL_Color accent_fill = theme->ribbon_mid;
channel_text, SDL_Color accent_border = theme->ribbon_bottom;
&channel_pill, SDL_Color text_color = COLOR_TEXT_LIGHT;
channel_pill.x + 14,
channel_pill.y + 8, if (numeric_input_invalid) {
ensure_contrast(theme->panel_text, theme->panel_fill), snprintf(numeric_display, sizeof(numeric_display), "???");
color_with_alpha(COLOR_BLACK, 255)); } else {
snprintf(numeric_display, sizeof(numeric_display), "%s_", numeric_input_buffer);
}
draw_pill_button(renderer, theme, &channel_pill, accent_fill, accent_border);
draw_text_shadowed(renderer,
fonts->medium,
numeric_display,
&channel_pill,
channel_pill.x + 14,
channel_pill.y + 8,
text_color,
color_with_alpha(COLOR_BLACK, 255));
} else {
draw_pill_button(renderer, theme, &channel_pill, theme->panel_fill, theme->panel_border);
draw_text_shadowed(renderer,
fonts->medium,
channel_text,
&channel_pill,
channel_pill.x + 14,
channel_pill.y + 8,
ensure_contrast(theme->panel_text, theme->panel_fill),
color_with_alpha(COLOR_BLACK, 255));
}
set_draw_color(renderer, theme->status_divider); set_draw_color(renderer, theme->status_divider);
SDL_RenderDrawLine(renderer, SDL_RenderDrawLine(renderer,
channel_pill.x + channel_pill.w + 6, channel_pill.x + channel_pill.w + 6,
@ -813,7 +841,10 @@ void ui_render_fullscreen(SDL_Renderer *renderer,
int active_channel, int active_channel,
time_t app_start_time, time_t app_start_time,
time_t now_wall, time_t now_wall,
int show_channel_banner) { int show_channel_banner,
const char *numeric_input_buffer,
int numeric_input_length,
int numeric_input_invalid) {
SDL_Rect window = {0, 0, window_width, window_height}; SDL_Rect window = {0, 0, window_width, window_height};
fill_rect(renderer, &window, COLOR_BLACK); fill_rect(renderer, &window, COLOR_BLACK);
draw_video(renderer, video_texture, texture_width, texture_height, window); draw_video(renderer, video_texture, texture_width, texture_height, window);
@ -826,7 +857,10 @@ void ui_render_fullscreen(SDL_Renderer *renderer,
app_start_time, app_start_time,
now_wall, now_wall,
window_width, window_width,
window_height); window_height,
numeric_input_buffer,
numeric_input_length,
numeric_input_invalid);
} }
} }

View file

@ -59,7 +59,10 @@ void ui_render_fullscreen(SDL_Renderer *renderer,
int active_channel, int active_channel,
time_t app_start_time, time_t app_start_time,
time_t now_wall, time_t now_wall,
int show_channel_banner); int show_channel_banner,
const char *numeric_input_buffer,
int numeric_input_length,
int numeric_input_invalid);
void ui_render_guide(SDL_Renderer *renderer, void ui_render_guide(SDL_Renderer *renderer,
SDL_Texture *video_texture, SDL_Texture *video_texture,
int texture_width, int texture_width,

BIN
src/ui.o

Binary file not shown.