GUI module example: 2 button dialog (#308)
* GUI: reusable module example
This commit is contained in:
		
							parent
							
								
									8f9b2513ff
								
							
						
					
					
						commit
						d0ed33e710
					
				| @ -305,6 +305,7 @@ APP_GUI	?= 0 | |||||||
| ifeq ($(APP_GUI), 1) | ifeq ($(APP_GUI), 1) | ||||||
| CFLAGS		+= -DAPP_GUI | CFLAGS		+= -DAPP_GUI | ||||||
| C_SOURCES	+= $(wildcard $(APP_DIR)/gui/*.c) | C_SOURCES	+= $(wildcard $(APP_DIR)/gui/*.c) | ||||||
|  | C_SOURCES	+= $(wildcard $(APP_DIR)/gui/modules/*.c) | ||||||
| C_SOURCES	+= $(wildcard $(APP_DIR)/backlight-control/*.c) | C_SOURCES	+= $(wildcard $(APP_DIR)/backlight-control/*.c) | ||||||
| endif | endif | ||||||
| 
 | 
 | ||||||
|  | |||||||
							
								
								
									
										106
									
								
								applications/gui/modules/dialog.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										106
									
								
								applications/gui/modules/dialog.c
									
									
									
									
									
										Normal 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; }); | ||||||
|  | } | ||||||
							
								
								
									
										69
									
								
								applications/gui/modules/dialog.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										69
									
								
								applications/gui/modules/dialog.h
									
									
									
									
									
										Normal 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); | ||||||
| @ -10,7 +10,7 @@ | |||||||
| #include <gui/widget.h> | #include <gui/widget.h> | ||||||
| #include <gui/view.h> | #include <gui/view.h> | ||||||
| #include <gui/view_dispatcher.h> | #include <gui/view_dispatcher.h> | ||||||
| 
 | #include <gui/modules/dialog.h> | ||||||
| #include <assets_icons.h> | #include <assets_icons.h> | ||||||
| #include <cli/cli.h> | #include <cli/cli.h> | ||||||
| #include <stm32wbxx.h> | #include <stm32wbxx.h> | ||||||
| @ -25,6 +25,8 @@ struct Power { | |||||||
|     Icon* battery_icon; |     Icon* battery_icon; | ||||||
|     Widget* battery_widget; |     Widget* battery_widget; | ||||||
| 
 | 
 | ||||||
|  |     Dialog* dialog; | ||||||
|  | 
 | ||||||
|     ValueMutex* menu_vm; |     ValueMutex* menu_vm; | ||||||
|     Cli* cli; |     Cli* cli; | ||||||
|     MenuItem* menu; |     MenuItem* menu; | ||||||
| @ -55,8 +57,24 @@ void power_menu_off_callback(void* context) { | |||||||
|     api_hal_power_off(); |     api_hal_power_off(); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void power_menu_reset_callback(void* context) { | void power_menu_reset_dialog_result(DialogResult result, void* context) { | ||||||
|  |     if(result == DialogResultLeft) { | ||||||
|  |         api_hal_boot_set_mode(ApiHalBootModeDFU); | ||||||
|         NVIC_SystemReset(); |         NVIC_SystemReset(); | ||||||
|  |     } else if(result == DialogResultRight) { | ||||||
|  |         api_hal_boot_set_mode(ApiHalBootModeNormal); | ||||||
|  |         NVIC_SystemReset(); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void power_menu_reset_callback(void* context) { | ||||||
|  |     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) { | 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_set_previous_callback(power->info_view, power_info_back_callback); | ||||||
|     view_dispatcher_add_view(power->view_dispatcher, PowerViewInfo, power->info_view); |     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_icon = assets_icons_get(I_USBConnected_15x8); | ||||||
|     power->usb_widget = widget_alloc(); |     power->usb_widget = widget_alloc(); | ||||||
|     widget_set_width(power->usb_widget, icon_get_width(power->usb_icon)); |     widget_set_width(power->usb_widget, icon_get_width(power->usb_icon)); | ||||||
|  | |||||||
| @ -6,7 +6,7 @@ | |||||||
| #include <gui/canvas.h> | #include <gui/canvas.h> | ||||||
| #include <gui/view.h> | #include <gui/view.h> | ||||||
| 
 | 
 | ||||||
| typedef enum { PowerViewInfo } PowerView; | typedef enum { PowerViewInfo, PowerViewDialog } PowerView; | ||||||
| 
 | 
 | ||||||
| typedef struct { | typedef struct { | ||||||
|     float current_charger; |     float current_charger; | ||||||
|  | |||||||
							
								
								
									
										97
									
								
								applications/tests/furi_new_test.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										97
									
								
								applications/tests/furi_new_test.c
									
									
									
									
									
										Normal 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"); | ||||||
|  |     */ | ||||||
|  | } | ||||||
| @ -16,6 +16,7 @@ void test_furi_value_manager(); | |||||||
| void test_furi_event(); | void test_furi_event(); | ||||||
| 
 | 
 | ||||||
| void test_furi_memmgr(); | void test_furi_memmgr(); | ||||||
|  | void test_furi_new(); | ||||||
| 
 | 
 | ||||||
| static int foo = 0; | static int foo = 0; | ||||||
| 
 | 
 | ||||||
| @ -62,6 +63,10 @@ MU_TEST(mu_test_furi_memmgr) { | |||||||
|     test_furi_memmgr(); |     test_furi_memmgr(); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | MU_TEST(mu_test_furi_new) { | ||||||
|  |     test_furi_new(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
| MU_TEST(mu_test_furi_value_expanders) { | MU_TEST(mu_test_furi_value_expanders) { | ||||||
|     test_furi_value_composer(); |     test_furi_value_composer(); | ||||||
|     test_furi_value_manager(); |     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_event); | ||||||
| 
 | 
 | ||||||
|     MU_RUN_TEST(mu_test_furi_memmgr); |     MU_RUN_TEST(mu_test_furi_memmgr); | ||||||
|  |     MU_RUN_TEST(mu_test_furi_new); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| int run_minunit() { | int run_minunit() { | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 あく
						あく