GUI module example: 2 button dialog (#308)

* GUI: reusable module example
This commit is contained in:
あく 2021-01-20 19:51:01 +03:00 committed by GitHub
parent 8f9b2513ff
commit d0ed33e710
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 305 additions and 3 deletions

View File

@ -305,6 +305,7 @@ APP_GUI ?= 0
ifeq ($(APP_GUI), 1)
CFLAGS += -DAPP_GUI
C_SOURCES += $(wildcard $(APP_DIR)/gui/*.c)
C_SOURCES += $(wildcard $(APP_DIR)/gui/modules/*.c)
C_SOURCES += $(wildcard $(APP_DIR)/backlight-control/*.c)
endif

View File

@ -0,0 +1,106 @@
#include "dialog.h"
#include <furi.h>
struct Dialog {
View* view;
void* context;
DialogResultCallback callback;
};
typedef struct {
const char* header_text;
const char* text;
const char* left_text;
const char* right_text;
} DialogModel;
static void dialog_view_draw_callback(Canvas* canvas, void* _model) {
DialogModel* model = _model;
// Prepare canvas
canvas_clear(canvas);
canvas_set_color(canvas, ColorBlack);
// Draw header
canvas_set_font(canvas, FontPrimary);
canvas_draw_str(canvas, 2, 10, model->header_text);
// Draw text
canvas_set_font(canvas, FontSecondary);
canvas_draw_str(canvas, 5, 22, model->text);
// Draw buttons
uint8_t bottom_base_line = canvas_height(canvas) - 2;
canvas_set_font(canvas, FontPrimary);
canvas_draw_str(canvas, 5, bottom_base_line, model->left_text);
canvas_draw_str(canvas, 69, bottom_base_line, model->right_text);
}
static bool dialog_view_input_callback(InputEvent* event, void* context) {
Dialog* dialog = context;
// Process key presses only
if(event->state && dialog->callback) {
if(event->input == InputLeft) {
dialog->callback(DialogResultLeft, dialog->context);
} else if(event->input == InputRight) {
dialog->callback(DialogResultRight, dialog->context);
}
}
// All input events consumed
return true;
}
Dialog* dialog_alloc() {
Dialog* dialog = furi_alloc(sizeof(Dialog));
dialog->view = view_alloc();
view_set_context(dialog->view, dialog);
view_allocate_model(dialog->view, ViewModelTypeLockFree, sizeof(DialogModel));
view_set_draw_callback(dialog->view, dialog_view_draw_callback);
view_set_input_callback(dialog->view, dialog_view_input_callback);
return dialog;
}
void dialog_free(Dialog* dialog) {
furi_assert(dialog);
view_free(dialog->view);
free(dialog);
}
View* dialog_get_view(Dialog* dialog) {
furi_assert(dialog);
return dialog->view;
}
void dialog_set_result_callback(Dialog* dialog, DialogResultCallback callback) {
furi_assert(dialog);
dialog->callback = callback;
}
void dialog_set_context(Dialog* dialog, void* context) {
furi_assert(dialog);
dialog->context = context;
}
void dialog_set_header_text(Dialog* dialog, const char* text) {
furi_assert(dialog);
furi_assert(text);
with_view_model(
dialog->view, (DialogModel * model) { model->header_text = text; });
}
void dialog_set_text(Dialog* dialog, const char* text) {
furi_assert(dialog);
furi_assert(text);
with_view_model(
dialog->view, (DialogModel * model) { model->text = text; });
}
void dialog_set_left_button_text(Dialog* dialog, const char* text) {
furi_assert(dialog);
furi_assert(text);
with_view_model(
dialog->view, (DialogModel * model) { model->left_text = text; });
}
void dialog_set_right_button_text(Dialog* dialog, const char* text) {
furi_assert(dialog);
furi_assert(text);
with_view_model(
dialog->view, (DialogModel * model) { model->right_text = text; });
}

View File

@ -0,0 +1,69 @@
#pragma once
#include <gui/view.h>
/* Dialog anonymous structure */
typedef struct Dialog Dialog;
/* Dialog result */
typedef enum {
DialogResultLeft,
DialogResultRight,
} DialogResult;
/* Dialog result callback type
* @warning comes from GUI thread
*/
typedef void (*DialogResultCallback)(DialogResult result, void* context);
/* Allocate and initialize dialog
* This dialog used to ask simple questions like Yes/
*/
Dialog* dialog_alloc();
/* Deinitialize and free dialog
* @param dialog - Dialog instance
*/
void dialog_free(Dialog* dialog);
/* Get dialog view
* @param dialog - Dialog instance
* @return View instance that can be used for embedding
*/
View* dialog_get_view(Dialog* dialog);
/* Set dialog header text
* @param dialog - Dialog instance
* @param text - text to be shown
*/
void dialog_set_result_callback(Dialog* dialog, DialogResultCallback callback);
/* Set dialog header text
* @param dialog - Dialog instance
* @param context - context pointer, will be passed to result callback
*/
void dialog_set_context(Dialog* dialog, void* context);
/* Set dialog header text
* @param dialog - Dialog instance
* @param text - text to be shown
*/
void dialog_set_header_text(Dialog* dialog, const char* text);
/* Set dialog text
* @param dialog - Dialog instance
* @param text - text to be shown
*/
void dialog_set_text(Dialog* dialog, const char* text);
/* Set left button text
* @param dialog - Dialog instance
* @param text - text to be shown
*/
void dialog_set_left_button_text(Dialog* dialog, const char* text);
/* Set right button text
* @param dialog - Dialog instance
* @param text - text to be shown
*/
void dialog_set_right_button_text(Dialog* dialog, const char* text);

View File

@ -10,7 +10,7 @@
#include <gui/widget.h>
#include <gui/view.h>
#include <gui/view_dispatcher.h>
#include <gui/modules/dialog.h>
#include <assets_icons.h>
#include <cli/cli.h>
#include <stm32wbxx.h>
@ -25,6 +25,8 @@ struct Power {
Icon* battery_icon;
Widget* battery_widget;
Dialog* dialog;
ValueMutex* menu_vm;
Cli* cli;
MenuItem* menu;
@ -55,8 +57,24 @@ void power_menu_off_callback(void* context) {
api_hal_power_off();
}
void power_menu_reset_dialog_result(DialogResult result, void* context) {
if(result == DialogResultLeft) {
api_hal_boot_set_mode(ApiHalBootModeDFU);
NVIC_SystemReset();
} else if(result == DialogResultRight) {
api_hal_boot_set_mode(ApiHalBootModeNormal);
NVIC_SystemReset();
}
}
void power_menu_reset_callback(void* context) {
NVIC_SystemReset();
Power* power = context;
dialog_set_result_callback(power->dialog, power_menu_reset_dialog_result);
dialog_set_header_text(power->dialog, "Reset type");
dialog_set_text(power->dialog, "Reboot where?");
dialog_set_left_button_text(power->dialog, "DFU");
dialog_set_right_button_text(power->dialog, "OS");
view_dispatcher_switch_to_view(power->view_dispatcher, PowerViewDialog);
}
void power_menu_enable_otg_callback(void* context) {
@ -100,6 +118,11 @@ Power* power_alloc() {
view_set_previous_callback(power->info_view, power_info_back_callback);
view_dispatcher_add_view(power->view_dispatcher, PowerViewInfo, power->info_view);
power->dialog = dialog_alloc();
dialog_set_context(power->dialog, power);
view_dispatcher_add_view(
power->view_dispatcher, PowerViewDialog, dialog_get_view(power->dialog));
power->usb_icon = assets_icons_get(I_USBConnected_15x8);
power->usb_widget = widget_alloc();
widget_set_width(power->usb_widget, icon_get_width(power->usb_icon));

View File

@ -6,7 +6,7 @@
#include <gui/canvas.h>
#include <gui/view.h>
typedef enum { PowerViewInfo } PowerView;
typedef enum { PowerViewInfo, PowerViewDialog } PowerView;
typedef struct {
float current_charger;

View File

@ -0,0 +1,97 @@
#include <stdio.h>
#include <string.h>
#include <furi.h>
#include "minunit.h"
#include "furi-new.h"
const int int_value_init = 0x1234;
const int int_value_changed = 0x5678;
osMessageQueueId_t test_messages;
typedef struct {
char text[256];
bool result;
} test_message;
#define SEND_MESSAGE(value, data) \
{ \
message.result = value; \
snprintf(message.text, 256, "Error at line %d, %s", __LINE__, data); \
osMessageQueuePut(test_messages, &message, 0U, 0U); \
}
void _furi_new_wait() {
osThreadFlagsWait(0x0001U, osFlagsWaitAny, osWaitForever);
}
void _furi_new_continue(FuriAppId thread_id) {
osThreadFlagsSet(thread_id, 0x0001U);
}
void _furi_new_main_app(void* p) {
test_message message;
_furi_new_wait();
int another_test_value = int_value_init;
furi_record_create("test/another_app_record", &another_test_value);
SEND_MESSAGE(false, "dummy text");
new_flapp_app_exit();
}
void test_furi_new() {
test_message message;
test_messages = osMessageQueueNew(1, sizeof(test_message), NULL);
// init core
new_furi_init();
// launch test thread
FuriAppId main_app = new_flapp_app_start(_furi_new_main_app, "main_app", 512, NULL);
_furi_new_continue(main_app);
while(1) {
if(osMessageQueueGet(test_messages, &message, NULL, osWaitForever) == osOK) {
if(message.result == true) {
break;
} else {
mu_assert(false, message.text);
}
}
};
/*
// test that "create" wont affect pointer value
furi_record_create("test/record", &test_value);
mu_assert_int_eq(test_value, int_value_init);
// test that we get correct pointer
int* test_value_pointer = furi_record_open("test/record");
mu_assert_pointers_not_eq(test_value_pointer, NULL);
mu_assert_pointers_eq(test_value_pointer, &test_value);
*test_value_pointer = int_value_changed;
mu_assert_int_eq(test_value, int_value_changed);
// start another app
new_record_available = osSemaphoreNew(1, 1, NULL);
osSemaphoreAcquire(new_record_available, osWaitForever);
osThreadAttr_t another_app_attr = {.name = "another_app", .stack_size = 512};
osThreadId_t player = osThreadNew(another_app, NULL, &another_app_attr);
// wait until app create record
osSemaphoreAcquire(new_record_available, osWaitForever);
// open record, test that record pointed to int_value_init
test_value_pointer = furi_record_open("test/another_app_record");
mu_assert_pointers_not_eq(test_value_pointer, NULL);
mu_assert_int_eq(*test_value_pointer, int_value_init);
// test that we can close, (unsubscribe) from record
bool close_result = new_furi_close("test/another_app_record");
mu_assert(close_result, "cannot close record");
*/
}

View File

@ -16,6 +16,7 @@ void test_furi_value_manager();
void test_furi_event();
void test_furi_memmgr();
void test_furi_new();
static int foo = 0;
@ -62,6 +63,10 @@ MU_TEST(mu_test_furi_memmgr) {
test_furi_memmgr();
}
MU_TEST(mu_test_furi_new) {
test_furi_new();
}
MU_TEST(mu_test_furi_value_expanders) {
test_furi_value_composer();
test_furi_value_manager();
@ -87,6 +92,7 @@ MU_TEST_SUITE(test_suite) {
MU_RUN_TEST(mu_test_furi_event);
MU_RUN_TEST(mu_test_furi_memmgr);
MU_RUN_TEST(mu_test_furi_new);
}
int run_minunit() {