#include 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; } }