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

GUI: Add a fast-select "Zapper" mode for VarItemList #258

Open
wants to merge 24 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,4 @@
- OFW: GPIO: Merged gsurkov/vcp_break_support branch for usb uart bridge (WIP!!!)

### Removed:
- Nothing
- Nothing
231 changes: 230 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,109 @@ 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) {
ZapperMenu* zapper_menu = &model->zapper_menu;
VariableItem* item = zapper_menu->item;

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;

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);

canvas_set_color(canvas, ColorBlack);
canvas_draw_str(canvas, 4, item_position * 14 + 9 + (item_position + 1) * 1, option_text);
}

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

//scroll bar
#define SCROLL_BAR_HEIGHT 0
const uint8_t scroll_bar_y = canvas_height(canvas) - SCROLL_BAR_HEIGHT;
elements_scrollbar_horizontal(
canvas,
0,
scroll_bar_y,
canvas_width(canvas),
zapper_menu->current_page,
zapper_menu->total_pages);

const uint8_t draw_count = end_option - start_option;
//frame
#define GAP_SIZE_PX 1
#define FRAME_HEIGHT 14
for(int i = 0; i < draw_count; i++) {
uint8_t y = i * (FRAME_HEIGHT + GAP_SIZE_PX);
canvas_draw_rframe(canvas, 0, y, canvas_width(canvas), FRAME_HEIGHT, 3);
}

//arrow
#define ARROR_SIZE 8
const uint8_t arrow_x = canvas_width(canvas) - 9;
// ^
canvas_draw_triangle(
canvas, arrow_x, 16 - (16 - 9) / 2 - 3, ARROR_SIZE, ARROR_SIZE, CanvasDirectionBottomToTop);
if(draw_count == 1) return;
// <
canvas_draw_triangle(
canvas, arrow_x + 9 / 2, 24 - 2, ARROR_SIZE, ARROR_SIZE, CanvasDirectionRightToLeft);
if(draw_count == 2) return;
// >
canvas_draw_triangle(
canvas, arrow_x - 9 / 2 + 2, 40 - 4, ARROR_SIZE, ARROR_SIZE, CanvasDirectionLeftToRight);
if(draw_count == 3) return;
// v
canvas_draw_triangle(
canvas, arrow_x, 16 * 3 + 6 - 6, ARROR_SIZE, ARROR_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 +316,86 @@ 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;
// this ok_ever_released var is for: prevent to trigger shuffing when first enter, because without that, would make muscle memory press not possible,
// because it would start shuffle when long pressed OK and entered zapper menu if user didn't release OK in time (consumed system seems didn't consider this kind of edge case).
// the static usage is because make it keeps life among func calls,
// otherwise it would be reseted to false each time enter this func, but each calls would make it false.
// it now seted back to false when exit zapper menu.
static bool ok_ever_released = false;

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

if(event->type == InputTypeRelease && event->key == InputKeyOk) {
ok_ever_released = true;
} else if(event->type == InputTypeShort) {
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
ok_ever_released = false;
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
ok_ever_released = false;
model->is_zapper_menu_active = false;
}
} else if(event->type == InputTypeRepeat) {
if(event->key == InputKeyOk && ok_ever_released) {
zapper_menu->current_page =
(zapper_menu->current_page + 1) % zapper_menu->total_pages;
} else if(event->key == InputKeyBack) {
ok_ever_released = false;
model->is_zapper_menu_active = false;
}
} else if(event->type == InputTypeLong && event->key == InputKeyBack) {
ok_ever_released = false;
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 +405,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 +469,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 +606,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