 5c81bb8abc
			
		
	
	
		5c81bb8abc
		
			
		
	
	
	
	
		
			
			* add u8g2 and ui libs * add display driver and usage example * not init display in test mode * change todo text * fix removed code * Target f2 (#107) * add ioc for flipperzero f2 * add generated f1 files to f2 * regenerate cubemx * invert initial state of led * blink backligh * shutdown backlight on idle
		
			
				
	
	
		
			590 lines
		
	
	
		
			18 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			590 lines
		
	
	
		
			18 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| #include <stdio.h>
 | |
| 
 | |
| extern "C" {
 | |
|     #include "main.h"
 | |
|     #include "cmsis_os.h"
 | |
|     #include "u8g2_support.h"
 | |
|     #include "u8g2/u8g2.h"
 | |
| }
 | |
| 
 | |
| #include "ui.h"
 | |
| #include "events.h"
 | |
| 
 | |
| // function draw basic layout -- single bmp
 | |
| void draw_bitmap(const char* bitmap, u8g2_t* u8g2, ScreenArea area) {
 | |
|     if(bitmap == NULL) {
 | |
|         printf("[basic layout] no content\n");
 | |
|         u8g2_SetFont(u8g2, u8g2_font_6x10_mf);
 | |
|         u8g2_SetDrawColor(u8g2, 1);
 | |
|         u8g2_SetFontMode(u8g2, 1);
 | |
|         u8g2_DrawStr(u8g2, 2, 12, "no content");
 | |
|     } else {
 | |
|         u8g2_SetDrawColor(u8g2, 1);
 | |
|         u8g2_DrawXBM(u8g2, 0, 0, area.x + area.width, area.y + area.height, (unsigned char*)bitmap);
 | |
|     }
 | |
| }
 | |
| 
 | |
| void draw_text(const char* text, u8g2_t* u8g2, ScreenArea area) {
 | |
|     // TODO proper cleanup statusbar
 | |
|     u8g2_SetDrawColor(u8g2, 0);
 | |
|     u8g2_DrawBox(u8g2, 0, 0, area.x + area.width, area.y + area.height);
 | |
| 
 | |
|     Block text_block = Block {
 | |
|         width: area.width,
 | |
|         height: area.height,
 | |
|         margin_left: 0,
 | |
|         margin_top: 0,
 | |
|         padding_left: 3,
 | |
|         padding_top: 7,
 | |
|         background: 0,
 | |
|         color: 1,
 | |
|         font: (uint8_t*)u8g2_font_6x10_mf,
 | |
|     };
 | |
| 
 | |
|     draw_block(u8g2, text, text_block, area.x, area.y);
 | |
| }
 | |
| 
 | |
| // draw layout and switch between ui item by button and timer
 | |
| void LayoutComponent::handle(Event* event, Store* store, u8g2_t* u8g2, ScreenArea area) {
 | |
|     switch(event->type) {
 | |
|         // get button event
 | |
|         case EventTypeButton:
 | |
|             if(event->value.button.state) {
 | |
|                 for(size_t i = 0; i < this->actions_size; i++) {
 | |
|                     FlipperComponent* next = NULL;
 | |
| 
 | |
|                     switch(this->actions[i].action) {
 | |
|                         case LayoutActionUp:
 | |
|                             if(event->value.button.id == ButtonsUp) {
 | |
|                                 next = this->actions[i].item;
 | |
|                             }
 | |
|                         break;
 | |
|                         case LayoutActionDown:
 | |
|                             if(event->value.button.id == ButtonsDown) {
 | |
|                                 next = this->actions[i].item;
 | |
|                             }
 | |
|                         break;
 | |
|                         case LayoutActionLeft:
 | |
|                             if(event->value.button.id == ButtonsLeft) {
 | |
|                                 next = this->actions[i].item;
 | |
|                             }
 | |
|                         break;
 | |
|                         case LayoutActionRight:
 | |
|                             if(event->value.button.id == ButtonsRight) {
 | |
|                                 next = this->actions[i].item;
 | |
|                             }
 | |
|                         break;
 | |
|                         case LayoutActionOk:
 | |
|                             if(event->value.button.id == ButtonsOk) {
 | |
|                                 next = this->actions[i].item;
 | |
|                             }
 | |
|                         break;
 | |
|                         case LayoutActionBack:
 | |
|                             if(event->value.button.id == ButtonsBack) {
 | |
|                                 next = this->actions[i].item;
 | |
|                             }
 | |
|                         break;
 | |
| 
 | |
|                         // stub action
 | |
|                         case LayoutActionUsbDisconnect:
 | |
|                             if(event->value.button.id == ButtonsLeft) {
 | |
|                                 next = this->actions[i].item;
 | |
|                             }
 | |
|                         break;
 | |
| 
 | |
|                         case LayoutActionUsbConnect:
 | |
|                             if(event->value.button.id == ButtonsRight) {
 | |
|                                 next = this->actions[i].item;
 | |
|                             }
 | |
|                         break;
 | |
| 
 | |
|                         default: break;
 | |
|                     }
 | |
| 
 | |
|                     if(next) {
 | |
|                         printf("[layout view] go to next item\n");
 | |
|                         Event send_event;
 | |
|                         send_event.type = EventTypeUiNext;
 | |
|                         next->handle(
 | |
|                             &send_event,
 | |
|                             store,
 | |
|                             u8g2,
 | |
|                             area
 | |
|                         );
 | |
|                     }
 | |
|                 }
 | |
|             }
 | |
|         break;
 | |
| 
 | |
|         case EventTypeUsb: {
 | |
|             printf("get usb event\n");
 | |
|             
 | |
|             FlipperComponent* next = NULL;
 | |
| 
 | |
|             if(event->value.usb == UsbEventConnect) {
 | |
|                 for(size_t i = 0; i < this->actions_size; i++) {
 | |
|                     if(this->actions[i].action == LayoutActionUsbConnect) {
 | |
|                         next = this->actions[i].item;
 | |
|                     }
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             if(event->value.usb == UsbEventDisconnect) {
 | |
|                 for(size_t i = 0; i < this->actions_size; i++) {
 | |
|                     if(this->actions[i].action == LayoutActionUsbDisconnect) {
 | |
|                         next = this->actions[i].item;
 | |
|                     }
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             if(next) {
 | |
|                 printf("[layout view] go to next item\n");
 | |
|                 Event send_event;
 | |
|                 send_event.type = EventTypeUiNext;
 | |
|                 next->handle(
 | |
|                     &send_event,
 | |
|                     store,
 | |
|                     u8g2,
 | |
|                     area
 | |
|                 );
 | |
|             }
 | |
|         } break;
 | |
| 
 | |
|         // start component from prev
 | |
|         case EventTypeUiNext:
 | |
|             printf("[layout view] start component %lX\n", (uint32_t)this);
 | |
| 
 | |
|             if(this->timeout > 0) {
 | |
|                 // TODO start timer
 | |
|             }
 | |
| 
 | |
|             // set current item to self
 | |
|             store->current_component = this;
 | |
| 
 | |
|             this->wait_time = 0;
 | |
| 
 | |
|             // render layout
 | |
|             this->dirty = true;
 | |
|         break;
 | |
| 
 | |
|         case EventTypeTick:
 | |
|             this->wait_time += event->value.tick_delta;
 | |
| 
 | |
|             if(this->wait_time > this->timeout) {
 | |
|                 for(size_t i = 0; i < this->actions_size; i++) {
 | |
|                     if(this->actions[i].action == LayoutActionTimeout ||
 | |
|                         this->actions[i].action == LayoutActionEndOfCycle
 | |
|                     ) {
 | |
|                         if(this->actions[i].item != NULL) {
 | |
|                             printf("[layout view] go to next item\n");
 | |
|                             Event send_event;
 | |
|                             send_event.type = EventTypeUiNext;
 | |
|                             this->actions[i].item->handle(
 | |
|                                 &send_event,
 | |
|                                 store,
 | |
|                                 u8g2,
 | |
|                                 area
 | |
|                             );
 | |
| 
 | |
|                             return;
 | |
|                         }
 | |
|                     }
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             if(this->dirty) {
 | |
|                 this->draw_fn(this->data, u8g2, area);
 | |
| 
 | |
|                 store->dirty_screen = true;
 | |
| 
 | |
|                 this->dirty = false;
 | |
|             }
 | |
|         break;
 | |
| 
 | |
|         default: break;
 | |
|     }
 | |
| }
 | |
| 
 | |
| void BlinkerComponent::handle(Event* event, Store* store, u8g2_t* u8g2, ScreenArea area) {
 | |
|     switch(event->type) {
 | |
|         // get button event
 | |
|         case EventTypeButton:
 | |
|             if(event->value.button.state && event->value.button.id == ButtonsBack) {
 | |
|                 if(this->prev && this->prev != this) {
 | |
|                     printf("[blinker view] go back\n");
 | |
| 
 | |
|                     Event send_event;
 | |
|                     send_event.type = EventTypeUiNext;
 | |
|                     this->prev->handle(
 | |
|                         &send_event,
 | |
|                         store,
 | |
|                         u8g2,
 | |
|                         area
 | |
|                     );
 | |
| 
 | |
|                     this->prev = NULL;
 | |
| 
 | |
|                     store->led = ColorBlack;
 | |
|                     this->wait_time = 0;
 | |
|                     this->is_on = true;
 | |
|                     this->active = false;
 | |
|                 } else {
 | |
|                     printf("[blinker view] no back/loop\n");
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             if(event->value.button.state && event->value.button.id != ButtonsBack) {
 | |
|                 this->active = false;
 | |
|             }
 | |
| 
 | |
|             if(!event->value.button.state && event->value.button.id != ButtonsBack) {
 | |
|                 this->active = true;
 | |
|             }
 | |
|         break;
 | |
| 
 | |
|         // start component from prev
 | |
|         case EventTypeUiNext:
 | |
|             printf("[blinker view] start component %lX\n", (uint32_t)this);
 | |
| 
 | |
|             if(this->prev == NULL) {
 | |
|                 this->prev = store->current_component;
 | |
|             }
 | |
| 
 | |
|             // set current item to self
 | |
|             store->current_component = this;
 | |
| 
 | |
|             this->dirty = true;
 | |
|             this->wait_time = 0;
 | |
|             this->is_on = true;
 | |
|             this->active = false;
 | |
|         break;
 | |
| 
 | |
|         case EventTypeTick:
 | |
|             if(this->active) {
 | |
|                 this->wait_time += event->value.tick_delta;
 | |
| 
 | |
|                 if(this->is_on) {
 | |
|                     if(this->wait_time > this->config.on_time) {
 | |
|                         this->wait_time = 0;
 | |
|                         this->is_on = false;
 | |
|                     }
 | |
|                 } else {
 | |
|                     if(this->wait_time > this->config.off_time) {
 | |
|                         this->wait_time = 0;
 | |
|                         this->is_on = true;
 | |
|                     }
 | |
|                 }
 | |
| 
 | |
|                 store->led = this->is_on ? this->config.on_color : this->config.off_color;
 | |
|             } else {
 | |
|                 store->led = ColorBlack;
 | |
|                 this->wait_time = 0;
 | |
|                 this->is_on = true;
 | |
|             }
 | |
| 
 | |
|             if(this->dirty) {
 | |
|                 this->draw_fn(this->data, u8g2, area);
 | |
| 
 | |
|                 store->dirty_screen = true;
 | |
| 
 | |
|                 this->dirty = false;
 | |
|             }
 | |
|         break;
 | |
| 
 | |
|         default: break;
 | |
|     }
 | |
| }
 | |
| void BlinkerComponentOnBtn::handle(Event* event, Store* store, u8g2_t* u8g2, ScreenArea area) {
 | |
|     switch(event->type) {
 | |
|         // get button event
 | |
|         case EventTypeButton:
 | |
|             if(event->value.button.state && event->value.button.id == ButtonsBack) {
 | |
|                 if(this->prev && this->prev != this) {
 | |
|                     printf("[blinker view] go back\n");
 | |
| 
 | |
|                     Event send_event;
 | |
|                     send_event.type = EventTypeUiNext;
 | |
|                     this->prev->handle(
 | |
|                         &send_event,
 | |
|                         store,
 | |
|                         u8g2,
 | |
|                         area
 | |
|                     );
 | |
| 
 | |
|                     this->prev = NULL;
 | |
| 
 | |
|                     store->led = ColorBlack;
 | |
|                     this->wait_time = 0;
 | |
|                     this->is_on = true;
 | |
|                     this->active = false;
 | |
|                 } else {
 | |
|                     printf("[blinker view] no back/loop\n");
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             if(event->value.button.state && event->value.button.id != ButtonsBack) {
 | |
|                 this->active = true;
 | |
|             }
 | |
| 
 | |
|             if(!event->value.button.state && event->value.button.id != ButtonsBack) {
 | |
|                 this->active = false;
 | |
|             }
 | |
|         break;
 | |
| 
 | |
|         // start component from prev
 | |
|         case EventTypeUiNext:
 | |
|             printf("[blinker view] start component %lX\n", (uint32_t)this);
 | |
| 
 | |
|             if(this->prev == NULL) {
 | |
|                 this->prev = store->current_component;
 | |
|             }
 | |
| 
 | |
|             // set current item to self
 | |
|             store->current_component = this;
 | |
| 
 | |
|             this->dirty = true;
 | |
|             this->wait_time = 0;
 | |
|             this->is_on = true;
 | |
|             this->active = false;
 | |
|         break;
 | |
| 
 | |
|         case EventTypeTick:
 | |
|             if(this->active) {
 | |
|                 this->wait_time += event->value.tick_delta;
 | |
| 
 | |
|                 if(this->is_on) {
 | |
|                     if(this->wait_time > this->config.on_time) {
 | |
|                         this->wait_time = 0;
 | |
|                         this->is_on = false;
 | |
|                     }
 | |
|                 } else {
 | |
|                     if(this->wait_time > this->config.off_time) {
 | |
|                         this->wait_time = 0;
 | |
|                         this->is_on = true;
 | |
|                     }
 | |
|                 }
 | |
| 
 | |
|                 store->led = this->is_on ? this->config.on_color : this->config.off_color;
 | |
|             } else {
 | |
|                 store->led = ColorBlack;
 | |
|                 this->wait_time = 0;
 | |
|                 this->is_on = true;
 | |
|             }
 | |
| 
 | |
|             if(this->dirty) {
 | |
|                 this->draw_fn(this->data, u8g2, area);
 | |
| 
 | |
|                 store->dirty_screen = true;
 | |
| 
 | |
|                 this->dirty = false;
 | |
|             }
 | |
|         break;
 | |
| 
 | |
|         default: break;
 | |
|     }
 | |
| }
 | |
| 
 | |
| #define MENU_DRAW_LINES 4
 | |
| 
 | |
| Point draw_block(u8g2_t* u8g2, const char* text, Block layout, uint8_t x, uint8_t y) {
 | |
|     u8g2_SetDrawColor(u8g2, layout.background);
 | |
|     u8g2_DrawBox(u8g2,
 | |
|         x + layout.margin_left,
 | |
|         y + layout.margin_top,
 | |
|         layout.width, layout.height
 | |
|     );
 | |
| 
 | |
|     u8g2_SetDrawColor(u8g2, layout.color);
 | |
|     u8g2_SetFont(u8g2, layout.font);
 | |
|     if(text != NULL) {
 | |
|         u8g2_DrawStr(u8g2,
 | |
|             x + layout.margin_left + layout.padding_left,
 | |
|             y + layout.margin_top + layout.padding_top,
 | |
|             text
 | |
|         );
 | |
|     }
 | |
| 
 | |
|     return {
 | |
|         x: x + layout.margin_left + layout.width,
 | |
|         y: y + layout.margin_top + layout.height
 | |
|     };
 | |
| }
 | |
| 
 | |
| void draw_menu(MenuCtx* ctx, u8g2_t* u8g2, ScreenArea area) {
 | |
|     // u8g2_ClearBuffer(u8g2);
 | |
|     // clear area
 | |
|     u8g2_SetDrawColor(u8g2, 0);
 | |
|     u8g2_DrawBox(u8g2, area.x, area.y, area.width, area.height);
 | |
| 
 | |
|     u8g2_SetFontMode(u8g2, 1);
 | |
| 
 | |
|     uint8_t list_start = ctx->current - ctx->cursor;
 | |
|     uint8_t list_size = ctx->size > MENU_DRAW_LINES ? MENU_DRAW_LINES : ctx->size;
 | |
| 
 | |
|     // draw header
 | |
|     /*
 | |
|     Point next = draw_block(u8g2, (const char*)data->name, Block {
 | |
|         width: 128,
 | |
|         height: 14,
 | |
|         margin_left: 0,
 | |
|         margin_top: 0,
 | |
|         padding_left: 4,
 | |
|         padding_top: 13,
 | |
|         background: 1,
 | |
|         color: 0,
 | |
|         font: (uint8_t*)u8g2_font_helvB14_tf,
 | |
|     }, area.x, area.y);
 | |
|     */
 | |
| 
 | |
|     Point next = {area.x, area.y};
 | |
| 
 | |
|     for(size_t i = 0; i < list_size; i++) {
 | |
|         next = draw_block(u8g2, (const char*)ctx->list[list_start + i].name, Block {
 | |
|             width: 128,
 | |
|             height: 15,
 | |
|             margin_left: 0,
 | |
|             margin_top: i == 0 ? 2 : 0,
 | |
|             padding_left: 2,
 | |
|             padding_top: 12,
 | |
|             background: i == ctx->cursor ? 1 : 0,
 | |
|             color: i == ctx->cursor ? 0 : 1,
 | |
|             font: (uint8_t*)u8g2_font_7x14_tf,
 | |
|         }, area.x, next.y);
 | |
|     }
 | |
| 
 | |
|     // u8g2_font_7x14_tf
 | |
|     // u8g2_font_profont12_tf smallerbut cute
 | |
|     // u8g2_font_samim_12_t_all орочий
 | |
| }
 | |
| 
 | |
| void MenuCtx::handle(MenuEvent event) {
 | |
|     uint8_t menu_size = this->size > MENU_DRAW_LINES ? MENU_DRAW_LINES : this->size;
 | |
| 
 | |
|     switch(event) {
 | |
|         case MenuEventDown: {
 | |
|             if(this->current < (this->size - 1)) {
 | |
|                 this->current++;
 | |
| 
 | |
|                 if(this->cursor < menu_size - 2 || this->current == this->size - 1) {
 | |
|                     this->cursor++;
 | |
|                 }
 | |
|             } else {
 | |
|                 this->current = 0;
 | |
|                 this->cursor = 0;
 | |
|             }
 | |
|         } break;
 | |
| 
 | |
|         case MenuEventUp: {
 | |
|             if(this->current > 0) {
 | |
|                 this->current--;
 | |
| 
 | |
|                 if(this->cursor > 1 || this->current == 0) {
 | |
|                     this->cursor--;
 | |
|                 }
 | |
| 
 | |
|             } else {
 | |
|                 this->current = this->size - 1;
 | |
|                 this->cursor = menu_size - 1;
 | |
|             }
 | |
|         } break;
 | |
|     }
 | |
| }
 | |
| 
 | |
| void MenuCtx::reset() {
 | |
|     this->current = 0;
 | |
|     this->cursor = 0;
 | |
| }
 | |
| 
 | |
| // draw numenu and handle navigation
 | |
| void MenuComponent::handle(Event* event, Store* store, u8g2_t* u8g2, ScreenArea area) {
 | |
|     switch(event->type) {
 | |
|         // get button event
 | |
|         case EventTypeButton: {
 | |
|             if(event->value.button.id == ButtonsOk && event->value.button.state) {
 | |
|                 if(this->ctx.current < this->ctx.size) {
 | |
|                     FlipperComponent* next_item = (FlipperComponent*)this->ctx.list[this->ctx.current].item;
 | |
| 
 | |
|                     if(next_item) {
 | |
|                         store->is_fullscreen = false;
 | |
| 
 | |
|                         printf("[layout view] go to %d item\n", this->ctx.current);
 | |
|                         Event send_event;
 | |
|                         send_event.type = EventTypeUiNext;
 | |
| 
 | |
|                         next_item->handle(
 | |
|                             &send_event,
 | |
|                             store,
 | |
|                             u8g2,
 | |
|                             area
 | |
|                         );
 | |
|                     } else {
 | |
|                         printf("[menu view] no item at %d\n", this->ctx.current);
 | |
|                     }
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             if(event->value.button.id == ButtonsDown && event->value.button.state) {
 | |
|                 this->ctx.handle(MenuEventDown);
 | |
|                 this->dirty = true;
 | |
|             }
 | |
| 
 | |
|             if(event->value.button.id == ButtonsUp && event->value.button.state) {
 | |
|                 this->ctx.handle(MenuEventUp);
 | |
|                 this->dirty = true;
 | |
|             }
 | |
| 
 | |
|             // go back item
 | |
|             if(event->value.button.id == ButtonsBack && event->value.button.state) {
 | |
|                 if(this->prev && this->prev != this) {
 | |
|                     store->is_fullscreen = false;
 | |
| 
 | |
|                     printf("[menu view] go back\n");
 | |
| 
 | |
|                     this->ctx.reset();
 | |
| 
 | |
|                     Event send_event;
 | |
|                     send_event.type = EventTypeUiNext;
 | |
|                     this->prev->handle(
 | |
|                         &send_event,
 | |
|                         store,
 | |
|                         u8g2,
 | |
|                         area
 | |
|                     );
 | |
| 
 | |
|                     this->prev = NULL;
 | |
|                 } else {
 | |
|                     printf("[menu view] no back/loop\n");
 | |
|                 }
 | |
|             }
 | |
|         } break;
 | |
| 
 | |
|         // start component from prev
 | |
|         case EventTypeUiNext:
 | |
|             printf("[menu view] start component %lX (size %d)\n", (uint32_t)this, this->ctx.size);
 | |
| 
 | |
|             // set prev item
 | |
|             if(this->prev == NULL) {
 | |
|                 printf("[menu view] set prev element to %lX\n", (uint32_t)store->current_component);
 | |
|                 this->prev = store->current_component;
 | |
|             }
 | |
|             // set current item to self
 | |
|             store->current_component = this;
 | |
| 
 | |
|             store->is_fullscreen = true;
 | |
| 
 | |
|             // render menu
 | |
|             this->dirty = true;
 | |
|         break;
 | |
| 
 | |
|         case EventTypeTick:
 | |
|             if(this->dirty) {
 | |
|                 draw_menu(&this->ctx, u8g2, area);
 | |
|                 store->dirty_screen = true;
 | |
|                 this->dirty = false;
 | |
|             }
 | |
|         break;
 | |
| 
 | |
|         default: break;
 | |
|     }
 | |
| }
 |