[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:
288
applications/gui/modules/button_menu.c
Normal file
288
applications/gui/modules/button_menu.c
Normal 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);
|
||||
}
|
73
applications/gui/modules/button_menu.h
Normal file
73
applications/gui/modules/button_menu.h
Normal 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
|
@@ -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;
|
||||
});
|
||||
}
|
||||
|
@@ -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
|
||||
|
Reference in New Issue
Block a user