Просмотр исходного кода

GUI module example: 2 button dialog (#308)

* GUI: reusable module example
あく 5 лет назад
Родитель
Сommit
d0ed33e710

+ 1 - 0
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
 

+ 106 - 0
applications/gui/modules/dialog.c

@@ -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; });
+}

+ 69 - 0
applications/gui/modules/dialog.h

@@ -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);

+ 25 - 2
applications/power/power.c

@@ -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));

+ 1 - 1
applications/power/power_views.h

@@ -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;

+ 97 - 0
applications/tests/furi_new_test.c

@@ -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");
+    */
+}

+ 6 - 0
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() {