[FL-3471] Infrared: buttons move feature rework (#2949)

This commit is contained in:
Nikolay Minaylov 2023-08-07 12:18:46 +03:00 committed by GitHub
parent c7648eb932
commit 4c771b66dc
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 280 additions and 176 deletions

View file

@ -165,6 +165,10 @@ static Infrared* infrared_alloc() {
view_dispatcher_add_view(
view_dispatcher, InfraredViewStack, view_stack_get_view(infrared->view_stack));
infrared->move_view = infrared_move_view_alloc();
view_dispatcher_add_view(
view_dispatcher, InfraredViewMove, infrared_move_view_get_view(infrared->move_view));
if(app_state->is_debug_enabled) {
infrared->debug_view = infrared_debug_view_alloc();
view_dispatcher_add_view(
@ -209,6 +213,9 @@ static void infrared_free(Infrared* infrared) {
view_dispatcher_remove_view(view_dispatcher, InfraredViewStack);
view_stack_free(infrared->view_stack);
view_dispatcher_remove_view(view_dispatcher, InfraredViewMove);
infrared_move_view_free(infrared->move_view);
if(app_state->is_debug_enabled) {
view_dispatcher_remove_view(view_dispatcher, InfraredViewDebugView);
infrared_debug_view_free(infrared->debug_view);

View file

@ -30,6 +30,7 @@
#include "scenes/infrared_scene.h"
#include "views/infrared_progress_view.h"
#include "views/infrared_debug_view.h"
#include "views/infrared_move_view.h"
#include "rpc/rpc_app.h"
@ -60,8 +61,6 @@ typedef enum {
InfraredEditModeNone,
InfraredEditModeRename,
InfraredEditModeDelete,
InfraredEditModeMove,
InfraredEditModeMoveSelectDest
} InfraredEditMode;
typedef struct {
@ -96,6 +95,7 @@ struct Infrared {
ViewStack* view_stack;
InfraredDebugView* debug_view;
InfraredMoveView* move_view;
ButtonPanel* button_panel;
Loading* loading;
@ -116,6 +116,7 @@ typedef enum {
InfraredViewPopup,
InfraredViewStack,
InfraredViewDebugView,
InfraredViewMove,
} InfraredView;
typedef enum {

View file

@ -108,19 +108,13 @@ bool infrared_remote_delete_button(InfraredRemote* remote, size_t index) {
return infrared_remote_store(remote);
}
bool infrared_remote_move_button(InfraredRemote* remote, size_t index_orig, size_t index_dest) {
void infrared_remote_move_button(InfraredRemote* remote, size_t index_orig, size_t index_dest) {
furi_assert(index_orig < InfraredButtonArray_size(remote->buttons));
furi_assert(index_dest <= InfraredButtonArray_size(remote->buttons));
if(index_orig == index_dest) {
return true;
}
furi_assert(index_dest < InfraredButtonArray_size(remote->buttons));
InfraredRemoteButton* button;
InfraredButtonArray_pop_at(&button, remote->buttons, index_orig);
if(index_orig > index_dest)
InfraredButtonArray_push_at(remote->buttons, index_dest, button);
else
InfraredButtonArray_push_at(remote->buttons, index_dest - 1, button);
return infrared_remote_store(remote);
InfraredButtonArray_push_at(remote->buttons, index_dest, button);
}
bool infrared_remote_store(InfraredRemote* remote) {

View file

@ -23,7 +23,7 @@ bool infrared_remote_find_button_by_name(InfraredRemote* remote, const char* nam
bool infrared_remote_add_button(InfraredRemote* remote, const char* name, InfraredSignal* signal);
bool infrared_remote_rename_button(InfraredRemote* remote, const char* new_name, size_t index);
bool infrared_remote_delete_button(InfraredRemote* remote, size_t index);
bool infrared_remote_move_button(InfraredRemote* remote, size_t index_orig, size_t index_dest);
void infrared_remote_move_button(InfraredRemote* remote, size_t index_orig, size_t index_dest);
bool infrared_remote_store(InfraredRemote* remote);
bool infrared_remote_load(InfraredRemote* remote, FuriString* path);

View file

@ -8,7 +8,6 @@ ADD_SCENE(infrared, edit_button_select, EditButtonSelect)
ADD_SCENE(infrared, edit_rename, EditRename)
ADD_SCENE(infrared, edit_rename_done, EditRenameDone)
ADD_SCENE(infrared, edit_move, EditMove)
ADD_SCENE(infrared, edit_move_done, EditMoveDone)
ADD_SCENE(infrared, learn, Learn)
ADD_SCENE(infrared, learn_done, LearnDone)
ADD_SCENE(infrared, learn_enter_name, LearnEnterName)

View file

@ -82,9 +82,7 @@ bool infrared_scene_edit_on_event(void* context, SceneManagerEvent event) {
scene_manager_next_scene(scene_manager, InfraredSceneEditButtonSelect);
consumed = true;
} else if(submenu_index == SubmenuIndexMoveButton) {
infrared->app_state.edit_target = InfraredEditTargetButton;
infrared->app_state.edit_mode = InfraredEditModeMove;
scene_manager_next_scene(scene_manager, InfraredSceneEditButtonSelect);
scene_manager_next_scene(scene_manager, InfraredSceneEditMove);
consumed = true;
} else if(submenu_index == SubmenuIndexDeleteButton) {
infrared->app_state.edit_target = InfraredEditTargetButton;

View file

@ -11,23 +11,9 @@ void infrared_scene_edit_button_select_on_enter(void* context) {
InfraredRemote* remote = infrared->remote;
InfraredAppState* app_state = &infrared->app_state;
const char* header = NULL;
switch(infrared->app_state.edit_mode) {
case InfraredEditModeRename:
header = "Rename Button:";
break;
case InfraredEditModeDelete:
header = "Delete Button:";
break;
case InfraredEditModeMove:
header = "Select Button to Move:";
break;
case InfraredEditModeMoveSelectDest:
case InfraredEditModeNone:
default:
header = "Move Button Before:";
break;
}
const char* header = infrared->app_state.edit_mode == InfraredEditModeRename ?
"Rename Button:" :
"Delete Button:";
submenu_set_header(submenu, header);
const size_t button_count = infrared_remote_get_button_count(remote);
@ -40,14 +26,6 @@ void infrared_scene_edit_button_select_on_enter(void* context) {
infrared_scene_edit_button_select_submenu_callback,
context);
}
if(infrared->app_state.edit_mode == InfraredEditModeMoveSelectDest) {
submenu_add_item(
submenu,
"-- Move to the end --",
button_count,
infrared_scene_edit_button_select_submenu_callback,
context);
}
if(button_count && app_state->current_button_index != InfraredButtonIndexNone) {
submenu_set_selected_item(submenu, app_state->current_button_index);
app_state->current_button_index = InfraredButtonIndexNone;
@ -69,12 +47,6 @@ bool infrared_scene_edit_button_select_on_event(void* context, SceneManagerEvent
scene_manager_next_scene(scene_manager, InfraredSceneEditRename);
} else if(edit_mode == InfraredEditModeDelete) {
scene_manager_next_scene(scene_manager, InfraredSceneEditDelete);
} else if(edit_mode == InfraredEditModeMove) {
app_state->current_button_index_move_orig = event.event;
app_state->edit_mode = InfraredEditModeMoveSelectDest;
scene_manager_next_scene(scene_manager, InfraredSceneEditButtonSelect);
} else if(edit_mode == InfraredEditModeMoveSelectDest) {
scene_manager_next_scene(scene_manager, InfraredSceneEditMove);
} else {
furi_assert(0);
}

View file

@ -1,103 +1,44 @@
#include "../infrared_i.h"
static void infrared_scene_edit_move_dialog_result_callback(DialogExResult result, void* context) {
Infrared* infrared = context;
view_dispatcher_send_custom_event(infrared->view_dispatcher, result);
static void infrared_scene_move_button(uint32_t index_old, uint32_t index_new, void* context) {
InfraredRemote* remote = context;
furi_assert(remote);
infrared_remote_move_button(remote, index_old, index_new);
}
static const char* infrared_scene_get_btn_name(uint32_t index, void* context) {
InfraredRemote* remote = context;
furi_assert(remote);
InfraredRemoteButton* button = infrared_remote_get_button(remote, index);
return (infrared_remote_button_get_name(button));
}
void infrared_scene_edit_move_on_enter(void* context) {
Infrared* infrared = context;
DialogEx* dialog_ex = infrared->dialog_ex;
InfraredRemote* remote = infrared->remote;
const InfraredEditTarget edit_target = infrared->app_state.edit_target;
if(edit_target == InfraredEditTargetButton) {
int32_t current_button_index = infrared->app_state.current_button_index_move_orig;
furi_assert(current_button_index != InfraredButtonIndexNone);
infrared_move_view_set_callback(infrared->move_view, infrared_scene_move_button);
dialog_ex_set_header(dialog_ex, "Move Button?", 64, 0, AlignCenter, AlignTop);
InfraredRemoteButton* current_button =
infrared_remote_get_button(remote, current_button_index);
InfraredSignal* signal = infrared_remote_button_get_signal(current_button);
uint32_t btn_count = infrared_remote_get_button_count(remote);
infrared_move_view_list_init(
infrared->move_view, btn_count, infrared_scene_get_btn_name, remote);
infrared_move_view_list_update(infrared->move_view);
if(infrared_signal_is_raw(signal)) {
const InfraredRawSignal* raw = infrared_signal_get_raw_signal(signal);
infrared_text_store_set(
infrared,
0,
"%s\nRAW\n%ld samples",
infrared_remote_button_get_name(current_button),
raw->timings_size);
} else {
const InfraredMessage* message = infrared_signal_get_message(signal);
infrared_text_store_set(
infrared,
0,
"%s\n%s\nA=0x%0*lX C=0x%0*lX",
infrared_remote_button_get_name(current_button),
infrared_get_protocol_name(message->protocol),
ROUND_UP_TO(infrared_get_protocol_address_length(message->protocol), 4),
message->address,
ROUND_UP_TO(infrared_get_protocol_command_length(message->protocol), 4),
message->command);
}
} else {
furi_assert(0);
}
dialog_ex_set_text(dialog_ex, infrared->text_store[0], 64, 31, AlignCenter, AlignCenter);
dialog_ex_set_icon(dialog_ex, 0, 0, NULL);
dialog_ex_set_left_button_text(dialog_ex, "Cancel");
dialog_ex_set_right_button_text(dialog_ex, "Move");
dialog_ex_set_result_callback(dialog_ex, infrared_scene_edit_move_dialog_result_callback);
dialog_ex_set_context(dialog_ex, context);
view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewDialogEx);
view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewMove);
}
bool infrared_scene_edit_move_on_event(void* context, SceneManagerEvent event) {
Infrared* infrared = context;
SceneManager* scene_manager = infrared->scene_manager;
bool consumed = false;
if(event.type == SceneManagerEventTypeCustom) {
if(event.event == DialogExResultLeft) {
scene_manager_previous_scene(scene_manager);
consumed = true;
} else if(event.event == DialogExResultRight) {
bool success = false;
InfraredRemote* remote = infrared->remote;
InfraredAppState* app_state = &infrared->app_state;
const InfraredEditTarget edit_target = app_state->edit_target;
if(edit_target == InfraredEditTargetButton) {
furi_assert(app_state->current_button_index != InfraredButtonIndexNone);
success = infrared_remote_move_button(
remote,
app_state->current_button_index_move_orig,
app_state->current_button_index);
app_state->current_button_index_move_orig = InfraredButtonIndexNone;
app_state->current_button_index = InfraredButtonIndexNone;
} else {
furi_assert(0);
}
if(success) {
scene_manager_next_scene(scene_manager, InfraredSceneEditMoveDone);
} else {
const uint32_t possible_scenes[] = {InfraredSceneRemoteList, InfraredSceneStart};
scene_manager_search_and_switch_to_previous_scene_one_of(
scene_manager, possible_scenes, COUNT_OF(possible_scenes));
}
consumed = true;
}
}
UNUSED(event);
UNUSED(infrared);
return consumed;
}
void infrared_scene_edit_move_on_exit(void* context) {
Infrared* infrared = context;
UNUSED(infrared);
InfraredRemote* remote = infrared->remote;
infrared_remote_store(remote);
}

View file

@ -1,48 +0,0 @@
#include "../infrared_i.h"
void infrared_scene_edit_move_done_on_enter(void* context) {
Infrared* infrared = context;
Popup* popup = infrared->popup;
popup_set_icon(popup, 0, 2, &I_DolphinMafia_115x62);
popup_set_header(popup, "Moved", 83, 19, AlignLeft, AlignBottom);
popup_set_callback(popup, infrared_popup_closed_callback);
popup_set_context(popup, context);
popup_set_timeout(popup, 1500);
popup_enable_timeout(popup);
view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewPopup);
}
bool infrared_scene_edit_move_done_on_event(void* context, SceneManagerEvent event) {
Infrared* infrared = context;
SceneManager* scene_manager = infrared->scene_manager;
bool consumed = false;
if(event.type == SceneManagerEventTypeCustom) {
if(event.event == InfraredCustomEventTypePopupClosed) {
const InfraredEditTarget edit_target = infrared->app_state.edit_target;
if(edit_target == InfraredEditTargetButton) {
scene_manager_search_and_switch_to_previous_scene(
scene_manager, InfraredSceneRemote);
} else if(edit_target == InfraredEditTargetRemote) {
const uint32_t possible_scenes[] = {InfraredSceneStart, InfraredSceneRemoteList};
if(!scene_manager_search_and_switch_to_previous_scene_one_of(
scene_manager, possible_scenes, COUNT_OF(possible_scenes))) {
view_dispatcher_stop(infrared->view_dispatcher);
}
} else {
furi_assert(0);
}
consumed = true;
}
}
return consumed;
}
void infrared_scene_edit_move_done_on_exit(void* context) {
Infrared* infrared = context;
UNUSED(infrared);
}

View file

@ -0,0 +1,215 @@
#include "infrared_move_view.h"
#include <gui/canvas.h>
#include <gui/elements.h>
#include <stdlib.h>
#include <string.h>
#define LIST_ITEMS 4U
#define LIST_LINE_H 13U
#define HEADER_H 12U
#define MOVE_X_OFFSET 5U
struct InfraredMoveView {
View* view;
InfraredMoveCallback move_cb;
void* cb_context;
};
typedef struct {
const char** btn_names;
uint32_t btn_number;
int32_t list_offset;
int32_t item_idx;
bool is_moving;
InfraredMoveGetItemCallback get_item_cb;
} InfraredMoveViewModel;
static void infrared_move_view_draw_callback(Canvas* canvas, void* _model) {
InfraredMoveViewModel* model = _model;
UNUSED(model);
canvas_set_color(canvas, ColorBlack);
canvas_set_font(canvas, FontPrimary);
elements_multiline_text_aligned(
canvas, canvas_width(canvas) / 2, 0, AlignCenter, AlignTop, "Select a button to move");
bool show_scrollbar = model->btn_number > LIST_ITEMS;
canvas_set_font(canvas, FontSecondary);
for(uint32_t i = 0; i < MIN(model->btn_number, LIST_ITEMS); i++) {
int32_t idx = CLAMP((uint32_t)(i + model->list_offset), model->btn_number, 0u);
uint8_t x_offset = (model->is_moving && model->item_idx == idx) ? MOVE_X_OFFSET : 0;
uint8_t y_offset = HEADER_H + i * LIST_LINE_H;
uint8_t box_end_x = canvas_width(canvas) - (show_scrollbar ? 6 : 1);
canvas_set_color(canvas, ColorBlack);
if(model->item_idx == idx) {
canvas_draw_box(canvas, x_offset, y_offset, box_end_x - x_offset, LIST_LINE_H);
canvas_set_color(canvas, ColorWhite);
canvas_draw_dot(canvas, x_offset, y_offset);
canvas_draw_dot(canvas, x_offset + 1, y_offset);
canvas_draw_dot(canvas, x_offset, y_offset + 1);
canvas_draw_dot(canvas, x_offset, y_offset + LIST_LINE_H - 1);
canvas_draw_dot(canvas, box_end_x - 1, y_offset);
canvas_draw_dot(canvas, box_end_x - 1, y_offset + LIST_LINE_H - 1);
}
canvas_draw_str_aligned(
canvas, x_offset + 3, y_offset + 3, AlignLeft, AlignTop, model->btn_names[idx]);
}
if(show_scrollbar) {
elements_scrollbar_pos(
canvas,
canvas_width(canvas),
HEADER_H,
canvas_height(canvas) - HEADER_H,
model->item_idx,
model->btn_number);
}
}
static void update_list_offset(InfraredMoveViewModel* model) {
int32_t bounds = model->btn_number > (LIST_ITEMS - 1) ? 2 : model->btn_number;
if((model->btn_number > (LIST_ITEMS - 1)) &&
(model->item_idx >= ((int32_t)model->btn_number - 1))) {
model->list_offset = model->item_idx - (LIST_ITEMS - 1);
} else if(model->list_offset < model->item_idx - bounds) {
model->list_offset = CLAMP(
model->item_idx - (int32_t)(LIST_ITEMS - 2), (int32_t)model->btn_number - bounds, 0);
} else if(model->list_offset > model->item_idx - bounds) {
model->list_offset = CLAMP(model->item_idx - 1, (int32_t)model->btn_number - bounds, 0);
}
}
static bool infrared_move_view_input_callback(InputEvent* event, void* context) {
InfraredMoveView* move_view = context;
bool consumed = false;
if(((event->type == InputTypeShort || event->type == InputTypeRepeat)) &&
((event->key == InputKeyUp) || (event->key == InputKeyDown))) {
bool is_moving = false;
uint32_t index_old = 0;
uint32_t index_new = 0;
with_view_model(
move_view->view,
InfraredMoveViewModel * model,
{
is_moving = model->is_moving;
index_old = model->item_idx;
if(event->key == InputKeyUp) {
if(model->item_idx <= 0) {
model->item_idx = model->btn_number;
}
model->item_idx--;
} else if(event->key == InputKeyDown) {
model->item_idx++;
if(model->item_idx >= (int32_t)(model->btn_number)) {
model->item_idx = 0;
}
}
index_new = model->item_idx;
update_list_offset(model);
},
!is_moving);
if((is_moving) && (move_view->move_cb)) {
move_view->move_cb(index_old, index_new, move_view->cb_context);
infrared_move_view_list_update(move_view);
}
consumed = true;
}
if((event->key == InputKeyOk) && (event->type == InputTypeShort)) {
with_view_model(
move_view->view,
InfraredMoveViewModel * model,
{ model->is_moving = !(model->is_moving); },
true);
consumed = true;
}
return consumed;
}
static void infrared_move_view_on_exit(void* context) {
furi_assert(context);
InfraredMoveView* move_view = context;
with_view_model(
move_view->view,
InfraredMoveViewModel * model,
{
if(model->btn_names) {
free(model->btn_names);
model->btn_names = NULL;
}
model->btn_number = 0;
model->get_item_cb = NULL;
},
false);
move_view->cb_context = NULL;
}
void infrared_move_view_set_callback(InfraredMoveView* move_view, InfraredMoveCallback callback) {
furi_assert(move_view);
move_view->move_cb = callback;
}
void infrared_move_view_list_init(
InfraredMoveView* move_view,
uint32_t item_count,
InfraredMoveGetItemCallback load_cb,
void* context) {
furi_assert(move_view);
move_view->cb_context = context;
with_view_model(
move_view->view,
InfraredMoveViewModel * model,
{
furi_assert(model->btn_names == NULL);
model->btn_names = malloc(sizeof(char*) * item_count);
model->btn_number = item_count;
model->get_item_cb = load_cb;
},
false);
}
void infrared_move_view_list_update(InfraredMoveView* move_view) {
furi_assert(move_view);
with_view_model(
move_view->view,
InfraredMoveViewModel * model,
{
for(uint32_t i = 0; i < model->btn_number; i++) {
if(!model->get_item_cb) break;
model->btn_names[i] = model->get_item_cb(i, move_view->cb_context);
}
},
true);
}
InfraredMoveView* infrared_move_view_alloc(void) {
InfraredMoveView* move_view = malloc(sizeof(InfraredMoveView));
move_view->view = view_alloc();
view_allocate_model(move_view->view, ViewModelTypeLocking, sizeof(InfraredMoveViewModel));
view_set_draw_callback(move_view->view, infrared_move_view_draw_callback);
view_set_input_callback(move_view->view, infrared_move_view_input_callback);
view_set_exit_callback(move_view->view, infrared_move_view_on_exit);
view_set_context(move_view->view, move_view);
return move_view;
}
void infrared_move_view_free(InfraredMoveView* move_view) {
view_free(move_view->view);
free(move_view);
}
View* infrared_move_view_get_view(InfraredMoveView* move_view) {
return move_view->view;
}

View file

@ -0,0 +1,25 @@
#pragma once
#include <gui/view.h>
typedef struct InfraredMoveView InfraredMoveView;
typedef void (*InfraredMoveCallback)(uint32_t index_old, uint32_t index_new, void* context);
typedef const char* (*InfraredMoveGetItemCallback)(uint32_t index, void* context);
InfraredMoveView* infrared_move_view_alloc(void);
void infrared_move_view_free(InfraredMoveView* debug_view);
View* infrared_move_view_get_view(InfraredMoveView* debug_view);
void infrared_move_view_set_callback(InfraredMoveView* move_view, InfraredMoveCallback callback);
void infrared_move_view_list_init(
InfraredMoveView* move_view,
uint32_t item_count,
InfraredMoveGetItemCallback load_cb,
void* context);
void infrared_move_view_list_update(InfraredMoveView* move_view);