From d0ed33e7100aac0b0335e412d78fe1e8261f51d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=82=E3=81=8F?= Date: Wed, 20 Jan 2021 19:51:01 +0300 Subject: [PATCH] GUI module example: 2 button dialog (#308) * GUI: reusable module example --- applications/applications.mk | 1 + applications/gui/modules/dialog.c | 106 +++++++++++++++++++++++++++++ applications/gui/modules/dialog.h | 69 +++++++++++++++++++ applications/power/power.c | 27 +++++++- applications/power/power_views.h | 2 +- applications/tests/furi_new_test.c | 97 ++++++++++++++++++++++++++ applications/tests/minunit_test.c | 6 ++ 7 files changed, 305 insertions(+), 3 deletions(-) create mode 100644 applications/gui/modules/dialog.c create mode 100644 applications/gui/modules/dialog.h create mode 100644 applications/tests/furi_new_test.c diff --git a/applications/applications.mk b/applications/applications.mk index d79926c0..a7dd5645 100644 --- a/applications/applications.mk +++ b/applications/applications.mk @@ -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 diff --git a/applications/gui/modules/dialog.c b/applications/gui/modules/dialog.c new file mode 100644 index 00000000..ae0f85a0 --- /dev/null +++ b/applications/gui/modules/dialog.c @@ -0,0 +1,106 @@ +#include "dialog.h" +#include + +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; }); +} diff --git a/applications/gui/modules/dialog.h b/applications/gui/modules/dialog.h new file mode 100644 index 00000000..36cd3164 --- /dev/null +++ b/applications/gui/modules/dialog.h @@ -0,0 +1,69 @@ +#pragma once + +#include + +/* 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); diff --git a/applications/power/power.c b/applications/power/power.c index 9252d85d..a451cd1f 100644 --- a/applications/power/power.c +++ b/applications/power/power.c @@ -10,7 +10,7 @@ #include #include #include - +#include #include #include #include @@ -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)); diff --git a/applications/power/power_views.h b/applications/power/power_views.h index 3858b70f..c3325219 100644 --- a/applications/power/power_views.h +++ b/applications/power/power_views.h @@ -6,7 +6,7 @@ #include #include -typedef enum { PowerViewInfo } PowerView; +typedef enum { PowerViewInfo, PowerViewDialog } PowerView; typedef struct { float current_charger; diff --git a/applications/tests/furi_new_test.c b/applications/tests/furi_new_test.c new file mode 100644 index 00000000..c507353b --- /dev/null +++ b/applications/tests/furi_new_test.c @@ -0,0 +1,97 @@ +#include +#include +#include +#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"); + */ +} \ No newline at end of file diff --git a/applications/tests/minunit_test.c b/applications/tests/minunit_test.c index 0fa28313..87c312d7 100644 --- a/applications/tests/minunit_test.c +++ b/applications/tests/minunit_test.c @@ -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() {