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) | ||||
| 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
									
								
								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/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_callback(void* context) { | ||||
| 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) { | ||||
|     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)); | ||||
|  | ||||
| @ -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
									
								
								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_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() { | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 あく
						あく