Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a fast-select "Zapper" function for var_item type menu #258

Open
wants to merge 12 commits into
base: dev
Choose a base branch
from
222 changes: 221 additions & 1 deletion applications/services/gui/modules/variable_item_list.c
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,14 @@ struct VariableItemList {
FuriTimer* locked_timer;
};

typedef struct {
VariableItem* item;
uint8_t current_page;
uint8_t selected_option_index;
uint8_t total_options;
uint8_t total_pages;
} ZapperMenu;

typedef struct {
VariableItemArray_t items;
uint8_t position;
Expand All @@ -37,21 +45,121 @@ typedef struct {
FuriString* header;
size_t scroll_counter;
bool locked_message_visible;

/// zapper menu
bool is_zapper_menu_active;
ZapperMenu zapper_menu;
} VariableItemListModel;

static const char* allow_zapper_field[] = {"Frequency", "Modulation"};

static void variable_item_list_process_up(VariableItemList* variable_item_list);
static void variable_item_list_process_down(VariableItemList* variable_item_list);
static void variable_item_list_process_left(VariableItemList* variable_item_list);
static void variable_item_list_process_right(VariableItemList* variable_item_list);
static void variable_item_list_process_ok(VariableItemList* variable_item_list);
static void variable_item_list_process_ok_long(VariableItemList* variable_item_list);

static size_t variable_item_list_items_on_screen(VariableItemListModel* model) {
size_t res = 4;
return (furi_string_empty(model->header)) ? res : res - 1;
}

static void zapper_menu_draw(Canvas* canvas, VariableItemListModel* model) {
// TODO: ugly now
zxkmm marked this conversation as resolved.
Show resolved Hide resolved
ZapperMenu* zapper_menu = &model->zapper_menu;
VariableItem* item = zapper_menu->item;

const uint8_t item_height = 16;
// uint8_t item_width = canvas_width(canvas) - 5;

canvas_clear(canvas);
canvas_set_font(canvas, FontSecondary);

uint8_t start_option = zapper_menu->current_page * 4;
uint8_t end_option = start_option + 4;
if(end_option > zapper_menu->total_options) {
end_option = zapper_menu->total_options;
}

uint8_t original_index = item->current_value_index;

for(uint8_t i = start_option; i < end_option; i++) {
uint8_t item_position = i - start_option;
uint8_t item_y = item_position * item_height;
uint8_t item_text_y = item_y + item_height - 4;

// TODO: maybe not need highlight since we choose by ^ v < >
// if(item_position == zapper_menu->selected_option_index) {
// canvas_set_color(canvas, ColorBlack);
// elements_slightly_rounded_box(canvas, 0, item_y + 1, item_width, item_height - 2);
// canvas_set_color(canvas, ColorWhite);
// } else {
canvas_set_color(canvas, ColorBlack);
// }

// temp set current_value_index to use change_callback to get option text (TODO: find a better way, or maybe this is hardless as long as it only tune freq when back to receive page in subghz (need check))
item->current_value_index = i;
if(item->change_callback) {
item->change_callback(item);
}
const char* option_text = furi_string_get_cstr(item->current_value_text);

// paint
canvas_draw_str(canvas, 6, item_text_y, option_text);
}

// reset current_value_index
item->current_value_index = original_index;
if(item->change_callback) {
item->change_callback(item);
}

// // pager : TODO find a better place to put this widget, disable for now
// char page_info[16];
// snprintf(
// page_info,
// sizeof(page_info),
// "Page %d/%d",
// zapper_menu->current_page + 1,
// zapper_menu->total_pages);
// canvas_draw_str_aligned(
// canvas,
// canvas_width(canvas) / 2,
// canvas_height(canvas) - 10,
// AlignCenter,
// AlignBottom,
// page_info);

const uint8_t arrow_size = 9;
const uint8_t arrow_x = canvas_width(canvas) - 9;

// ^
canvas_draw_triangle(
canvas, arrow_x, 16 - (16 - 9) / 2, arrow_size, arrow_size, CanvasDirectionBottomToTop);

// <
canvas_draw_triangle(
canvas, arrow_x + 9 / 2, 24, arrow_size, arrow_size, CanvasDirectionRightToLeft);

// >
canvas_draw_triangle(
canvas, arrow_x - 9 / 2, 40, arrow_size, arrow_size, CanvasDirectionLeftToRight);

// v
canvas_draw_triangle(
canvas, arrow_x, 16 * 3 + 6, arrow_size, arrow_size, CanvasDirectionTopToBottom);
}

static void variable_item_list_draw_callback(Canvas* canvas, void* _model) {
VariableItemListModel* model = _model;
canvas_clear(canvas);

if(model->is_zapper_menu_active) {
// paint
zapper_menu_draw(canvas, model);
return;
}

const uint8_t item_height = 16;
uint8_t item_width = canvas_width(canvas) - 5;
Expand Down Expand Up @@ -220,6 +328,65 @@ void variable_item_list_set_header(VariableItemList* variable_item_list, const c
true);
}

static bool zapper_menu_input_handler(InputEvent* event, void* context) {
zxkmm marked this conversation as resolved.
Show resolved Hide resolved
VariableItemList* variable_item_list = context;
bool consumed = true;

with_view_model(
variable_item_list->view,
VariableItemListModel * model,
{
ZapperMenu* zapper_menu = &model->zapper_menu;
VariableItem* item = zapper_menu->item;

if(event->type == InputTypeShort || event->type == InputTypeRepeat) {
uint8_t selected_option = 0xFF; // as nullptr
switch(event->key) {
case InputKeyUp:
selected_option = zapper_menu->current_page * 4 + 0;
break;
case InputKeyLeft:
selected_option = zapper_menu->current_page * 4 + 1;
break;
case InputKeyRight:
selected_option = zapper_menu->current_page * 4 + 2;
break;
case InputKeyDown:
selected_option = zapper_menu->current_page * 4 + 3;
break;
case InputKeyOk:
// paging
zapper_menu->current_page =
(zapper_menu->current_page + 1) % zapper_menu->total_pages;
// reset sel-ed one
zapper_menu->selected_option_index = 0;
break;
case InputKeyBack:
// exit
model->is_zapper_menu_active = false;
break;
default:
break;
}

// check valid
if(selected_option != 0xFF &&
selected_option < zapper_menu->total_options) { //0xFF use as nullptr
// update anf callback
item->current_value_index = selected_option;
if(item->change_callback) {
item->change_callback(item);
}
// exit
model->is_zapper_menu_active = false;
}
}
},
true);

return consumed;
}

static bool variable_item_list_input_callback(InputEvent* event, void* context) {
VariableItemList* variable_item_list = context;
furi_assert(variable_item_list);
Expand All @@ -229,9 +396,17 @@ static bool variable_item_list_input_callback(InputEvent* event, void* context)
with_view_model(
variable_item_list->view,
VariableItemListModel * model,
{ locked_message_visible = model->locked_message_visible; },
{
if(model->is_zapper_menu_active) {
consumed = zapper_menu_input_handler(event, context);
} else {
locked_message_visible = model->locked_message_visible;
}
},
false);

if(consumed) return true;
Copy link
Contributor Author

@zxkmm zxkmm Oct 12, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's for prevent the zapper menu infect in the var_item meny obj.


if((event->type != InputTypePress && event->type != InputTypeRelease) &&
locked_message_visible) {
with_view_model(
Expand Down Expand Up @@ -285,6 +460,15 @@ static bool variable_item_list_input_callback(InputEvent* event, void* context)
default:
break;
}
} else if(event->type == InputTypeLong) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

only one option but switch to keep it clean

switch(event->key) {
case InputKeyOk:
consumed = true;
variable_item_list_process_ok_long(variable_item_list);
break;
default:
break;
}
}

return consumed;
Expand Down Expand Up @@ -413,6 +597,42 @@ void variable_item_list_process_ok(VariableItemList* variable_item_list) {
true);
}

void variable_item_list_process_ok_long(VariableItemList* variable_item_list) {
furi_check(variable_item_list);

with_view_model(
variable_item_list->view,
VariableItemListModel * model,
{
VariableItem* item = variable_item_list_get_selected_item(model);

bool is_allowed = false;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is just for prevent mistaken-touch for those two options field. but they works good too, just i think not needed

for(size_t i = 0; i < sizeof(allow_zapper_field) / sizeof(allow_zapper_field[0]);
i++) {
if(strcmp(furi_string_get_cstr(item->label), allow_zapper_field[i]) == 0) {
is_allowed = true;
break;
}
}

if(is_allowed) {
// init
model->is_zapper_menu_active = true;
ZapperMenu* zapper_menu = &model->zapper_menu;

zapper_menu->item = item;
zapper_menu->current_page = 0;
zapper_menu->selected_option_index = 0;
zapper_menu->total_options = item->values_count;
zapper_menu->total_pages = (zapper_menu->total_options + 3) / 4;

// update
model->scroll_counter = 0;
}
},
true);
}

static void variable_item_list_scroll_timer_callback(void* context) {
furi_assert(context);
VariableItemList* variable_item_list = context;
Expand Down
Loading