[FL-1250, FL-1252, FL-1323, FL-1324] New IRDA Application (part 1) (#497)

* Add new IrdaApp (half ready), add ButtonMenu

* Fix NEC's extension

* clang-format

* Fix leak

* Add submenu optional header

* IRDA: add Edit button

* clang-format

* IrdaApp: Fix scene flow

* Add IRDA NEC extended protocol

* IRDA: Add address/command length

Co-authored-by: SG <who.just.the.doctor@gmail.com>
This commit is contained in:
Albert Kharisov
2021-06-02 18:16:05 +03:00
committed by GitHub
parent d040515f84
commit 31c31db479
62 changed files with 2568 additions and 375 deletions

View File

@@ -0,0 +1,288 @@
#include "button_menu.h"
#include "gui/canvas.h"
#include "gui/elements.h"
#include <m-array.h>
#include <furi.h>
#include <stdint.h>
#define ITEM_FIRST_OFFSET 17
#define ITEM_NEXT_OFFSET 4
#define ITEM_HEIGHT 14
#define ITEM_WIDTH 64
#define BUTTONS_PER_SCREEN 6
struct ButtonMenuItem {
const char* label;
int32_t index;
ButtonMenuItemCallback callback;
ButtonMenuItemType type;
void* callback_context;
};
ARRAY_DEF(ButtonMenuItemArray, ButtonMenuItem, M_POD_OPLIST);
struct ButtonMenu {
View* view;
};
typedef struct {
ButtonMenuItemArray_t items;
uint8_t position;
const char* header;
} ButtonMenuModel;
static void button_menu_draw_control_button(
Canvas* canvas,
uint8_t item_position,
const char* text,
bool selected) {
furi_assert(canvas);
furi_assert(text);
uint8_t item_x = 0;
uint8_t item_y = ITEM_FIRST_OFFSET + (item_position * (ITEM_HEIGHT + ITEM_NEXT_OFFSET));
canvas_set_color(canvas, ColorBlack);
if(selected) {
elements_slightly_rounded_box(canvas, item_x, item_y, ITEM_WIDTH, ITEM_HEIGHT);
canvas_set_color(canvas, ColorWhite);
}
canvas_draw_str_aligned(
canvas,
item_x + (ITEM_WIDTH / 2),
item_y + (ITEM_HEIGHT / 2),
AlignCenter,
AlignCenter,
text);
}
static void button_menu_draw_common_button(
Canvas* canvas,
uint8_t item_position,
const char* text,
bool selected) {
furi_assert(canvas);
furi_assert(text);
uint8_t item_x = 0;
uint8_t item_y = ITEM_FIRST_OFFSET + (item_position * (ITEM_HEIGHT + ITEM_NEXT_OFFSET));
canvas_set_color(canvas, ColorBlack);
if(selected) {
canvas_draw_rbox(canvas, item_x, item_y, ITEM_WIDTH, ITEM_HEIGHT, 5);
canvas_set_color(canvas, ColorWhite);
} else {
canvas_draw_rframe(canvas, item_x, item_y, ITEM_WIDTH, ITEM_HEIGHT, 5);
}
canvas_draw_str_aligned(
canvas,
item_x + (ITEM_WIDTH / 2),
item_y + (ITEM_HEIGHT / 2),
AlignCenter,
AlignCenter,
text);
}
static void button_menu_view_draw_callback(Canvas* canvas, void* _model) {
furi_assert(canvas);
furi_assert(_model);
ButtonMenuModel* model = (ButtonMenuModel*)_model;
canvas_clear(canvas);
canvas_set_font(canvas, FontSecondary);
uint8_t item_position = 0;
int8_t active_screen = model->position / BUTTONS_PER_SCREEN;
size_t items_size = ButtonMenuItemArray_size(model->items);
int8_t max_screen = ((int16_t)items_size - 1) / BUTTONS_PER_SCREEN;
ButtonMenuItemArray_it_t it;
if(active_screen > 0) {
canvas_draw_icon_name(canvas, 28, 1, I_IrdaArrowUp_4x8);
}
if(max_screen > active_screen) {
canvas_draw_icon_name(canvas, 28, 123, I_IrdaArrowDown_4x8);
}
canvas_draw_str_aligned(canvas, 32, 10, AlignCenter, AlignCenter, model->header);
for(ButtonMenuItemArray_it(it, model->items); !ButtonMenuItemArray_end_p(it);
ButtonMenuItemArray_next(it), ++item_position) {
if(active_screen == (item_position / BUTTONS_PER_SCREEN)) {
if(ButtonMenuItemArray_cref(it)->type == ButtonMenuItemTypeControl) {
button_menu_draw_control_button(
canvas,
item_position % BUTTONS_PER_SCREEN,
ButtonMenuItemArray_cref(it)->label,
(item_position == model->position));
} else if(ButtonMenuItemArray_cref(it)->type == ButtonMenuItemTypeCommon) {
button_menu_draw_common_button(
canvas,
item_position % BUTTONS_PER_SCREEN,
ButtonMenuItemArray_cref(it)->label,
(item_position == model->position));
}
}
}
}
static void button_menu_process_up(ButtonMenu* button_menu) {
furi_assert(button_menu);
with_view_model(
button_menu->view, (ButtonMenuModel * model) {
if(model->position > 0) {
model->position--;
} else {
model->position = ButtonMenuItemArray_size(model->items) - 1;
}
return true;
});
}
static void button_menu_process_down(ButtonMenu* button_menu) {
furi_assert(button_menu);
with_view_model(
button_menu->view, (ButtonMenuModel * model) {
if(model->position < (ButtonMenuItemArray_size(model->items) - 1)) {
model->position++;
} else {
model->position = 0;
}
return true;
});
}
static void button_menu_process_ok(ButtonMenu* button_menu) {
furi_assert(button_menu);
ButtonMenuItem* item = NULL;
with_view_model(
button_menu->view, (ButtonMenuModel * model) {
if(model->position < (ButtonMenuItemArray_size(model->items))) {
item = ButtonMenuItemArray_get(model->items, model->position);
}
return true;
});
if(item && item->callback) {
item->callback(item->callback_context, item->index);
}
}
static bool button_menu_view_input_callback(InputEvent* event, void* context) {
furi_assert(event);
ButtonMenu* button_menu = context;
bool consumed = false;
if(event->type == InputTypeShort) {
switch(event->key) {
case InputKeyUp:
consumed = true;
button_menu_process_up(button_menu);
break;
case InputKeyDown:
consumed = true;
button_menu_process_down(button_menu);
break;
case InputKeyOk:
consumed = true;
button_menu_process_ok(button_menu);
break;
default:
break;
}
}
return consumed;
}
View* button_menu_get_view(ButtonMenu* button_menu) {
furi_assert(button_menu);
return button_menu->view;
}
void button_menu_clean(ButtonMenu* button_menu) {
furi_assert(button_menu);
with_view_model(
button_menu->view, (ButtonMenuModel * model) {
ButtonMenuItemArray_clean(model->items);
model->position = 0;
return true;
});
}
void button_menu_set_header(ButtonMenu* button_menu, const char* header) {
furi_assert(button_menu);
with_view_model(
button_menu->view, (ButtonMenuModel * model) {
model->header = header;
return true;
});
}
ButtonMenuItem* button_menu_add_item(
ButtonMenu* button_menu,
const char* label,
int32_t index,
ButtonMenuItemCallback callback,
ButtonMenuItemType type,
void* callback_context) {
ButtonMenuItem* item = NULL;
furi_assert(label);
furi_assert(button_menu);
with_view_model(
button_menu->view, (ButtonMenuModel * model) {
item = ButtonMenuItemArray_push_new(model->items);
item->label = label;
item->index = index;
item->type = type;
item->callback = callback;
item->callback_context = callback_context;
return true;
});
return item;
}
ButtonMenu* button_menu_alloc(void) {
ButtonMenu* button_menu = furi_alloc(sizeof(ButtonMenu));
button_menu->view = view_alloc();
view_set_orientation(button_menu->view, ViewOrientationVertical);
view_set_context(button_menu->view, button_menu);
view_allocate_model(button_menu->view, ViewModelTypeLocking, sizeof(ButtonMenuModel));
view_set_draw_callback(button_menu->view, button_menu_view_draw_callback);
view_set_input_callback(button_menu->view, button_menu_view_input_callback);
with_view_model(
button_menu->view, (ButtonMenuModel * model) {
ButtonMenuItemArray_init(model->items);
model->position = 0;
model->header = NULL;
return true;
});
return button_menu;
}
void button_menu_free(ButtonMenu* button_menu) {
furi_assert(button_menu);
with_view_model(
button_menu->view, (ButtonMenuModel * model) {
ButtonMenuItemArray_clear(model->items);
return true;
});
view_free(button_menu->view);
free(button_menu);
}

View File

@@ -0,0 +1,73 @@
#pragma once
#include <stdint.h>
#include <gui/view.h>
#ifdef __cplusplus
extern "C" {
#endif
/* ButtonMenu anonymous structure */
typedef struct ButtonMenu ButtonMenu;
typedef struct ButtonMenuItem ButtonMenuItem;
/* Callback for any button menu actions */
typedef void (*ButtonMenuItemCallback)(void* context, int32_t index);
/* Type of button. Difference in drawing buttons. */
typedef enum {
ButtonMenuItemTypeCommon,
ButtonMenuItemTypeControl,
} ButtonMenuItemType;
/**
* @brief Get button menu view
* @param button_menu - ButtonMenu instance
* @return View instance that can be used for embedding
*/
View* button_menu_get_view(ButtonMenu* button_menu);
/**
* @brief Clean button menu
* @param button_menu - ButtonMenu instance
*/
void button_menu_clean(ButtonMenu* button_menu);
/**
* @brief Add item to button menu instance
* @param button_menu - ButtonMenu instance
* @param label - text inside new button
* @param index - value to distinct between buttons inside ButtonMenuItemCallback
* @param type - type of button to create. Differ by button drawing.
* Control buttons have no frames, and have more squared borders.
* @return pointer to just-created item
*/
ButtonMenuItem* button_menu_add_item(
ButtonMenu* button_menu,
const char* label,
int32_t index,
ButtonMenuItemCallback callback,
ButtonMenuItemType type,
void* callback_context);
/**
* @brief Allocate and initialize new instance of ButtonMenu model
* @return just-created ButtonMenu model
*/
ButtonMenu* button_menu_alloc(void);
/**
* @brief Free ButtonMenu element
* @param button_menu - ButtonMenu instance
*/
void button_menu_free(ButtonMenu* button_menu);
/**
* @brief Set ButtonMenu header on top of canvas
* @param button_menu - ButtonMenu instance
* @param header - header on the top of button menu
*/
void button_menu_set_header(ButtonMenu* button_menu, const char* header);
#ifdef __cplusplus
}
#endif

View File

@@ -1,7 +1,9 @@
#include "submenu.h"
#include "gui/canvas.h"
#include <m-array.h>
#include <furi.h>
#include <gui/elements.h>
#include <stdint.h>
struct SubmenuItem {
const char* label;
@@ -18,6 +20,7 @@ struct Submenu {
typedef struct {
SubmenuItemArray_t items;
const char* header;
uint8_t position;
uint8_t window_position;
} SubmenuModel;
@@ -33,34 +36,39 @@ static void submenu_view_draw_callback(Canvas* canvas, void* _model) {
const uint8_t item_width = 123;
canvas_clear(canvas);
canvas_set_font(canvas, FontSecondary);
canvas_set_font(canvas, FontPrimary);
uint8_t position = 0;
SubmenuItemArray_it_t it;
if(model->header) {
canvas_draw_str(canvas, 4, 11, model->header);
}
canvas_set_font(canvas, FontSecondary);
for(SubmenuItemArray_it(it, model->items); !SubmenuItemArray_end_p(it);
SubmenuItemArray_next(it)) {
uint8_t item_position = position - model->window_position;
uint8_t elements_on_screen = model->header ? 3 : 4;
uint8_t y_offset = model->header ? 16 : 0;
if(item_position < 4) {
if(item_position < elements_on_screen) {
if(position == model->position) {
canvas_set_color(canvas, ColorBlack);
canvas_draw_box(
canvas, 0, (item_position * item_height) + 1, item_width, item_height - 2);
elements_slightly_rounded_box(
canvas,
0,
y_offset + (item_position * item_height) + 1,
item_width,
item_height - 2);
canvas_set_color(canvas, ColorWhite);
canvas_draw_dot(canvas, 0, (item_position * item_height) + 1);
canvas_draw_dot(canvas, 0, (item_position * item_height) + item_height - 2);
canvas_draw_dot(canvas, item_width - 1, (item_position * item_height) + 1);
canvas_draw_dot(
canvas, item_width - 1, (item_position * item_height) + item_height - 2);
} else {
canvas_set_color(canvas, ColorBlack);
}
canvas_draw_str(
canvas,
6,
(item_position * item_height) + item_height - 4,
y_offset + (item_position * item_height) + item_height - 4,
SubmenuItemArray_cref(it)->label);
}
@@ -110,6 +118,7 @@ Submenu* submenu_alloc() {
SubmenuItemArray_init(model->items);
model->position = 0;
model->window_position = 0;
model->header = NULL;
return true;
});
@@ -164,6 +173,7 @@ void submenu_clean(Submenu* submenu) {
SubmenuItemArray_clean(model->items);
model->position = 0;
model->window_position = 0;
model->header = NULL;
return true;
});
}
@@ -207,15 +217,17 @@ void submenu_set_selected_item(Submenu* submenu, uint32_t index) {
void submenu_process_up(Submenu* submenu) {
with_view_model(
submenu->view, (SubmenuModel * model) {
uint8_t elements_on_screen = model->header ? 3 : 4;
if(model->position > 0) {
model->position--;
if((model->position - model->window_position) < 1 && model->window_position > 0) {
if(((model->position - model->window_position) < 1) &&
model->window_position > 0) {
model->window_position--;
}
} else {
model->position = SubmenuItemArray_size(model->items) - 1;
if(model->position > 3) {
model->window_position = model->position - 3;
if(model->position > (elements_on_screen - 1)) {
model->window_position = model->position - (elements_on_screen - 1);
}
}
return true;
@@ -225,10 +237,12 @@ void submenu_process_up(Submenu* submenu) {
void submenu_process_down(Submenu* submenu) {
with_view_model(
submenu->view, (SubmenuModel * model) {
uint8_t elements_on_screen = model->header ? 3 : 4;
if(model->position < (SubmenuItemArray_size(model->items) - 1)) {
model->position++;
if((model->position - model->window_position) > 2 &&
model->window_position < (SubmenuItemArray_size(model->items) - 4)) {
if((model->position - model->window_position) > (elements_on_screen - 2) &&
model->window_position <
(SubmenuItemArray_size(model->items) - elements_on_screen)) {
model->window_position++;
}
} else {
@@ -254,3 +268,13 @@ void submenu_process_ok(Submenu* submenu) {
item->callback(item->callback_context, item->index);
}
}
void submenu_set_header(Submenu* submenu, const char* header) {
furi_assert(submenu);
with_view_model(
submenu->view, (SubmenuModel * model) {
model->header = header;
return true;
});
}

View File

@@ -16,12 +16,6 @@ typedef void (*SubmenuItemCallback)(void* context, uint32_t index);
*/
Submenu* submenu_alloc();
/**
* @brief Allocate and initialize submenu for vertical display
* This submenu is used to select one option
*/
Submenu* submenu_vertical_alloc();
/**
* @brief Deinitialize and free submenu
* @param submenu - Submenu instance
@@ -59,11 +53,18 @@ void submenu_clean(Submenu* submenu);
/**
* @brief Set submenu item selector
* @param submenu
* @param index
* @param submenu
* @param index
*/
void submenu_set_selected_item(Submenu* submenu, uint32_t index);
/**
* @brief Set optional header for submenu
* @param submenu - submenu entity
* @param header - header to set
*/
void submenu_set_header(Submenu* submenu, const char* header);
#ifdef __cplusplus
}
#endif