diff --git a/components/sys_app/sys_app.c b/components/sys_app/sys_app.c index afe1c0a..3842812 100644 --- a/components/sys_app/sys_app.c +++ b/components/sys_app/sys_app.c @@ -21,13 +21,11 @@ static void launcher_update(uint32_t delta_ms) sys_status_get(&status); sys_gfx_draw_status_bar(&status); - sys_gfx_draw_str_3x5(2, 9, "--- APPS ---", COLOR_WHITE); - const char *names[MAX_APPS]; for (size_t i = 0; i < s_app_count; i++) { names[i] = s_registry[i]->name; } - sys_gfx_draw_menu(15, names, s_app_count, s_launcher_selected); + sys_gfx_draw_menu(10, names, s_app_count, s_launcher_selected); } static void launcher_handle_input(input_action_t action) @@ -130,7 +128,7 @@ void sys_app_update(uint32_t delta_ms) void sys_app_dispatch_input(input_action_t action) { - if (action == INPUT_ACTION_HOLD_HOME) { + if (action == INPUT_ACTION_HOLD_HOME || action == INPUT_ACTION_TRIPLE_CLICK) { sys_app_launch_launcher(); return; } diff --git a/components/sys_cli/sys_cli.c b/components/sys_cli/sys_cli.c index 8f01ba0..32c54b8 100644 --- a/components/sys_cli/sys_cli.c +++ b/components/sys_cli/sys_cli.c @@ -1,4 +1,4 @@ -#include "os_cli.h" +#include "os_cli.h" #include "os_app.h" #include "os_input.h" #include "os_gfx.h" @@ -162,7 +162,7 @@ static int cmd_app(int argc, char **argv) static int cmd_input(int argc, char **argv) { if (argc < 2) { - printf("Usage: input \n"); + printf("Usage: input \n"); return 1; } @@ -170,6 +170,8 @@ static int cmd_input(int argc, char **argv) sys_input_inject(INPUT_ACTION_CLICK); } else if (strcmp(argv[1], "double") == 0) { sys_input_inject(INPUT_ACTION_DOUBLE_CLICK); + } else if (strcmp(argv[1], "triple") == 0) { + sys_input_inject(INPUT_ACTION_TRIPLE_CLICK); } else if (strcmp(argv[1], "long") == 0) { sys_input_inject(INPUT_ACTION_LONG_PRESS); } else if (strcmp(argv[1], "home") == 0) { diff --git a/components/sys_gfx/sys_gfx.c b/components/sys_gfx/sys_gfx.c index 2bb14df..256ee23 100644 --- a/components/sys_gfx/sys_gfx.c +++ b/components/sys_gfx/sys_gfx.c @@ -215,36 +215,41 @@ void sys_gfx_draw_action_bar(const char *left_hint, const char *right_hint) } } -/* UI Framework: Scrollable List Menu */ +/* UI Framework: Scrollable List Menu (Always centers highlighted entry) */ void sys_gfx_draw_menu(int16_t y_start, const char *items[], size_t count, size_t selected_idx) { if (!items || count == 0) return; - size_t visible_count = 3; - size_t top_idx = 0; + int16_t y_center = y_start + 9; + int16_t y_top = y_start; + int16_t y_bot = y_start + 18; - if (selected_idx >= visible_count) { - top_idx = selected_idx - visible_count + 1; + if (count == 1) { + sys_gfx_fill_rect(0, y_center - 1, OLED_WIDTH - 4, 9, COLOR_WHITE); + sys_gfx_draw_char_3x5(2, y_center + 1, '>', COLOR_BLACK); + sys_gfx_draw_str_5x7(8, y_center, items[0], COLOR_BLACK); + return; } - for (size_t i = 0; i < visible_count && (top_idx + i) < count; i++) { - size_t item_idx = top_idx + i; - int16_t y = y_start + (i * 9); + // Top item: previous item in list (cyclically) + size_t prev_idx = (selected_idx + count - 1) % count; + sys_gfx_draw_str_5x7(8, y_top, items[prev_idx], COLOR_WHITE); - if (item_idx == selected_idx) { - sys_gfx_fill_rect(0, y - 1, OLED_WIDTH - 3, 9, COLOR_WHITE); - sys_gfx_draw_char_3x5(2, y + 1, '>', COLOR_BLACK); - sys_gfx_draw_str_5x7(8, y, items[item_idx], COLOR_BLACK); - } else { - sys_gfx_draw_str_5x7(8, y, items[item_idx], COLOR_WHITE); - } - } + // Center item: currently highlighted entry + sys_gfx_fill_rect(0, y_center - 1, OLED_WIDTH - 4, 9, COLOR_WHITE); + sys_gfx_draw_char_3x5(2, y_center + 1, '>', COLOR_BLACK); + sys_gfx_draw_str_5x7(8, y_center, items[selected_idx], COLOR_BLACK); - if (count > visible_count) { + // Bottom item: next item in list (cyclically) + size_t next_idx = (selected_idx + 1) % count; + sys_gfx_draw_str_5x7(8, y_bot, items[next_idx], COLOR_WHITE); + + // Right-hand vertical scroll indicator + if (count > 1) { sys_gfx_draw_line(OLED_WIDTH - 1, y_start, OLED_WIDTH - 1, y_start + 26, COLOR_WHITE); - int16_t thumb_h = 27 / count; + int16_t thumb_h = 27 / (int16_t)count; if (thumb_h < 3) thumb_h = 3; - int16_t thumb_y = y_start + (selected_idx * (27 - thumb_h)) / (count - 1); + int16_t thumb_y = y_start + (int16_t)((selected_idx * (27 - thumb_h)) / (count - 1)); sys_gfx_fill_rect(OLED_WIDTH - 2, thumb_y, 2, thumb_h, COLOR_WHITE); } } diff --git a/components/sys_input/include/os_input.h b/components/sys_input/include/os_input.h index ae321bc..c652e58 100644 --- a/components/sys_input/include/os_input.h +++ b/components/sys_input/include/os_input.h @@ -12,10 +12,11 @@ extern "C" { typedef enum { INPUT_ACTION_NONE = 0, - INPUT_ACTION_CLICK, /* Short press (Next / Step / Advance) */ - INPUT_ACTION_DOUBLE_CLICK,/* Double click (Back / Cancel) */ - INPUT_ACTION_LONG_PRESS, /* Hold > 400ms (Select / Confirm / Open) */ - INPUT_ACTION_HOLD_HOME, /* Hold > 1500ms (Return to App Launcher) */ + INPUT_ACTION_CLICK, /* Short press (Next / Step / Advance) */ + INPUT_ACTION_DOUBLE_CLICK, /* Double click (Back / Cancel) */ + INPUT_ACTION_TRIPLE_CLICK, /* Triple click (Global Home / Launcher) */ + INPUT_ACTION_LONG_PRESS, /* Hold > 380ms (Select / Confirm / Open) */ + INPUT_ACTION_HOLD_HOME, /* Hold > 1400ms (Return to App Launcher) */ } input_action_t; typedef void (*sys_input_handler_t)(input_action_t action, void *user_arg); diff --git a/components/sys_input/sys_input.c b/components/sys_input/sys_input.c index 613c2b1..8dacb08 100644 --- a/components/sys_input/sys_input.c +++ b/components/sys_input/sys_input.c @@ -32,12 +32,13 @@ static void dispatch_action(input_action_t action) case INPUT_ACTION_DOUBLE_CLICK: evt_id = SYS_EVENT_INPUT_DOUBLE_CLICK; break; - case INPUT_ACTION_LONG_PRESS: - evt_id = SYS_EVENT_INPUT_LONG_PRESS; - break; + case INPUT_ACTION_TRIPLE_CLICK: case INPUT_ACTION_HOLD_HOME: evt_id = SYS_EVENT_INPUT_EXTRA_LONG_PRESS; break; + case INPUT_ACTION_LONG_PRESS: + evt_id = SYS_EVENT_INPUT_LONG_PRESS; + break; default: return; } @@ -70,7 +71,8 @@ static void button_scanner_task(void *pvParameters) { bool last_state = false; uint32_t press_start_ms = 0; - uint32_t last_click_time_ms = 0; + uint32_t last_release_time_ms = 0; + uint8_t tap_count = 0; bool long_press_fired = false; bool home_press_fired = false; @@ -89,22 +91,32 @@ static void button_scanner_task(void *pvParameters) uint32_t held_duration = now_ms - press_start_ms; if (!home_press_fired && held_duration >= HOME_PRESS_TIME_MS) { home_press_fired = true; + tap_count = 0; dispatch_action(INPUT_ACTION_HOLD_HOME); } else if (!long_press_fired && !home_press_fired && held_duration >= LONG_PRESS_TIME_MS) { long_press_fired = true; + tap_count = 0; dispatch_action(INPUT_ACTION_LONG_PRESS); } } else if (!current_state && last_state) { // Button released uint32_t press_duration = now_ms - press_start_ms; if (press_duration >= DEBOUNCE_TIME_MS && !long_press_fired && !home_press_fired) { - if ((now_ms - last_click_time_ms) <= DOUBLE_CLICK_GAP_MS) { - dispatch_action(INPUT_ACTION_DOUBLE_CLICK); - last_click_time_ms = 0; + if ((now_ms - last_release_time_ms) <= DOUBLE_CLICK_GAP_MS) { + tap_count++; } else { - // Instant click dispatch upon tap release (no 280ms lag penalty!) + tap_count = 1; + } + last_release_time_ms = now_ms; + + if (tap_count == 1) { dispatch_action(INPUT_ACTION_CLICK); - last_click_time_ms = now_ms; + } else if (tap_count == 2) { + dispatch_action(INPUT_ACTION_DOUBLE_CLICK); + } else if (tap_count >= 3) { + dispatch_action(INPUT_ACTION_TRIPLE_CLICK); + tap_count = 0; + last_release_time_ms = 0; } } }