feat: Carousel Launcher & Triple Click Home

This commit is contained in:
dark98
2026-09-25 21:23:57 +01:00
parent ea3ccd3b83
commit 0426ea79f5
5 changed files with 56 additions and 38 deletions
+2 -4
View File
@@ -21,13 +21,11 @@ static void launcher_update(uint32_t delta_ms)
sys_status_get(&status); sys_status_get(&status);
sys_gfx_draw_status_bar(&status); sys_gfx_draw_status_bar(&status);
sys_gfx_draw_str_3x5(2, 9, "--- APPS ---", COLOR_WHITE);
const char *names[MAX_APPS]; const char *names[MAX_APPS];
for (size_t i = 0; i < s_app_count; i++) { for (size_t i = 0; i < s_app_count; i++) {
names[i] = s_registry[i]->name; 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) 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) 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(); sys_app_launch_launcher();
return; return;
} }
+4 -2
View File
@@ -1,4 +1,4 @@
#include "os_cli.h" #include "os_cli.h"
#include "os_app.h" #include "os_app.h"
#include "os_input.h" #include "os_input.h"
#include "os_gfx.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) static int cmd_input(int argc, char **argv)
{ {
if (argc < 2) { if (argc < 2) {
printf("Usage: input <click | double | long | home>\n"); printf("Usage: input <click | double | triple | long | home>\n");
return 1; return 1;
} }
@@ -170,6 +170,8 @@ static int cmd_input(int argc, char **argv)
sys_input_inject(INPUT_ACTION_CLICK); sys_input_inject(INPUT_ACTION_CLICK);
} else if (strcmp(argv[1], "double") == 0) { } else if (strcmp(argv[1], "double") == 0) {
sys_input_inject(INPUT_ACTION_DOUBLE_CLICK); 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) { } else if (strcmp(argv[1], "long") == 0) {
sys_input_inject(INPUT_ACTION_LONG_PRESS); sys_input_inject(INPUT_ACTION_LONG_PRESS);
} else if (strcmp(argv[1], "home") == 0) { } else if (strcmp(argv[1], "home") == 0) {
+24 -19
View File
@@ -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) void sys_gfx_draw_menu(int16_t y_start, const char *items[], size_t count, size_t selected_idx)
{ {
if (!items || count == 0) return; if (!items || count == 0) return;
size_t visible_count = 3; int16_t y_center = y_start + 9;
size_t top_idx = 0; int16_t y_top = y_start;
int16_t y_bot = y_start + 18;
if (selected_idx >= visible_count) { if (count == 1) {
top_idx = selected_idx - visible_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++) { // Top item: previous item in list (cyclically)
size_t item_idx = top_idx + i; size_t prev_idx = (selected_idx + count - 1) % count;
int16_t y = y_start + (i * 9); sys_gfx_draw_str_5x7(8, y_top, items[prev_idx], COLOR_WHITE);
if (item_idx == selected_idx) { // Center item: currently highlighted entry
sys_gfx_fill_rect(0, y - 1, OLED_WIDTH - 3, 9, COLOR_WHITE); sys_gfx_fill_rect(0, y_center - 1, OLED_WIDTH - 4, 9, COLOR_WHITE);
sys_gfx_draw_char_3x5(2, y + 1, '>', COLOR_BLACK); sys_gfx_draw_char_3x5(2, y_center + 1, '>', COLOR_BLACK);
sys_gfx_draw_str_5x7(8, y, items[item_idx], COLOR_BLACK); sys_gfx_draw_str_5x7(8, y_center, items[selected_idx], COLOR_BLACK);
} else {
sys_gfx_draw_str_5x7(8, y, items[item_idx], COLOR_WHITE);
}
}
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); 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; 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); sys_gfx_fill_rect(OLED_WIDTH - 2, thumb_y, 2, thumb_h, COLOR_WHITE);
} }
} }
+5 -4
View File
@@ -12,10 +12,11 @@ extern "C" {
typedef enum { typedef enum {
INPUT_ACTION_NONE = 0, INPUT_ACTION_NONE = 0,
INPUT_ACTION_CLICK, /* Short press (Next / Step / Advance) */ INPUT_ACTION_CLICK, /* Short press (Next / Step / Advance) */
INPUT_ACTION_DOUBLE_CLICK,/* Double click (Back / Cancel) */ INPUT_ACTION_DOUBLE_CLICK, /* Double click (Back / Cancel) */
INPUT_ACTION_LONG_PRESS, /* Hold > 400ms (Select / Confirm / Open) */ INPUT_ACTION_TRIPLE_CLICK, /* Triple click (Global Home / Launcher) */
INPUT_ACTION_HOLD_HOME, /* Hold > 1500ms (Return to App Launcher) */ INPUT_ACTION_LONG_PRESS, /* Hold > 380ms (Select / Confirm / Open) */
INPUT_ACTION_HOLD_HOME, /* Hold > 1400ms (Return to App Launcher) */
} input_action_t; } input_action_t;
typedef void (*sys_input_handler_t)(input_action_t action, void *user_arg); typedef void (*sys_input_handler_t)(input_action_t action, void *user_arg);
+21 -9
View File
@@ -32,12 +32,13 @@ static void dispatch_action(input_action_t action)
case INPUT_ACTION_DOUBLE_CLICK: case INPUT_ACTION_DOUBLE_CLICK:
evt_id = SYS_EVENT_INPUT_DOUBLE_CLICK; evt_id = SYS_EVENT_INPUT_DOUBLE_CLICK;
break; break;
case INPUT_ACTION_LONG_PRESS: case INPUT_ACTION_TRIPLE_CLICK:
evt_id = SYS_EVENT_INPUT_LONG_PRESS;
break;
case INPUT_ACTION_HOLD_HOME: case INPUT_ACTION_HOLD_HOME:
evt_id = SYS_EVENT_INPUT_EXTRA_LONG_PRESS; evt_id = SYS_EVENT_INPUT_EXTRA_LONG_PRESS;
break; break;
case INPUT_ACTION_LONG_PRESS:
evt_id = SYS_EVENT_INPUT_LONG_PRESS;
break;
default: default:
return; return;
} }
@@ -70,7 +71,8 @@ static void button_scanner_task(void *pvParameters)
{ {
bool last_state = false; bool last_state = false;
uint32_t press_start_ms = 0; 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 long_press_fired = false;
bool home_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; uint32_t held_duration = now_ms - press_start_ms;
if (!home_press_fired && held_duration >= HOME_PRESS_TIME_MS) { if (!home_press_fired && held_duration >= HOME_PRESS_TIME_MS) {
home_press_fired = true; home_press_fired = true;
tap_count = 0;
dispatch_action(INPUT_ACTION_HOLD_HOME); dispatch_action(INPUT_ACTION_HOLD_HOME);
} else if (!long_press_fired && !home_press_fired && held_duration >= LONG_PRESS_TIME_MS) { } else if (!long_press_fired && !home_press_fired && held_duration >= LONG_PRESS_TIME_MS) {
long_press_fired = true; long_press_fired = true;
tap_count = 0;
dispatch_action(INPUT_ACTION_LONG_PRESS); dispatch_action(INPUT_ACTION_LONG_PRESS);
} }
} else if (!current_state && last_state) { } else if (!current_state && last_state) {
// Button released // Button released
uint32_t press_duration = now_ms - press_start_ms; uint32_t press_duration = now_ms - press_start_ms;
if (press_duration >= DEBOUNCE_TIME_MS && !long_press_fired && !home_press_fired) { if (press_duration >= DEBOUNCE_TIME_MS && !long_press_fired && !home_press_fired) {
if ((now_ms - last_click_time_ms) <= DOUBLE_CLICK_GAP_MS) { if ((now_ms - last_release_time_ms) <= DOUBLE_CLICK_GAP_MS) {
dispatch_action(INPUT_ACTION_DOUBLE_CLICK); tap_count++;
last_click_time_ms = 0;
} else { } 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); 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;
} }
} }
} }