GUI: status bar rendering. Power: battery indicator. (#207)

* Menu: animation. Irukagotchi: idle image.
* Power: battery, usb activity widget
* Power: tune battery max voltage and clamp overshoot
* get initial charge state

Co-authored-by: Aleksandr Kutuzov <aku@plooks.com>
Co-authored-by: aanper <mail@s3f.ru>
This commit is contained in:
あく 2020-10-29 10:11:16 +03:00 committed by GitHub
parent 8aeafd8179
commit 0af239ebc0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 357 additions and 33 deletions

View file

@ -2,6 +2,7 @@
#include <gui/gui.h>
#include "menu/menu.h"
#include "applications.h"
#include <assets_icons.h>
typedef struct {
FuriApp* handler;
@ -87,7 +88,9 @@ void app_loader(void* p) {
ctx->app = &FLIPPER_APPS[i];
menu_item_add(
menu, menu_item_alloc_function(FLIPPER_APPS[i].name, NULL, handle_menu, ctx));
menu,
menu_item_alloc_function(
FLIPPER_APPS[i].name, assets_icons_get(A_Infrared_14), handle_menu, ctx));
}
/*

View file

@ -31,6 +31,7 @@ void cc1101_workaround(void* p);
void lf_rfid_workaround(void* p);
void nfc_task(void* p);
void irukagotchi_task(void* p);
void power_task(void* p);
const FlipperStartupApp FLIPPER_STARTUP[] = {
#ifdef APP_DISPLAY
@ -59,6 +60,10 @@ const FlipperStartupApp FLIPPER_STARTUP[] = {
{.app = irukagotchi_task, .name = "irukagotchi_task", .libs = {1, FURI_LIB{"menu_task"}}},
#endif
#ifdef APP_POWER
{.app = power_task, .name = "power_task", .libs = {1, FURI_LIB{"gui_task"}}},
#endif
#ifdef APP_CC1101
{.app = cc1101_workaround, .name = "cc1101 workaround", .libs = {1, FURI_LIB{"gui_task"}}},
#endif

View file

@ -10,6 +10,7 @@ APP_RELEASE ?= 0
ifeq ($(APP_RELEASE), 1)
APP_MENU = 1
APP_NFC = 1
APP_POWER = 1
BUILD_IRDA = 1
APP_IRUKAGOTCHI = 1
BUILD_EXAMPLE_BLINK = 1
@ -34,6 +35,13 @@ CFLAGS += -DAPP_IRUKAGOTCHI
C_SOURCES += $(wildcard $(APP_DIR)/irukagotchi/*.c)
endif
APP_POWER ?= 0
ifeq ($(APP_POWER), 1)
APP_GUI = 1
CFLAGS += -DAPP_POWER
C_SOURCES += $(wildcard $(APP_DIR)/power/*.c)
endif
APP_MENU ?= 0
ifeq ($(APP_MENU), 1)
CFLAGS += -DAPP_MENU

View file

@ -64,6 +64,12 @@ void canvas_api_free(CanvasApi* api) {
free(api);
}
void canvas_reset(CanvasApi* api) {
assert(api);
canvas_color_set(api, ColorBlack);
canvas_font_set(api, FontSecondary);
}
void canvas_commit(CanvasApi* api) {
furi_assert(api);
Canvas* canvas = (Canvas*)api;
@ -144,23 +150,33 @@ void canvas_icon_draw(CanvasApi* api, uint8_t x, uint8_t y, Icon* icon) {
void canvas_dot_draw(CanvasApi* api, uint8_t x, uint8_t y) {
furi_assert(api);
Canvas* canvas = (Canvas*)api;
x += canvas->offset_x;
y += canvas->offset_y;
u8g2_DrawPixel(&canvas->fb, x, y);
}
void canvas_box_draw(CanvasApi* api, uint8_t x, uint8_t y, uint8_t width, uint8_t height) {
furi_assert(api);
Canvas* canvas = (Canvas*)api;
x += canvas->offset_x;
y += canvas->offset_y;
u8g2_DrawBox(&canvas->fb, x, y, width, height);
}
void canvas_draw_frame(CanvasApi* api, uint8_t x, uint8_t y, uint8_t width, uint8_t height) {
furi_assert(api);
Canvas* canvas = (Canvas*)api;
x += canvas->offset_x;
y += canvas->offset_y;
u8g2_DrawFrame(&canvas->fb, x, y, width, height);
}
void canvas_draw_line(CanvasApi* api, uint8_t x1, uint8_t y1, uint8_t x2, uint8_t y2) {
furi_assert(api);
Canvas* canvas = (Canvas*)api;
x1 += canvas->offset_x;
y1 += canvas->offset_y;
x2 += canvas->offset_x;
y2 += canvas->offset_y;
u8g2_DrawLine(&canvas->fb, x1, y1, x2, y2);
}

View file

@ -4,6 +4,8 @@ CanvasApi* canvas_api_init();
void canvas_api_free(CanvasApi* api);
void canvas_reset(CanvasApi* api);
void canvas_commit(CanvasApi* api);
void canvas_frame_set(

View file

@ -41,7 +41,7 @@ void gui_update(Gui* gui) {
}
bool gui_redraw_fs(Gui* gui) {
canvas_frame_set(gui->canvas_api, 0, 0, 128, 64);
canvas_frame_set(gui->canvas_api, 0, 0, GUI_DISPLAY_WIDTH, GUI_DISPLAY_HEIGHT);
Widget* widget = gui_widget_find_enabled(gui->layers[GuiLayerFullscreen]);
if(widget) {
widget_draw(widget, gui->canvas_api);
@ -51,18 +51,48 @@ bool gui_redraw_fs(Gui* gui) {
}
}
bool gui_redraw_status_bar(Gui* gui) {
canvas_frame_set(gui->canvas_api, 0, 0, 128, 64);
Widget* widget = gui_widget_find_enabled(gui->layers[GuiLayerStatusBar]);
if(widget) {
widget_draw(widget, gui->canvas_api);
return true;
void gui_redraw_status_bar(Gui* gui) {
WidgetArray_it_t it;
uint8_t x;
uint8_t x_used = 0;
uint8_t width;
Widget* widget;
// Right side
x = 128;
WidgetArray_it(it, gui->layers[GuiLayerStatusBarRight]);
while(!WidgetArray_end_p(it) && x_used < GUI_STATUS_BAR_WIDTH) {
// Render widget;
widget = *WidgetArray_ref(it);
if(widget_is_enabled(widget)) {
width = widget_get_width(widget);
if(!width) width = 8;
x_used += width;
x -= width;
canvas_frame_set(gui->canvas_api, x, GUI_STATUS_BAR_Y, width, GUI_STATUS_BAR_HEIGHT);
widget_draw(widget, gui->canvas_api);
}
WidgetArray_next(it);
}
// Left side
x = 0;
WidgetArray_it(it, gui->layers[GuiLayerStatusBarLeft]);
while(!WidgetArray_end_p(it) && x_used < GUI_STATUS_BAR_WIDTH) {
// Render widget;
widget = *WidgetArray_ref(it);
if(widget_is_enabled(widget)) {
width = widget_get_width(widget);
if(!width) width = 8;
x_used += width;
canvas_frame_set(gui->canvas_api, x, GUI_STATUS_BAR_Y, width, GUI_STATUS_BAR_HEIGHT);
widget_draw(widget, gui->canvas_api);
x += width;
}
WidgetArray_next(it);
}
return false;
}
bool gui_redraw_normal(Gui* gui) {
canvas_frame_set(gui->canvas_api, 0, 9, 128, 55);
canvas_frame_set(gui->canvas_api, GUI_MAIN_X, GUI_MAIN_Y, GUI_MAIN_WIDTH, GUI_MAIN_HEIGHT);
Widget* widget = gui_widget_find_enabled(gui->layers[GuiLayerMain]);
if(widget) {
widget_draw(widget, gui->canvas_api);
@ -72,7 +102,7 @@ bool gui_redraw_normal(Gui* gui) {
}
bool gui_redraw_none(Gui* gui) {
canvas_frame_set(gui->canvas_api, 0, 9, 118, 44);
canvas_frame_set(gui->canvas_api, GUI_MAIN_X, GUI_MAIN_Y, GUI_MAIN_WIDTH, GUI_MAIN_HEIGHT);
Widget* widget = gui_widget_find_enabled(gui->layers[GuiLayerNone]);
if(widget) {
widget_draw(widget, gui->canvas_api);
@ -86,6 +116,8 @@ void gui_redraw(Gui* gui) {
furi_assert(gui);
gui_lock(gui);
canvas_reset(gui->canvas_api);
if(!gui_redraw_fs(gui)) {
if(!gui_redraw_normal(gui)) {
gui_redraw_none(gui);

View file

@ -3,10 +3,24 @@
#include "widget.h"
#include "canvas.h"
#define GUI_DISPLAY_WIDTH 128
#define GUI_DISPLAY_HEIGHT 64
#define GUI_STATUS_BAR_X 0
#define GUI_STATUS_BAR_Y 0
#define GUI_STATUS_BAR_WIDTH GUI_DISPLAY_WIDTH
#define GUI_STATUS_BAR_HEIGHT 8
#define GUI_MAIN_X 0
#define GUI_MAIN_Y 9
#define GUI_MAIN_WIDTH GUI_DISPLAY_WIDTH
#define GUI_MAIN_HEIGHT (GUI_DISPLAY_HEIGHT - GUI_MAIN_Y)
typedef enum {
GuiLayerNone, /* Special layer for internal use only */
GuiLayerStatusBar, /* Status bar widget layer */
GuiLayerStatusBarLeft, /* Status bar left-side widget layer, auto-layout */
GuiLayerStatusBarRight, /* Status bar right-side widget layer, auto-layout */
GuiLayerMain, /* Main widget layer, status bar is shown */
GuiLayerFullscreen, /* Fullscreen widget layer */

View file

@ -6,9 +6,20 @@
struct GuiEvent {
PubSub* input_event_record;
osTimerId_t timer;
osMessageQueueId_t mqueue;
};
void gui_event_timer_callback(void* arg) {
assert(arg);
GuiEvent* gui_event = arg;
GuiMessage message;
message.type = GuiMessageTypeRedraw;
osMessageQueuePut(gui_event->mqueue, &message, 0, osWaitForever);
}
void gui_event_input_events_callback(const void* value, void* ctx) {
furi_assert(value);
furi_assert(ctx);
@ -29,6 +40,10 @@ GuiEvent* gui_event_alloc() {
gui_event->mqueue = osMessageQueueNew(GUI_EVENT_MQUEUE_SIZE, sizeof(GuiMessage), NULL);
furi_check(gui_event->mqueue);
gui_event->timer = osTimerNew(gui_event_timer_callback, osTimerPeriodic, gui_event, NULL);
assert(gui_event->timer);
osTimerStart(gui_event->timer, 1000 / 10);
// Input
gui_event->input_event_record = furi_open("input_events");
furi_check(gui_event->input_event_record != NULL);

View file

@ -10,15 +10,6 @@
// TODO add mutex to widget ops
struct Widget {
Gui* gui;
bool is_enabled;
WidgetDrawCallback draw_callback;
void* draw_callback_context;
WidgetInputCallback input_callback;
void* input_callback_context;
};
Widget* widget_alloc(WidgetDrawCallback callback, void* callback_context) {
Widget* widget = furi_alloc(sizeof(Widget));
widget->is_enabled = true;
@ -31,6 +22,26 @@ void widget_free(Widget* widget) {
free(widget);
}
void widget_set_width(Widget* widget, uint8_t width) {
assert(widget);
widget->width = width;
}
uint8_t widget_get_width(Widget* widget) {
assert(widget);
return widget->width;
}
void widget_set_height(Widget* widget, uint8_t height) {
assert(widget);
widget->height = height;
}
uint8_t widget_get_height(Widget* widget) {
assert(widget);
return widget->height;
}
void widget_enabled_set(Widget* widget, bool enabled) {
furi_assert(widget);
if(widget->is_enabled != enabled) {
@ -80,7 +91,9 @@ void widget_draw(Widget* widget, CanvasApi* canvas_api) {
void widget_input(Widget* widget, InputEvent* event) {
furi_assert(widget);
furi_assert(event);
furi_check(widget->gui);
if(widget->input_callback) widget->input_callback(event, widget->input_callback_context);
if(widget->input_callback) {
widget->input_callback(event, widget->input_callback_context);
}
}

View file

@ -8,14 +8,51 @@ typedef struct Widget Widget;
typedef void (*WidgetDrawCallback)(CanvasApi* api, void* context);
typedef void (*WidgetInputCallback)(InputEvent* event, void* context);
/*
* Widget allocator
* always returns widget or stops system if not enough memory.
*/
Widget* widget_alloc();
/*
* Widget deallocator
* Ensure that widget was unregistered in GUI system before use.
*/
void widget_free(Widget* widget);
/*
* Set widget width.
* Will be used to limit canvas drawing area and autolayout feature.
* @param width - wanted width, 0 - auto.
*/
void widget_set_width(Widget* widget, uint8_t width);
uint8_t widget_get_width(Widget* widget);
/*
* Set widget height.
* Will be used to limit canvas drawing area and autolayout feature.
* @param height - wanted height, 0 - auto.
*/
void widget_set_height(Widget* widget, uint8_t height);
uint8_t widget_get_height(Widget* widget);
/*
* Enable or disable widget rendering.
* @param enabled.
*/
void widget_enabled_set(Widget* widget, bool enabled);
bool widget_is_enabled(Widget* widget);
/*
* Widget event callbacks
* @param callback - appropriate callback function
* @param context - context to pass to callback
*/
void widget_draw_callback_set(Widget* widget, WidgetDrawCallback callback, void* context);
void widget_input_callback_set(Widget* widget, WidgetInputCallback callback, void* context);
// emit update signal
/*
* Emit update signal to GUI system.
* Rendering will happen later after GUI system process signal.
*/
void widget_update(Widget* widget);

View file

@ -2,8 +2,31 @@
#include "gui_i.h"
struct Widget {
Gui* gui;
bool is_enabled;
uint8_t width;
uint8_t height;
WidgetDrawCallback draw_callback;
void* draw_callback_context;
WidgetInputCallback input_callback;
void* input_callback_context;
};
/*
* Set GUI referenec.
* @param gui - gui instance pointer.
*/
void widget_gui_set(Widget* widget, Gui* gui);
/*
* Process draw call. Calls draw callback.
* @param canvas_api - canvas to draw at.
*/
void widget_draw(Widget* widget, CanvasApi* canvas_api);
/*
* Process input. Calls input callback.
* @param event - pointer to input event.
*/
void widget_input(Widget* widget, InputEvent* event);

View file

@ -21,8 +21,9 @@ void irukagotchi_draw_callback(CanvasApi* canvas, void* context) {
canvas->clear(canvas);
canvas->set_color(canvas, ColorBlack);
canvas->set_font(canvas, FontPrimary);
canvas->draw_icon(canvas, 10, 20, irukagotchi->icon);
canvas->draw_str(canvas, 30, 32, "Irukagotchi");
canvas->draw_icon(canvas, 0, 0, irukagotchi->icon);
canvas->draw_str(canvas, 80, 30, "111001");
canvas->draw_str(canvas, 80, 42, "011010");
}
void irukagotchi_input_callback(InputEvent* event, void* context) {
@ -37,7 +38,7 @@ void irukagotchi_input_callback(InputEvent* event, void* context) {
Irukagotchi* irukagotchi_alloc() {
Irukagotchi* irukagotchi = furi_alloc(sizeof(Irukagotchi));
irukagotchi->icon = assets_icons_get(A_Tamagotchi_14);
irukagotchi->icon = assets_icons_get(I_Flipper_young_80x60);
icon_start_animation(irukagotchi->icon);
irukagotchi->widget = widget_alloc();

View file

@ -9,12 +9,14 @@
#include "menu_event.h"
#include "menu_item.h"
#include <assets_icons.h>
struct Menu {
MenuEvent* event;
// GUI
Widget* widget;
Icon* icon;
// State
MenuItem* root;
@ -56,7 +58,8 @@ void menu_build_main(Menu* menu) {
// Root point
menu->root = menu_item_alloc_menu(NULL, NULL);
menu->settings = menu_item_alloc_menu("Setting", NULL);
Icon* icon = assets_icons_get(A_Bluetooth_14);
menu->settings = menu_item_alloc_menu("Setting", icon);
menu_item_add(menu, menu->settings);
}
@ -103,16 +106,18 @@ void menu_widget_callback(CanvasApi* canvas, void* context) {
canvas->set_font(canvas, FontPrimary);
shift_position = (1 + position + items_count - 1) % (MenuItemArray_size(*items));
item = *MenuItemArray_get(*items, shift_position);
canvas->draw_icon(canvas, 4, 24, menu_item_get_icon(item));
canvas->draw_str(canvas, 22, 35, menu_item_get_label(item));
canvas->draw_icon(canvas, 4, 25, menu_item_get_icon(item));
canvas->draw_str(canvas, 22, 36, menu_item_get_label(item));
// Third line
canvas->set_font(canvas, FontSecondary);
shift_position = (2 + position + items_count - 1) % (MenuItemArray_size(*items));
item = *MenuItemArray_get(*items, shift_position);
canvas->draw_icon(canvas, 4, 46, menu_item_get_icon(item));
canvas->draw_str(canvas, 22, 57, menu_item_get_label(item));
canvas->draw_icon(canvas, 4, 47, menu_item_get_icon(item));
canvas->draw_str(canvas, 22, 58, menu_item_get_label(item));
// Frame and scrollbar
elements_frame(canvas, 0, 20, 128 - 5, 22);
// elements_frame(canvas, 0, 0, 128 - 5, 21);
elements_frame(canvas, 0, 21, 128 - 5, 21);
// elements_frame(canvas, 0, 42, 128 - 5, 21);
elements_scrollbar(canvas, position, items_count);
} else {
canvas->draw_str(canvas, 2, 32, "Empty");
@ -122,9 +127,33 @@ void menu_widget_callback(CanvasApi* canvas, void* context) {
release_mutex((ValueMutex*)context, menu);
}
void menu_set_icon(Menu* menu, Icon* icon) {
assert(menu);
if(menu->icon) {
icon_stop_animation(menu->icon);
}
menu->icon = icon;
if(menu->icon) {
icon_start_animation(menu->icon);
}
}
void menu_update(Menu* menu) {
furi_assert(menu);
if(menu->current) {
size_t position = menu_item_get_position(menu->current);
MenuItemArray_t* items = menu_item_get_subitems(menu->current);
size_t items_count = MenuItemArray_size(*items);
if(items_count) {
MenuItem* item = *MenuItemArray_get(*items, position);
menu_set_icon(menu, menu_item_get_icon(item));
}
}
menu_event_activity_notify(menu->event);
widget_update(menu->widget);
}

117
applications/power/power.c Normal file
View file

@ -0,0 +1,117 @@
#include "power.h"
#include <flipper_v2.h>
#include <gui/gui.h>
#include <gui/widget.h>
#include <assets_icons.h>
#define BATTERY_MIN_VOLTAGE 3.2f
#define BATTERY_MAX_VOLTAGE 4.0f
#define BATTERY_INIT 0xFFAACCEE
extern ADC_HandleTypeDef hadc1;
struct Power {
Icon* usb_icon;
Widget* usb_widget;
Icon* battery_icon;
Widget* battery_widget;
uint32_t charge;
};
void power_draw_usb_callback(CanvasApi* canvas, void* context) {
assert(context);
Power* power = context;
canvas->draw_icon(canvas, 0, 0, power->usb_icon);
}
void power_draw_battery_callback(CanvasApi* canvas, void* context) {
assert(context);
Power* power = context;
canvas->draw_icon(canvas, 0, 0, power->battery_icon);
if(power->charge != BATTERY_INIT) {
float charge = ((float)power->charge / 1000 * 2 - BATTERY_MIN_VOLTAGE) /
(BATTERY_MAX_VOLTAGE - BATTERY_MIN_VOLTAGE);
if(charge > 1) {
charge = 1;
}
canvas->draw_box(canvas, 2, 2, charge * 14, 4);
}
}
void power_input_events_callback(const void* value, void* ctx) {
assert(ctx);
Power* power = ctx;
InputEvent* event = value;
if(event->input != InputCharging) return;
widget_enabled_set(power->usb_widget, event->state);
widget_update(power->usb_widget);
}
Power* power_alloc() {
Power* power = furi_alloc(sizeof(Power));
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));
ValueManager* input_state_manager = furi_open("input_state");
InputState input_state;
read_mutex_block(input_state_manager, &input_state, sizeof(input_state));
widget_enabled_set(power->usb_widget, input_state.charging);
widget_draw_callback_set(power->usb_widget, power_draw_usb_callback, power);
power->battery_icon = assets_icons_get(I_Battery_19x8);
power->battery_widget = widget_alloc();
widget_set_width(power->battery_widget, icon_get_width(power->battery_icon));
widget_draw_callback_set(power->battery_widget, power_draw_battery_callback, power);
PubSub* input_event_record = furi_open("input_events");
assert(input_event_record);
subscribe_pubsub(input_event_record, power_input_events_callback, power);
power->charge = BATTERY_INIT;
return power;
}
void power_free(Power* power) {
assert(power);
free(power);
}
void power_task(void* p) {
(void)p;
Power* power = power_alloc();
FuriRecordSubscriber* gui_record = furi_open_deprecated("gui", false, false, NULL, NULL, NULL);
assert(gui_record);
GuiApi* gui = furi_take(gui_record);
assert(gui);
gui->add_widget(gui, power->usb_widget, GuiLayerStatusBarLeft);
gui->add_widget(gui, power->battery_widget, GuiLayerStatusBarRight);
furi_commit(gui_record);
if(!furi_create("power", power)) {
printf("[power_task] unable to create power record\n");
furiac_exit(NULL);
}
furiac_ready();
while(1) {
HAL_ADC_Start(&hadc1);
if(HAL_ADC_PollForConversion(&hadc1, 1000) != HAL_TIMEOUT) {
power->charge = HAL_ADC_GetValue(&hadc1);
widget_update(power->battery_widget);
}
osDelay(1000);
}
}

View file

@ -0,0 +1,3 @@
#pragma once
typedef struct Power Power;

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:526c2637560aab102f259c029eeee93cc13e5cd8bdb935acb624f1318502f286
size 652

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:c7d2c5da9b957635189c2ee475d7065ae714fd6fb5d6f0c83ca5fa0bcd1497f8
size 2653