diff --git a/src/app.c b/src/app.c index e7e60df..2e74966 100644 --- a/src/app.c +++ b/src/app.c @@ -14,6 +14,7 @@ #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_SWITCH_LOCK_DURATION_MS 7000 +#define NUMERIC_INPUT_TIMEOUT_MS 1000 static void configure_runtime_environment(void) { 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; next_index = app->player.current_index; + fprintf(stderr, "[DEBUG tune_relative] current_index=%d, delta=%d\n", next_index, delta); if (next_index < 0) { next_index = 0; } 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); begin_startup_handoff(app); 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; } +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) { 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; break; case SDLK_UP: - tune_relative(app, -1); + tune_relative(app, 1); break; case SDLK_DOWN: - tune_relative(app, 1); + tune_relative(app, -1); break; case SDLK_LEFT: 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: browse_guide_time(app, GUIDE_BROWSE_STEP_MINUTES); 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: break; } @@ -357,6 +456,10 @@ void app_run(App *app) { 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) { app->running = 0; break; @@ -370,6 +473,15 @@ void app_run(App *app) { in_blackout = player_is_in_blackout(&app->player); now_ticks = SDL_GetTicks64(); 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) { app->startup_handoff_active = 1; app->startup_handoff_until = SDL_GetTicks() + 400; @@ -428,7 +540,10 @@ void app_run(App *app) { app->player.current_index, app->app_start_time, 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); diff --git a/src/app.h b/src/app.h index aaa871e..8660600 100644 --- a/src/app.h +++ b/src/app.h @@ -29,6 +29,10 @@ typedef struct App { time_t app_start_time; Uint64 app_start_ticks; int guide_time_offset_minutes; + char numeric_input_buffer[4]; + int numeric_input_length; + Uint32 numeric_input_timeout; + int numeric_input_invalid; ChannelList channels; Player player; UiFonts fonts; diff --git a/src/app.o b/src/app.o index 3984271..ee973f6 100644 Binary files a/src/app.o and b/src/app.o differ diff --git a/src/ui.c b/src/ui.c index e2f9e6c..aed51aa 100644 --- a/src/ui.c +++ b/src/ui.c @@ -634,7 +634,10 @@ static void draw_channel_status_banner(SDL_Renderer *renderer, time_t app_start_time, time_t now_wall, 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 channel_pill; SDL_Rect info_clip; @@ -643,6 +646,7 @@ static void draw_channel_status_banner(SDL_Renderer *renderer, char channel_text[96]; char time_range[64]; char end_text[32]; + char numeric_display[8]; time_t start_time; time_t end_time; double program_seek = 0.0; @@ -689,15 +693,39 @@ static void draw_channel_status_banner(SDL_Renderer *renderer, color_with_alpha(theme->gloss, 42), 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, - 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)); + + if (numeric_input_length > 0 || numeric_input_invalid) { + SDL_Color accent_fill = theme->ribbon_mid; + SDL_Color accent_border = theme->ribbon_bottom; + SDL_Color text_color = COLOR_TEXT_LIGHT; + + if (numeric_input_invalid) { + snprintf(numeric_display, sizeof(numeric_display), "???"); + } 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); SDL_RenderDrawLine(renderer, channel_pill.x + channel_pill.w + 6, @@ -813,7 +841,10 @@ void ui_render_fullscreen(SDL_Renderer *renderer, int active_channel, time_t app_start_time, 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}; fill_rect(renderer, &window, COLOR_BLACK); draw_video(renderer, video_texture, texture_width, texture_height, window); @@ -826,7 +857,10 @@ void ui_render_fullscreen(SDL_Renderer *renderer, app_start_time, now_wall, window_width, - window_height); + window_height, + numeric_input_buffer, + numeric_input_length, + numeric_input_invalid); } } diff --git a/src/ui.h b/src/ui.h index 61b5547..e54039c 100644 --- a/src/ui.h +++ b/src/ui.h @@ -59,7 +59,10 @@ void ui_render_fullscreen(SDL_Renderer *renderer, int active_channel, time_t app_start_time, 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, SDL_Texture *video_texture, int texture_width, diff --git a/src/ui.o b/src/ui.o index 10f7827..ddb19a9 100644 Binary files a/src/ui.o and b/src/ui.o differ