Adding helper info to the menu view

This commit is contained in:
Roman Belyakovsky
2026-04-28 23:51:12 +03:00
parent 0f877005e3
commit b05dad3386
+160 -37
View File
@@ -2,16 +2,22 @@
#include "hid_ptt.h"
#include <gui/elements.h>
#include <m-array.h>
#include <furi.h>
#include "../hid.h"
#include "../views.h"
#include "hid_icons.h"
#define TAG "HidPushToTalkMenu"
#define PTT_MENU_HELP_HINT_DELAY_MS 5000U
#define PTT_MENU_HINT_TIMER_PERIOD_MS 150U
struct HidPushToTalkMenu {
View* view;
Hid* hid;
PushToTalkMenuLongOkCallback long_ok_callback;
void* long_ok_callback_context;
PushToTalkMenuLongOkCallback long_ok_callback;
void* long_ok_callback_context;
FuriTimer* hint_timer;
};
typedef struct {
@@ -62,8 +68,71 @@ typedef struct {
size_t window_position;
PushToTalkMenuList* lists;
int lists_count;
uint32_t last_interaction_tick;
size_t hint_list_position;
size_t hint_item_position;
bool hint_visible;
uint16_t hint_scroll_tick;
} HidPushToTalkMenuModel;
static void hid_ptt_menu_mark_interaction(HidPushToTalkMenu* hid_ptt_menu) {
with_view_model(
hid_ptt_menu->view,
HidPushToTalkMenuModel * model,
{
model->last_interaction_tick = furi_get_tick();
model->hint_list_position = model->list_position;
model->hint_item_position = model->position;
model->hint_visible = false;
model->hint_scroll_tick = 0;
},
true);
}
static void hid_ptt_menu_hint_timer_callback(void* context) {
furi_assert(context);
HidPushToTalkMenu* hid_ptt_menu = context;
with_view_model(
hid_ptt_menu->view,
HidPushToTalkMenuModel * model,
{
const uint32_t now = furi_get_tick();
const bool selection_changed =
(model->list_position != model->hint_list_position) ||
(model->position != model->hint_item_position);
if(selection_changed) {
model->hint_list_position = model->list_position;
model->hint_item_position = model->position;
model->last_interaction_tick = now;
model->hint_visible = false;
model->hint_scroll_tick = 0;
} else if(!model->hint_visible) {
if((now - model->last_interaction_tick) >= PTT_MENU_HELP_HINT_DELAY_MS) {
model->hint_visible = true;
model->hint_scroll_tick = 0;
}
} else {
model->hint_scroll_tick++;
}
},
true);
}
static void hid_ptt_menu_enter_callback(void* context) {
furi_assert(context);
HidPushToTalkMenu* hid_ptt_menu = context;
hid_ptt_menu_mark_interaction(hid_ptt_menu);
furi_timer_start(
hid_ptt_menu->hint_timer, furi_ms_to_ticks(PTT_MENU_HINT_TIMER_PERIOD_MS));
}
static void hid_ptt_menu_exit_callback(void* context) {
furi_assert(context);
HidPushToTalkMenu* hid_ptt_menu = context;
furi_timer_stop(hid_ptt_menu->hint_timer);
}
static void
hid_ptt_menu_draw_list(Canvas* canvas, void* context, const PushToTalkMenuItemArray_t items) {
furi_assert(context);
@@ -96,7 +165,12 @@ static void
FuriString* disp_str;
disp_str = furi_string_alloc_set(PushToTalkMenuItemArray_cref(it)->label);
elements_string_fit_width(canvas, disp_str, item_width - (6 * 2));
size_t item_text_width = item_width - (6 * 2);
if((position == model->position) && model->hint_visible) {
// Reserve space for hint near selected item.
item_text_width = item_width / 2;
}
elements_string_fit_width(canvas, disp_str, item_text_width);
canvas_draw_str(
canvas,
@@ -104,6 +178,38 @@ static void
y_offset + (item_position * item_height) + item_height - 4,
furi_string_get_cstr(disp_str));
if((position == model->position) && model->hint_visible) {
const char* hint_prefix = "Long press";
const char* hint_suffix = "for help";
const int32_t text_y = y_offset + (item_position * item_height) + item_height - 4;
const int32_t icon_y = text_y - 8;
const size_t selected_text_w = canvas_string_width(canvas, furi_string_get_cstr(disp_str));
const int32_t hint_start_x = 6 + selected_text_w + 3;
const int32_t row_right_x = item_width - 2;
if(hint_start_x < row_right_x) {
const size_t prefix_w = canvas_string_width(canvas, hint_prefix);
const size_t icon_w = 9;
const size_t suffix_w = canvas_string_width(canvas, hint_suffix);
const size_t hint_w = prefix_w + 2 + icon_w + 2 + suffix_w;
const size_t available = row_right_x - hint_start_x;
int32_t scroll_offset = 0;
if(hint_w > available) {
const size_t overflow = hint_w - available;
const size_t cycle = overflow * 2;
const size_t step = cycle ? (model->hint_scroll_tick % cycle) : 0;
scroll_offset = (step <= overflow) ? step : (cycle - step);
}
const int32_t draw_x = hint_start_x - scroll_offset;
canvas_draw_str(canvas, draw_x, text_y, hint_prefix);
const int32_t icon_x = draw_x + prefix_w + 2;
canvas_draw_icon(canvas, icon_x, icon_y, &I_Ok_btn_9x9);
canvas_draw_str(canvas, icon_x + icon_w + 2, text_y, hint_suffix);
}
}
furi_string_free(disp_str);
}
@@ -340,38 +446,38 @@ void ptt_menu_process_ok(HidPushToTalkMenu* hid_ptt_menu) {
}
}
void ptt_menu_process_long_ok(HidPushToTalkMenu* hid_ptt_menu) {
PushToTalkMenuList* list = NULL;
PushToTalkMenuItem* item = NULL;
with_view_model(
hid_ptt_menu->view,
HidPushToTalkMenuModel * model,
{
list = &model->lists[model->list_position];
const size_t items_size = PushToTalkMenuItemArray_size(list->items);
if(model->position < items_size) {
item = PushToTalkMenuItemArray_get(list->items, model->position);
}
},
false);
if(item && list && hid_ptt_menu->long_ok_callback) {
hid_ptt_menu->long_ok_callback(
hid_ptt_menu->long_ok_callback_context,
list->index,
list->label,
item->index,
item->label);
}
void ptt_menu_process_long_ok(HidPushToTalkMenu* hid_ptt_menu) {
PushToTalkMenuList* list = NULL;
PushToTalkMenuItem* item = NULL;
with_view_model(
hid_ptt_menu->view,
HidPushToTalkMenuModel * model,
{
list = &model->lists[model->list_position];
const size_t items_size = PushToTalkMenuItemArray_size(list->items);
if(model->position < items_size) {
item = PushToTalkMenuItemArray_get(list->items, model->position);
}
},
false);
if(item && list && hid_ptt_menu->long_ok_callback) {
hid_ptt_menu->long_ok_callback(
hid_ptt_menu->long_ok_callback_context,
list->index,
list->label,
item->index,
item->label);
}
}
void ptt_menu_set_long_ok_callback(
HidPushToTalkMenu* hid_ptt_menu,
PushToTalkMenuLongOkCallback callback,
void* callback_context) {
furi_assert(hid_ptt_menu);
hid_ptt_menu->long_ok_callback = callback;
hid_ptt_menu->long_ok_callback_context = callback_context;
}
void ptt_menu_set_long_ok_callback(
HidPushToTalkMenu* hid_ptt_menu,
PushToTalkMenuLongOkCallback callback,
void* callback_context) {
furi_assert(hid_ptt_menu);
hid_ptt_menu->long_ok_callback = callback;
hid_ptt_menu->long_ok_callback_context = callback_context;
}
static bool hid_ptt_menu_input_callback(InputEvent* event, void* context) {
furi_assert(context);
@@ -410,10 +516,15 @@ static bool hid_ptt_menu_input_callback(InputEvent* event, void* context) {
consumed = true;
ptt_menu_process_down(hid_ptt_menu);
}
} else if(event->type == InputTypeLong && event->key == InputKeyOk) {
consumed = true;
ptt_menu_process_long_ok(hid_ptt_menu);
}
} else if(event->type == InputTypeLong && event->key == InputKeyOk) {
consumed = true;
ptt_menu_process_long_ok(hid_ptt_menu);
}
if(event->type != InputTypeRelease) {
hid_ptt_menu_mark_interaction(hid_ptt_menu);
}
return consumed;
}
@@ -430,6 +541,11 @@ HidPushToTalkMenu* hid_ptt_menu_alloc(Hid* hid) {
view_allocate_model(hid_ptt_menu->view, ViewModelTypeLocking, sizeof(HidPushToTalkMenuModel));
view_set_draw_callback(hid_ptt_menu->view, hid_ptt_menu_draw_callback);
view_set_input_callback(hid_ptt_menu->view, hid_ptt_menu_input_callback);
view_set_enter_callback(hid_ptt_menu->view, hid_ptt_menu_enter_callback);
view_set_exit_callback(hid_ptt_menu->view, hid_ptt_menu_exit_callback);
hid_ptt_menu->hint_timer =
furi_timer_alloc(hid_ptt_menu_hint_timer_callback, FuriTimerTypePeriodic, hid_ptt_menu);
with_view_model(
hid_ptt_menu->view,
@@ -438,6 +554,11 @@ HidPushToTalkMenu* hid_ptt_menu_alloc(Hid* hid) {
model->lists_count = 0;
model->position = 0;
model->window_position = 0;
model->last_interaction_tick = furi_get_tick();
model->hint_list_position = 0;
model->hint_item_position = 0;
model->hint_visible = false;
model->hint_scroll_tick = 0;
},
true);
return hid_ptt_menu;
@@ -445,6 +566,8 @@ HidPushToTalkMenu* hid_ptt_menu_alloc(Hid* hid) {
void hid_ptt_menu_free(HidPushToTalkMenu* hid_ptt_menu) {
furi_assert(hid_ptt_menu);
furi_timer_stop(hid_ptt_menu->hint_timer);
furi_timer_free(hid_ptt_menu->hint_timer);
with_view_model(
hid_ptt_menu->view,
HidPushToTalkMenuModel * model,