Implement UI scaling and more (#501)

**New Features**
 * Runtime font accessors and new symbol fonts for text, launcher, statusbar, and shared icons.
 * Added font height base setting to device.properties
 * Text fonts now have 3 sizes: small, default, large

**Improvements**
 * Renamed `UiScale` to `UiDensity`
 * Statusbar, toolbar and many UI components now compute heights and spacing from fonts/density.
 * SSD1306 initialization sequence refined for more stable startup.
 * Multiple image assets replaced by symbol-font rendering.
 * Many layout improvements related to density, font scaling and icon scaling
 * Updated folder name capitalization for newer style
This commit is contained in:
Ken Van Hoeylandt
2026-02-15 01:41:47 +01:00
committed by GitHub
parent 72c9b2b113
commit 9a11e6f47b
264 changed files with 5923 additions and 494 deletions
+1
View File
@@ -16,6 +16,7 @@
#include <Tactility/file/FileLock.h>
#include <Tactility/file/PropertiesFile.h>
#include <Tactility/hal/HalPrivate.h>
#include <Tactility/hal/sdcard/SdCardDevice.h>
#include <Tactility/Logger.h>
#include <Tactility/LogMessages.h>
#include <Tactility/lvgl/LvglPrivate.h>
+1 -1
View File
@@ -21,7 +21,7 @@ class AppListApp final : public App {
const void* icon = !manifest->appIcon.empty() ? manifest->appIcon.c_str() : LVGL_SYMBOL_TOOLBAR;
lv_obj_t* btn = lv_list_add_button(list, icon, manifest->appName.c_str());
lv_obj_t* image = lv_obj_get_child(btn, 0);
lv_obj_set_style_text_font(image, LVGL_SYMBOL_FONT_DEFAULT, LV_PART_MAIN);
lv_obj_set_style_text_font(image, lvgl_get_shared_icon_font(), LV_PART_MAIN);
lv_obj_add_event_cb(btn, &onAppPressed, LV_EVENT_SHORT_CLICKED, manifest.get());
}
@@ -22,7 +22,7 @@ class AppSettingsApp final : public App {
const void* icon = !manifest->appIcon.empty() ? manifest->appIcon.c_str() : LVGL_SYMBOL_TOOLBAR;
lv_obj_t* btn = lv_list_add_button(list, icon, manifest->appName.c_str());
lv_obj_t* image = lv_obj_get_child(btn, 0);
lv_obj_set_style_text_font(image, LVGL_SYMBOL_FONT_DEFAULT, LV_PART_MAIN);
lv_obj_set_style_text_font(image, lvgl_get_shared_icon_font(), LV_PART_MAIN);
lv_obj_add_event_cb(btn, &onAppPressed, LV_EVENT_SHORT_CLICKED, manifest.get());
}
@@ -28,7 +28,7 @@ public:
void onShow(AppContext& app, lv_obj_t* parent) override {
auto* display = lv_obj_get_display(parent);
int32_t parent_height = lv_display_get_vertical_resolution(display) - lvgl::STATUSBAR_HEIGHT;
int32_t parent_height = lv_display_get_vertical_resolution(display) - lvgl::statusbar_get_height();
lv_obj_add_event_cb(parent, onContinuePressed, LV_EVENT_SHORT_CLICKED, nullptr);
auto* top_label = lv_label_create(parent);
+3 -3
View File
@@ -122,7 +122,7 @@ public:
void onShow(AppContext& app, lv_obj_t* parent) override {
displaySettings = settings::display::loadOrGetDefault();
auto ui_scale = hal::getConfiguration()->uiScale;
auto ui_density = hal::getConfiguration()->uiDensity;
lv_obj_set_flex_flow(parent, LV_FLEX_FLOW_COLUMN);
lv_obj_set_style_pad_row(parent, 0, LV_STATE_DEFAULT);
@@ -144,7 +144,7 @@ public:
lv_obj_set_size(brightness_wrapper, LV_PCT(100), LV_SIZE_CONTENT);
lv_obj_set_style_pad_hor(brightness_wrapper, 0, LV_STATE_DEFAULT);
lv_obj_set_style_border_width(brightness_wrapper, 0, LV_STATE_DEFAULT);
if (ui_scale != hal::UiScale::Smallest) {
if (ui_density != hal::UiDensity::Compact) {
lv_obj_set_style_pad_ver(brightness_wrapper, 4, LV_STATE_DEFAULT);
}
@@ -168,7 +168,7 @@ public:
lv_obj_set_size(gamma_wrapper, LV_PCT(100), LV_SIZE_CONTENT);
lv_obj_set_style_pad_hor(gamma_wrapper, 0, LV_STATE_DEFAULT);
lv_obj_set_style_border_width(gamma_wrapper, 0, LV_STATE_DEFAULT);
if (ui_scale != hal::UiScale::Smallest) {
if (ui_density != hal::UiDensity::Compact) {
lv_obj_set_style_pad_ver(gamma_wrapper, 4, LV_STATE_DEFAULT);
}
+24 -26
View File
@@ -17,21 +17,22 @@ namespace tt::app::launcher {
static const auto LOGGER = Logger("Launcher");
static int getButtonSize(hal::UiScale scale) {
if (scale == hal::UiScale::Smallest) {
return 36; // icon size
static uint32_t getButtonPadding(hal::UiDensity density, uint32_t buttonSize) {
if (density == hal::UiDensity::Compact) {
return 0;
} else {
return 56;
return buttonSize / 8;
}
}
class LauncherApp final : public App {
static lv_obj_t* createAppButton(lv_obj_t* parent, hal::UiScale uiScale, const char* imageFile, const char* appId, int32_t itemMargin, bool isLandscape) {
auto button_size = getButtonSize(uiScale);
static lv_obj_t* createAppButton(lv_obj_t* parent, hal::UiDensity uiDensity, const char* imageFile, const char* appId, int32_t itemMargin, bool isLandscape) {
const auto button_size = lvgl_get_launcher_icon_font_height();
const auto button_padding = getButtonPadding(uiDensity, button_size);
auto* apps_button = lv_button_create(parent);
lv_obj_set_style_pad_all(apps_button, 0, LV_STATE_DEFAULT);
lv_obj_set_style_pad_all(apps_button, static_cast<int32_t>(button_padding), LV_STATE_DEFAULT);
if (isLandscape) {
lv_obj_set_style_margin_hor(apps_button, itemMargin, LV_STATE_DEFAULT);
} else {
@@ -43,7 +44,7 @@ class LauncherApp final : public App {
// create the image first
auto* button_image = lv_image_create(apps_button);
lv_obj_set_style_text_font(button_image, LVGL_SYMBOL_FONT_LAUNCHER, LV_STATE_DEFAULT);
lv_obj_set_style_text_font(button_image, lvgl_get_launcher_icon_font(), LV_STATE_DEFAULT);
lv_image_set_src(button_image, imageFile);
lv_obj_set_style_text_color(button_image, lv_theme_get_color_primary(button_image), LV_STATE_DEFAULT);
@@ -59,8 +60,7 @@ class LauncherApp final : public App {
lv_obj_set_style_image_recolor_opa(button_image, LV_OPA_COVER, LV_STATE_DEFAULT);
#endif
// Ensure buttons are still tappable when the asset fails to load
// Icon images are 40x40, so we get some extra padding too
// Ensure it's square (Material Symbols are slightly wider than tall)
lv_obj_set_size(button_image, button_size, button_size);
lv_obj_add_event_cb(apps_button, onAppPressed, LV_EVENT_SHORT_CLICKED, (void*)appId);
@@ -119,20 +119,18 @@ public:
void onShow(AppContext& app, lv_obj_t* parent) override {
auto* buttons_wrapper = lv_obj_create(parent);
auto ui_scale = hal::getConfiguration()->uiScale;
auto button_size = getButtonSize(ui_scale);
auto ui_density = hal::getConfiguration()->uiDensity;
const auto button_size = lvgl_get_launcher_icon_font_height();
const auto button_padding = getButtonPadding(ui_density, button_size);
const auto total_button_size = button_size + (button_padding * 2);
lv_obj_align(buttons_wrapper, LV_ALIGN_CENTER, 0, 0);
lv_obj_set_size(buttons_wrapper, LV_SIZE_CONTENT, LV_SIZE_CONTENT);
lv_obj_set_style_border_width(buttons_wrapper, 0, LV_STATE_DEFAULT);
lv_obj_set_flex_grow(buttons_wrapper, 1);
// Fix for button selection (problem with UiScale::Small on Cardputer)
if (!hal::hasDevice(hal::Device::Type::Touch)) {
lv_obj_set_style_pad_all(buttons_wrapper, 6, LV_STATE_DEFAULT);
} else {
lv_obj_set_style_pad_all(buttons_wrapper, 0, LV_STATE_DEFAULT);
}
// Fix for button selection
lv_obj_set_style_pad_all(buttons_wrapper, 6, LV_STATE_DEFAULT);
const auto* display = lv_obj_get_display(parent);
const auto horizontal_px = lv_display_get_horizontal_resolution(display);
@@ -146,16 +144,16 @@ public:
int32_t margin;
if (is_landscape_display) {
const int32_t available_width = std::max<int32_t>(0, lv_display_get_horizontal_resolution(display) - (3 * button_size));
margin = std::min<int32_t>(available_width / 16, button_size);
const int32_t available_width = std::max<int32_t>(0, lv_display_get_horizontal_resolution(display) - (3 * total_button_size));
margin = std::min<int32_t>(available_width / 16, total_button_size / 2);
} else {
const int32_t available_height = std::max<int32_t>(0, lv_display_get_vertical_resolution(display) - (3 * button_size));
margin = std::min<int32_t>(available_height / 16, button_size);
const int32_t available_height = std::max<int32_t>(0, lv_display_get_vertical_resolution(display) - (3 * total_button_size));
margin = std::min<int32_t>(available_height / 16, total_button_size / 2);
}
createAppButton(buttons_wrapper, ui_scale, LVGL_SYMBOL_APPS, "AppList", margin, is_landscape_display);
createAppButton(buttons_wrapper, ui_scale, LVGL_SYMBOL_FOLDER, "Files", margin, is_landscape_display);
createAppButton(buttons_wrapper, ui_scale, LVGL_SYMBOL_SETTINGS, "Settings", margin, is_landscape_display);
createAppButton(buttons_wrapper, ui_density, LVGL_SYMBOL_APPS, "AppList", margin, is_landscape_display);
createAppButton(buttons_wrapper, ui_density, LVGL_SYMBOL_FOLDER, "Files", margin, is_landscape_display);
createAppButton(buttons_wrapper, ui_density, LVGL_SYMBOL_SETTINGS, "Settings", margin, is_landscape_display);
if (shouldShowPowerButton()) {
auto* power_button = lv_btn_create(parent);
@@ -1,9 +1,10 @@
#include <Tactility/Tactility.h>
#include <Tactility/RecursiveMutex.h>
#include <Tactility/StringUtils.h>
#include <Tactility/app/localesettings/TextResources.h>
#include <Tactility/lvgl/Toolbar.h>
#include <Tactility/service/loader/Loader.h>
#include <Tactility/settings/Time.h>
#include <Tactility/StringUtils.h>
#include <Tactility/settings/Language.h>
#include <Tactility/settings/SystemSettings.h>
@@ -85,8 +86,6 @@ class LocaleSettingsApp final : public App {
public:
void onShow(AppContext& app, lv_obj_t* parent) override {
auto ui_scale = hal::getConfiguration()->uiScale;
textResources.load();
lv_obj_set_flex_flow(parent, LV_FLEX_FLOW_COLUMN);
@@ -1,19 +1,19 @@
#include <Tactility/Tactility.h>
#include <Tactility/TactilityConfig.h>
#include <Tactility/Timer.h>
#include <Tactility/kernel/Kernel.h>
#if TT_FEATURE_SCREENSHOT_ENABLED
#include <Tactility/app/App.h>
#include <Tactility/app/AppManifest.h>
#include <Tactility/hal/sdcard/SdCardDevice.h>
#include <Tactility/kernel/Platform.h>
#include <Tactility/Logger.h>
#include <Tactility/lvgl/Lvgl.h>
#include <Tactility/lvgl/LvglSync.h>
#include <Tactility/lvgl/Toolbar.h>
#include <Tactility/service/screenshot/Screenshot.h>
#include <Tactility/Timer.h>
#include <tactility/lvgl_symbols_shared.h>
+1 -1
View File
@@ -23,7 +23,7 @@ static void createWidget(const std::shared_ptr<AppManifest>& manifest, void* par
const void* icon = !manifest->appIcon.empty() ? manifest->appIcon.c_str() : LVGL_SYMBOL_TOOLBAR;
auto* btn = lv_list_add_button(list, icon, manifest->appName.c_str());
lv_obj_t* image = lv_obj_get_child(btn, 0);
lv_obj_set_style_text_font(image, LVGL_SYMBOL_FONT_DEFAULT, LV_PART_MAIN);
lv_obj_set_style_text_font(image, lvgl_get_shared_icon_font(), LV_PART_MAIN);
lv_obj_add_event_cb(btn, &onAppPressed, LV_EVENT_SHORT_CLICKED, (void*)manifest.get());
}
+9 -11
View File
@@ -1,15 +1,16 @@
#include <Tactility/TactilityConfig.h>
#include <Tactility/lvgl/Toolbar.h>
#include <Tactility/lvgl/LvglSync.h>
#include <Tactility/lvgl/Toolbar.h>
#include <Tactility/Assets.h>
#include <tactility/hal/Device.h>
#include <Tactility/hal/sdcard/SdCardDevice.h>
#include <Tactility/Tactility.h>
#include <Tactility/Timer.h>
#include <algorithm>
#include <format>
#include <lvgl.h>
#include <tactility/lvgl_fonts.h>
#include <tactility/hal/Device.h>
#include <tactility/lvgl_symbols_shared.h>
#include <utility>
#include <cstring>
@@ -139,7 +140,8 @@ static MemoryBarWidgets createMemoryBar(lv_obj_t* parent, const char* label) {
auto* left_label = lv_label_create(container);
lv_label_set_text(left_label, label);
lv_obj_set_width(left_label, 60);
auto label_width = 6 * lvgl_get_text_font_height(FONT_SIZE_DEFAULT);
lv_obj_set_width(left_label, label_width);
auto* bar = lv_bar_create(container);
lv_obj_set_flex_grow(bar, 1);
@@ -148,7 +150,7 @@ static MemoryBarWidgets createMemoryBar(lv_obj_t* parent, const char* label) {
lv_obj_set_width(bottom_label, LV_PCT(100));
lv_obj_set_style_text_align(bottom_label, LV_TEXT_ALIGN_RIGHT, 0);
if (hal::getConfiguration()->uiScale == hal::UiScale::Smallest) {
if (hal::getConfiguration()->uiDensity == hal::UiDensity::Compact) {
lv_obj_set_style_pad_bottom(bottom_label, 2, LV_STATE_DEFAULT);
} else {
lv_obj_set_style_pad_bottom(bottom_label, 12, LV_STATE_DEFAULT);
@@ -436,7 +438,6 @@ class SystemInfoApp final : public App {
// Summary
auto* summary_label = lv_label_create(psramContainer);
lv_label_set_text(summary_label, "PSRAM Usage Summary");
lv_obj_set_style_text_font(summary_label, &lv_font_montserrat_14, 0);
lv_obj_set_style_pad_bottom(summary_label, 8, 0);
// Current usage
@@ -477,7 +478,6 @@ class SystemInfoApp final : public App {
// PSRAM Configuration section
auto* config_header = lv_label_create(psramContainer);
lv_label_set_text(config_header, "PSRAM Configuration");
lv_obj_set_style_text_font(config_header, &lv_font_montserrat_14, 0);
lv_obj_set_style_pad_bottom(config_header, 8, 0);
// Get threshold from sdkconfig
@@ -514,7 +514,6 @@ class SystemInfoApp final : public App {
// Known PSRAM consumers header
auto* consumers_label = lv_label_create(psramContainer);
lv_label_set_text(consumers_label, "PSRAM Allocation Strategy");
lv_obj_set_style_text_font(consumers_label, &lv_font_montserrat_14, 0);
lv_obj_set_style_pad_bottom(consumers_label, 8, 0);
// Explain what's in PSRAM
@@ -554,7 +553,6 @@ class SystemInfoApp final : public App {
// App behavior explanation
auto* app_behavior_label = lv_label_create(psramContainer);
lv_label_set_text(app_behavior_label, "App Memory Behavior");
lv_obj_set_style_text_font(app_behavior_label, &lv_font_montserrat_14, 0);
lv_obj_set_style_pad_bottom(app_behavior_label, 8, 0);
auto* app_note1 = lv_label_create(psramContainer);
@@ -585,7 +583,8 @@ class SystemInfoApp final : public App {
auto* tabview = lv_tabview_create(wrapper);
lv_tabview_set_tab_bar_position(tabview, LV_DIR_LEFT);
lv_tabview_set_tab_bar_size(tabview, 80);
auto tab_bar_width = 6 * lvgl_get_text_font_height(FONT_SIZE_DEFAULT);
lv_tabview_set_tab_bar_size(tabview, tab_bar_width);
// Create tabs
auto* memory_tab = createTab(tabview, "Memory");
@@ -645,7 +644,6 @@ class SystemInfoApp final : public App {
// CPU tab - summary at top
cpuSummaryLabel = lv_label_create(cpu_tab);
lv_label_set_text(cpuSummaryLabel, "Overall CPU Usage: --.-%");
lv_obj_set_style_text_font(cpuSummaryLabel, &lv_font_montserrat_14, 0);
lv_obj_set_style_pad_bottom(cpuSummaryLabel, 4, 0);
taskCountLabel = lv_label_create(cpu_tab);
+10 -10
View File
@@ -1,16 +1,18 @@
#include <tactility/lvgl_fonts.h>
#include <tactility/lvgl_symbols_shared.h>
#include <Tactility/app/AppContext.h>
#include <Tactility/app/AppManifest.h>
#include <Tactility/app/AppPaths.h>
#include <Tactility/app/timezone/TimeZone.h>
#include <Tactility/Logger.h>
#include <Tactility/LogMessages.h>
#include <Tactility/lvgl/Lvgl.h>
#include <Tactility/lvgl/Toolbar.h>
#include <Tactility/lvgl/LvglSync.h>
#include <Tactility/Logger.h>
#include <Tactility/MountPoints.h>
#include <Tactility/service/loader/Loader.h>
#include <Tactility/StringUtils.h>
#include <Tactility/Timer.h>
#include <Tactility/lvgl/LvglSync.h>
#include <Tactility/lvgl/Toolbar.h>
#include <Tactility/service/loader/Loader.h>
#include <lvgl.h>
#include <memory>
@@ -202,10 +204,8 @@ public:
lv_obj_set_style_margin_left(icon, 8, 0);
lv_obj_set_style_image_recolor_opa(icon, 255, 0);
lv_obj_set_style_image_recolor(icon, lv_theme_get_color_primary(parent), 0);
std::string icon_path = lvgl::PATH_PREFIX + app.getPaths()->getAssetsPath("search.png");
lv_image_set_src(icon, icon_path.c_str());
lv_obj_set_style_image_recolor(icon, lv_theme_get_color_primary(parent), 0);
lv_obj_set_style_text_font(icon, lvgl_get_shared_icon_font(), LV_STATE_DEFAULT);
lv_image_set_src(icon, LVGL_SYMBOL_SEARCH);
auto* textarea = lv_textarea_create(search_wrapper);
lv_textarea_set_placeholder_text(textarea, "e.g. Europe/Amsterdam");
@@ -109,7 +109,7 @@ class TrackballSettingsApp final : public App {
public:
void onShow(AppContext& app, lv_obj_t* parent) override {
tbSettings = settings::trackball::loadOrGetDefault();
auto ui_scale = hal::getConfiguration()->uiScale;
auto ui_density = hal::getConfiguration()->uiDensity;
updated = false;
lv_obj_set_flex_flow(parent, LV_FLEX_FLOW_COLUMN);
@@ -154,7 +154,7 @@ public:
lv_obj_set_size(enc_sens_wrapper, LV_PCT(100), LV_SIZE_CONTENT);
lv_obj_set_style_pad_hor(enc_sens_wrapper, 0, LV_STATE_DEFAULT);
lv_obj_set_style_border_width(enc_sens_wrapper, 0, LV_STATE_DEFAULT);
if (ui_scale != hal::UiScale::Smallest) {
if (ui_density != hal::UiDensity::Compact) {
lv_obj_set_style_pad_ver(enc_sens_wrapper, 4, LV_STATE_DEFAULT);
}
@@ -178,7 +178,7 @@ public:
lv_obj_set_size(ptr_sens_wrapper, LV_PCT(100), LV_SIZE_CONTENT);
lv_obj_set_style_pad_hor(ptr_sens_wrapper, 0, LV_STATE_DEFAULT);
lv_obj_set_style_border_width(ptr_sens_wrapper, 0, LV_STATE_DEFAULT);
if (ui_scale != hal::UiScale::Smallest) {
if (ui_density != hal::UiDensity::Compact) {
lv_obj_set_style_pad_ver(ptr_sens_wrapper, 4, LV_STATE_DEFAULT);
}
+2 -1
View File
@@ -7,6 +7,7 @@
#include <Tactility/lvgl/Toolbar.h>
#include <Tactility/service/wifi/Wifi.h>
#include <Tactility/service/wifi/WifiSettings.h>
#include <Tactility/Tactility.h>
#include <format>
#include <string>
@@ -161,7 +162,7 @@ void View::updateNetworkList() {
lv_obj_add_event_cb(enable_on_boot_switch, onEnableOnBootSwitchChanged, LV_EVENT_VALUE_CHANGED, bindings);
lv_obj_add_event_cb(enable_on_boot_wrapper, onEnableOnBootParentClicked, LV_EVENT_SHORT_CLICKED, enable_on_boot_switch);
if (hal::getConfiguration()->uiScale == hal::UiScale::Smallest) {
if (hal::getConfiguration()->uiDensity == hal::UiDensity::Compact) {
lv_obj_set_style_pad_ver(enable_on_boot_wrapper, 2, LV_STATE_DEFAULT);
} else {
lv_obj_set_style_pad_ver(enable_on_boot_wrapper, 8, LV_STATE_DEFAULT);
+19 -6
View File
@@ -6,13 +6,15 @@
#include <Tactility/RecursiveMutex.h>
#include <Tactility/Tactility.h>
#include <Tactility/Timer.h>
#include <tactility/check.h>
#include <Tactility/kernel/SystemEvents.h>
#include <Tactility/lvgl/LvglSync.h>
#include <Tactility/lvgl/Statusbar.h>
#include <Tactility/lvgl/Style.h>
#include <Tactility/settings/Time.h>
#include <tactility/check.h>
#include <tactility/lvgl_fonts.h>
#include <lvgl.h>
namespace tt::lvgl {
@@ -88,7 +90,7 @@ static void onUpdateTime() {
}
}
static const lv_obj_class_t statusbar_class = {
static lv_obj_class_t statusbar_class = {
.base_class = &lv_obj_class,
.constructor_cb = &statusbar_constructor,
.destructor_cb = &statusbar_destructor,
@@ -96,7 +98,7 @@ static const lv_obj_class_t statusbar_class = {
.user_data = nullptr,
.name = nullptr,
.width_def = LV_PCT(100),
.height_def = STATUSBAR_HEIGHT,
.height_def = 20,
.editable = false,
.group_def = LV_OBJ_CLASS_GROUP_DEF_TRUE,
.instance_size = sizeof(Statusbar),
@@ -157,10 +159,11 @@ static void update_icon(lv_obj_t* image, const StatusbarIcon* icon) {
}
lv_obj_t* statusbar_create(lv_obj_t* parent) {
statusbar_class.height_def = statusbar_get_height();
lv_obj_t* obj = lv_obj_class_create_obj(&statusbar_class, parent);
lv_obj_class_init_obj(obj);
auto* statusbar = (Statusbar*)obj;
auto* statusbar = reinterpret_cast<Statusbar*>(obj);
lv_obj_set_width(obj, LV_PCT(100));
lv_obj_set_style_pad_ver(obj, 0, LV_STATE_DEFAULT);
@@ -168,6 +171,10 @@ lv_obj_t* statusbar_create(lv_obj_t* parent) {
lv_obj_center(obj);
lv_obj_set_flex_flow(obj, LV_FLEX_FLOW_ROW);
lv_obj_set_flex_align(obj, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);
auto icon_size = lvgl_get_statusbar_icon_font_height();
auto ui_density = hal::getConfiguration()->uiDensity;
auto icon_padding = (ui_density != hal::UiDensity::Compact) ? static_cast<uint32_t>(icon_size * 0.2f) : 2;
lv_obj_set_style_pad_column(obj, icon_padding, LV_STATE_DEFAULT);
statusbar->time = lv_label_create(obj);
lv_obj_set_style_text_color(statusbar->time, lv_color_white(), LV_STATE_DEFAULT);
@@ -182,7 +189,8 @@ lv_obj_t* statusbar_create(lv_obj_t* parent) {
statusbar_data.mutex.lock(kernel::MAX_TICKS);
for (int i = 0; i < STATUSBAR_ICON_LIMIT; ++i) {
auto* image = lv_image_create(obj);
lv_obj_set_size(image, STATUSBAR_ICON_SIZE, STATUSBAR_ICON_SIZE);
lv_obj_set_size(image, icon_size, icon_size); // regular padding doesn't work
lv_obj_set_style_text_font(image, lvgl_get_statusbar_icon_font(), LV_STATE_DEFAULT);
lv_obj_set_style_pad_all(image, 0, LV_STATE_DEFAULT);
obj_set_style_bg_blacken(image);
statusbar->icons[i] = image;
@@ -190,7 +198,6 @@ lv_obj_t* statusbar_create(lv_obj_t* parent) {
update_icon(image, &(statusbar_data.icons[i]));
}
statusbar_data.mutex.unlock();
return obj;
}
@@ -298,4 +305,10 @@ void statusbar_icon_set_visibility(int8_t id, bool visible) {
statusbar_data.pubsub->publish(nullptr);
}
int statusbar_get_height() {
const auto icon_size = lvgl_get_statusbar_icon_font_height();
const auto vertical_padding = static_cast<uint32_t>((static_cast<float>(icon_size) * 0.1f));
return icon_size + (2 * vertical_padding);
}
} // namespace
+61 -49
View File
@@ -1,40 +1,54 @@
#define LV_USE_PRIVATE_API 1 // For actual lv_obj_t declaration
#include <Tactility/Tactility.h>
#include <Tactility/lvgl/Toolbar.h>
#include <tactility/check.h>
#include <Tactility/lvgl/Spinner.h>
#include <Tactility/service/loader/Loader.h>
#include <tactility/check.h>
#include <tactility/lvgl_fonts.h>
namespace tt::lvgl {
static int getToolbarHeight(hal::UiScale uiScale) {
if (uiScale == hal::UiScale::Smallest) {
return 22;
static uint32_t getToolbarHeight(hal::UiDensity uiDensity) {
if (uiDensity == hal::UiDensity::Compact) {
return lvgl_get_text_font_height(FONT_SIZE_DEFAULT) * 1.4f;
} else {
return 40;
return lvgl_get_text_font_height(FONT_SIZE_LARGE) * 2.2f;
}
}
static const _lv_font_t* getToolbarFont(hal::UiScale uiScale) {
if (uiScale == hal::UiScale::Smallest) {
return &lv_font_montserrat_14;
static const _lv_font_t* getToolbarFont(hal::UiDensity uiDensity) {
if (uiDensity == hal::UiDensity::Compact) {
return lvgl_get_text_font(FONT_SIZE_DEFAULT);
} else {
return &lv_font_montserrat_18;
return lvgl_get_text_font(FONT_SIZE_LARGE);
}
}
static uint32_t getActionIconPadding(hal::UiDensity ui_density) {
auto toolbar_height = getToolbarHeight(ui_density);
// Minimal 8 pixels total padding for selection/animation (4+4 pixels)
return (ui_density != hal::UiDensity::Compact) ? (uint32_t)(toolbar_height * 0.2f) : 8;
}
/**
* Helps with button expansion and also with vertical alignment of content,
* as the parent flex doesn't allow for vertical alignment
*/
static lv_obj_t* create_action_wrapper(lv_obj_t* parent) {
static lv_obj_t* create_action_wrapper(lv_obj_t* parent, hal::UiDensity ui_density) {
auto* wrapper = lv_obj_create(parent);
lv_obj_set_size(wrapper, LV_SIZE_CONTENT, getToolbarHeight(hal::getConfiguration()->uiScale));
lv_obj_set_style_pad_all(wrapper, 2, LV_STATE_DEFAULT); // For selection / click expansion
auto toolbar_height = getToolbarHeight(ui_density);
lv_obj_set_size(wrapper, LV_SIZE_CONTENT, toolbar_height);
auto icon_padding = getActionIconPadding(ui_density);
auto icon_padding_half = icon_padding / 2;
lv_obj_set_style_pad_all(wrapper, icon_padding_half, LV_STATE_DEFAULT); // For selection and touch animation
lv_obj_set_style_bg_opa(wrapper, 0, LV_STATE_DEFAULT);
lv_obj_set_style_border_width(wrapper, 0, LV_STATE_DEFAULT);
lv_obj_set_style_border_opa(wrapper, 0, LV_STATE_DEFAULT);
return wrapper;
}
@@ -49,7 +63,7 @@ typedef struct {
static void toolbar_constructor(const lv_obj_class_t* class_p, lv_obj_t* obj);
static const lv_obj_class_t toolbar_class = {
static lv_obj_class_t toolbar_class = {
.base_class = &lv_obj_class,
.constructor_cb = &toolbar_constructor,
.destructor_cb = nullptr,
@@ -57,7 +71,7 @@ static const lv_obj_class_t toolbar_class = {
.user_data = nullptr,
.name = nullptr,
.width_def = LV_PCT(100),
.height_def = LV_SIZE_CONTENT,
.height_def = 0,
.editable = false,
.group_def = LV_OBJ_CLASS_GROUP_DEF_TRUE,
.instance_size = sizeof(Toolbar),
@@ -70,16 +84,14 @@ static void stop_app(lv_event_t* event) {
static void toolbar_constructor(const lv_obj_class_t* class_p, lv_obj_t* obj) {
LV_UNUSED(class_p);
LV_TRACE_OBJ_CREATE("begin");
lv_obj_remove_flag(obj, LV_OBJ_FLAG_SCROLLABLE);
lv_obj_add_flag(obj, LV_OBJ_FLAG_SCROLL_ON_FOCUS);
LV_TRACE_OBJ_CREATE("finished");
}
lv_obj_t* toolbar_create(lv_obj_t* parent, const std::string& title) {
LV_LOG_INFO("begin");
auto ui_scale = hal::getConfiguration()->uiScale;
auto toolbar_height = getToolbarHeight(ui_scale);
auto ui_density = hal::getConfiguration()->uiDensity;
auto toolbar_height = getToolbarHeight(ui_density);
toolbar_class.height_def = toolbar_height;
lv_obj_t* obj = lv_obj_class_create_obj(&toolbar_class, parent);
lv_obj_class_init_obj(obj);
lv_obj_set_height(obj, toolbar_height);
@@ -87,44 +99,40 @@ lv_obj_t* toolbar_create(lv_obj_t* parent, const std::string& title) {
auto* toolbar = reinterpret_cast<Toolbar*>(obj);
lv_obj_set_width(obj, LV_PCT(100));
lv_obj_set_style_pad_all(obj, 0, LV_STATE_DEFAULT);
lv_obj_set_style_pad_column(obj, 0, LV_STATE_DEFAULT);
lv_obj_center(obj);
lv_obj_set_flex_flow(obj, LV_FLEX_FLOW_ROW);
auto* close_button_wrapper = lv_obj_create(obj);
lv_obj_set_size(close_button_wrapper, LV_SIZE_CONTENT, toolbar_height);
lv_obj_set_style_pad_all(close_button_wrapper, 2, LV_STATE_DEFAULT);
lv_obj_set_style_border_width(close_button_wrapper, 0, LV_STATE_DEFAULT);
lv_obj_set_style_bg_opa(close_button_wrapper, 0, LV_STATE_DEFAULT);
auto icon_padding = getActionIconPadding(ui_density);
auto* close_button_wrapper = create_action_wrapper(obj, ui_density);
toolbar->close_button = lv_button_create(close_button_wrapper);
if (ui_scale == hal::UiScale::Smallest) {
lv_obj_set_size(toolbar->close_button, toolbar_height - 8, toolbar_height - 8);
} else {
lv_obj_set_size(toolbar->close_button, toolbar_height - 6, toolbar_height - 6);
if (ui_density == hal::UiDensity::Compact) {
lv_obj_set_style_bg_opa(toolbar->close_button, LV_OPA_TRANSP, LV_STATE_DEFAULT);
}
lv_obj_set_size(toolbar->close_button, toolbar_height - icon_padding, toolbar_height - icon_padding);
lv_obj_set_style_pad_all(toolbar->close_button, 0, LV_STATE_DEFAULT);
lv_obj_align(toolbar->close_button, LV_ALIGN_CENTER, 0, 0);
toolbar->close_button_image = lv_image_create(toolbar->close_button);
lv_obj_align(toolbar->close_button_image, LV_ALIGN_CENTER, 0, 0);
auto* title_wrapper = lv_obj_create(obj);
uint32_t title_left_padding = (ui_density != hal::UiDensity::Compact) ? icon_padding : 2;
uint32_t title_right_padding = (ui_density != hal::UiDensity::Compact) ? (icon_padding / 2) : 2;
lv_obj_set_size(title_wrapper, LV_SIZE_CONTENT, LV_PCT(100));
lv_obj_set_style_bg_opa(title_wrapper, 0, LV_STATE_DEFAULT);
lv_obj_set_style_pad_all(title_wrapper, 0, LV_STATE_DEFAULT);
lv_obj_set_style_pad_left(title_wrapper, title_left_padding, LV_STATE_DEFAULT);
lv_obj_set_style_pad_right(title_wrapper, title_right_padding, LV_STATE_DEFAULT);
lv_obj_set_style_pad_ver(title_wrapper, 0, LV_STATE_DEFAULT);
lv_obj_set_style_border_width(title_wrapper, 0, LV_STATE_DEFAULT);
lv_obj_set_flex_grow(title_wrapper, 1);
if (ui_scale == hal::UiScale::Smallest) {
lv_obj_set_style_pad_left(title_wrapper, 4, LV_STATE_DEFAULT);
} else {
lv_obj_set_style_pad_left(title_wrapper, 8, LV_STATE_DEFAULT);
}
toolbar->title_label = lv_label_create(title_wrapper);
lv_obj_set_style_text_font(toolbar->title_label, getToolbarFont(ui_scale), LV_STATE_DEFAULT);
lv_obj_set_style_text_font(toolbar->title_label, getToolbarFont(ui_density), LV_STATE_DEFAULT);
lv_label_set_text(toolbar->title_label, title.c_str());
lv_label_set_long_mode(toolbar->title_label, LV_LABEL_LONG_MODE_SCROLL);
lv_obj_set_style_text_align(toolbar->title_label, LV_TEXT_ALIGN_LEFT, LV_STATE_DEFAULT);
@@ -135,6 +143,7 @@ lv_obj_t* toolbar_create(lv_obj_t* parent, const std::string& title) {
lv_obj_set_width(toolbar->action_container, LV_SIZE_CONTENT);
lv_obj_set_flex_flow(toolbar->action_container, LV_FLEX_FLOW_ROW);
lv_obj_set_style_pad_all(toolbar->action_container, 0, LV_STATE_DEFAULT);
lv_obj_set_style_pad_column(toolbar->action_container, 0, LV_STATE_DEFAULT);
lv_obj_set_style_border_width(toolbar->action_container, 0, LV_STATE_DEFAULT);
lv_obj_set_style_bg_opa(toolbar->action_container, 0, LV_STATE_DEFAULT);
@@ -170,19 +179,20 @@ lv_obj_t* toolbar_add_button_action(lv_obj_t* obj, const char* imageOrButton, bo
check(toolbar->action_count < TOOLBAR_ACTION_LIMIT, "max actions reached");
toolbar->action_count++;
auto ui_scale = hal::getConfiguration()->uiScale;
auto toolbar_height = getToolbarHeight(ui_scale);
auto ui_density = hal::getConfiguration()->uiDensity;
auto toolbar_height = getToolbarHeight(ui_density);
auto* wrapper = create_action_wrapper(toolbar->action_container);
auto* wrapper = create_action_wrapper(toolbar->action_container, ui_density);
auto* action_button = lv_button_create(wrapper);
if (ui_scale == hal::UiScale::Smallest) {
lv_obj_set_size(action_button, toolbar_height - 8, toolbar_height - 8);
} else {
lv_obj_set_size(action_button, toolbar_height - 6, toolbar_height - 6);
}
auto padding = getActionIconPadding(ui_density);
lv_obj_t* action_button = lv_button_create(wrapper);
lv_obj_set_size(action_button, toolbar_height - padding, toolbar_height - padding);
lv_obj_set_style_pad_all(action_button, 0, LV_STATE_DEFAULT);
lv_obj_align(action_button, LV_ALIGN_CENTER, 0, 0);
if (ui_density == hal::UiDensity::Compact) {
lv_obj_set_style_bg_opa(action_button, LV_OPA_TRANSP, LV_STATE_DEFAULT);
}
lv_obj_add_event_cb(action_button, callback, LV_EVENT_SHORT_CLICKED, user_data);
lv_obj_t* button_content;
@@ -209,7 +219,8 @@ lv_obj_t* toolbar_add_text_button_action(lv_obj_t* obj, const char* text, lv_eve
lv_obj_t* toolbar_add_switch_action(lv_obj_t* obj) {
auto* toolbar = reinterpret_cast<Toolbar*>(obj);
auto* wrapper = create_action_wrapper(toolbar->action_container);
auto ui_density = hal::getConfiguration()->uiDensity;
auto* wrapper = create_action_wrapper(toolbar->action_container, ui_density);
lv_obj_set_style_pad_hor(wrapper, 4, LV_STATE_DEFAULT);
lv_obj_t* widget = lv_switch_create(wrapper);
@@ -220,7 +231,8 @@ lv_obj_t* toolbar_add_switch_action(lv_obj_t* obj) {
lv_obj_t* toolbar_add_spinner_action(lv_obj_t* obj) {
auto* toolbar = reinterpret_cast<Toolbar*>(obj);
auto* wrapper = create_action_wrapper(toolbar->action_container);
auto ui_density = hal::getConfiguration()->uiDensity;
auto* wrapper = create_action_wrapper(toolbar->action_container, ui_density);
auto* spinner = spinner_create(wrapper);
lv_obj_set_align(spinner, LV_ALIGN_CENTER);
+1 -1
View File
@@ -11,7 +11,7 @@ extern lv_obj_t* __real_lv_button_create(lv_obj_t* parent);
lv_obj_t* __wrap_lv_button_create(lv_obj_t* parent) {
auto button = __real_lv_button_create(parent);
if (tt::hal::getConfiguration()->uiScale == tt::hal::UiScale::Smallest) {
if (tt::hal::getConfiguration()->uiDensity == tt::hal::UiDensity::Compact) {
lv_obj_set_style_pad_all(button, 2, LV_STATE_DEFAULT);
lv_obj_set_style_radius(button, 3, LV_STATE_DEFAULT);
}
+1 -1
View File
@@ -11,7 +11,7 @@ extern lv_obj_t* __real_lv_dropdown_create(lv_obj_t* parent);
lv_obj_t* __wrap_lv_dropdown_create(lv_obj_t* parent) {
auto dropdown = __real_lv_dropdown_create(parent);
if (tt::hal::getConfiguration()->uiScale == tt::hal::UiScale::Smallest) {
if (tt::hal::getConfiguration()->uiDensity == tt::hal::UiDensity::Compact) {
lv_obj_set_style_pad_all(dropdown, 2, LV_STATE_DEFAULT);
}
+2 -2
View File
@@ -12,7 +12,7 @@ extern lv_obj_t* __real_lv_list_add_button(lv_obj_t* list, const void* icon, con
lv_obj_t* __wrap_lv_list_create(lv_obj_t* parent) {
auto* list = __real_lv_list_create(parent);
if (tt::hal::getConfiguration()->uiScale == tt::hal::UiScale::Smallest) {
if (tt::hal::getConfiguration()->uiDensity == tt::hal::UiDensity::Compact) {
lv_obj_set_style_pad_row(list, 2, LV_STATE_DEFAULT);
lv_obj_set_style_pad_column(list, 2, LV_STATE_DEFAULT);
lv_obj_set_style_pad_all(list, 2, LV_STATE_DEFAULT);
@@ -24,7 +24,7 @@ lv_obj_t* __wrap_lv_list_create(lv_obj_t* parent) {
lv_obj_t* __wrap_lv_list_add_button(lv_obj_t* list, const void* icon, const char* txt) {
auto* button = __real_lv_list_add_button(list, icon, txt);
if (tt::hal::getConfiguration()->uiScale == tt::hal::UiScale::Smallest) {
if (tt::hal::getConfiguration()->uiDensity == tt::hal::UiDensity::Compact) {
lv_obj_set_style_pad_ver(button, 2, LV_STATE_DEFAULT);
}
+2 -2
View File
@@ -12,14 +12,14 @@ extern lv_obj_t* __real_lv_obj_create(lv_obj_t* parent);
void __wrap_lv_obj_set_flex_flow(lv_obj_t* obj, lv_flex_flow_t flow) {
__real_lv_obj_set_flex_flow(obj, flow);
if (tt::hal::getConfiguration()->uiScale == tt::hal::UiScale::Smallest) {
if (tt::hal::getConfiguration()->uiDensity == tt::hal::UiDensity::Compact) {
lv_obj_set_style_pad_gap(obj, 4, LV_STATE_DEFAULT);
}
}
lv_obj_t* __wrap_lv_obj_create(lv_obj_t* parent) {
auto obj = __real_lv_obj_create(parent);
if (tt::hal::getConfiguration()->uiScale == tt::hal::UiScale::Smallest) {
if (tt::hal::getConfiguration()->uiDensity == tt::hal::UiDensity::Compact) {
lv_obj_set_style_pad_all(obj, 2, LV_STATE_DEFAULT);
lv_obj_set_style_pad_gap(obj, 2, LV_STATE_DEFAULT);
lv_obj_set_style_radius(obj, 3, LV_STATE_DEFAULT);
+1 -1
View File
@@ -11,7 +11,7 @@ extern lv_obj_t* __real_lv_switch_create(lv_obj_t* parent);
lv_obj_t* __wrap_lv_switch_create(lv_obj_t* parent) {
auto widget = __real_lv_switch_create(parent);
if (tt::hal::getConfiguration()->uiScale == tt::hal::UiScale::Smallest) {
if (tt::hal::getConfiguration()->uiDensity == tt::hal::UiDensity::Compact) {
lv_obj_set_style_size(widget, 25, 15, LV_STATE_DEFAULT);
}
+1 -1
View File
@@ -11,7 +11,7 @@ extern lv_obj_t* __real_lv_textarea_create(lv_obj_t* parent);
lv_obj_t* __wrap_lv_textarea_create(lv_obj_t* parent) {
auto textarea = __real_lv_textarea_create(parent);
if (tt::hal::getConfiguration()->uiScale == tt::hal::UiScale::Smallest) {
if (tt::hal::getConfiguration()->uiDensity == tt::hal::UiDensity::Compact) {
lv_obj_set_style_pad_all(textarea, 2, LV_STATE_DEFAULT);
}
+10 -10
View File
@@ -35,16 +35,16 @@ void download(
auto certificate_length = strlen(reinterpret_cast<const char*>(certificate.get())) + 1;
// TODO: Fix for missing initializer warnings
auto config = std::make_unique<esp_http_client_config_t>(esp_http_client_config_t {
.url = url.c_str(),
.auth_type = HTTP_AUTH_TYPE_NONE,
.cert_pem = reinterpret_cast<const char*>(certificate.get()),
.cert_len = certificate_length,
.tls_version = ESP_HTTP_CLIENT_TLS_VER_TLS_1_3,
.method = HTTP_METHOD_GET,
.timeout_ms = 5000,
.transport_type = HTTP_TRANSPORT_OVER_SSL
});
auto config = std::make_unique<esp_http_client_config_t>();
memset(config.get(), 0, sizeof(esp_http_client_config_t));
config->url = url.c_str();
config->auth_type = HTTP_AUTH_TYPE_NONE;
config->cert_pem = reinterpret_cast<const char*>(certificate.get());
config->cert_len = certificate_length;
config->tls_version = ESP_HTTP_CLIENT_TLS_VER_TLS_1_3;
config->method = HTTP_METHOD_GET;
config->timeout_ms = 5000;
config->transport_type = HTTP_TRANSPORT_OVER_SSL;
auto client = std::make_unique<EspHttpClient>();
if (!client->init(std::move(config))) {
@@ -1,12 +1,11 @@
#include <Tactility/Tactility.h>
#include <Tactility/Logger.h>
#include <Tactility/lvgl/Statusbar.h>
#include <Tactility/service/ServiceContext.h>
#include <Tactility/service/ServiceManifest.h>
#include <Tactility/service/ServicePaths.h>
#include <Tactility/service/memorychecker/MemoryCheckerService.h>
#include <tactility/lvgl_symbols_statusbar.h>
namespace tt::service::memorychecker {
static const auto LOGGER = Logger("MemoryChecker");
@@ -55,8 +54,7 @@ bool MemoryCheckerService::onStart(ServiceContext& service) {
auto lock = mutex.asScopedLock();
lock.lock();
auto icon_path = std::string("A:") + service.getPaths()->getAssetsPath("memory_alert.png");
statusbarIconId = lvgl::statusbar_icon_add(icon_path, false);
statusbarIconId = lvgl::statusbar_icon_add(LVGL_SYMBOL_MEMORY, false);
lvgl::statusbar_icon_set_visibility(statusbarIconId, false);
timer.setCallbackPriority(Thread::Priority::Lower);
@@ -14,60 +14,40 @@
#include <Tactility/service/gps/GpsService.h>
#include <Tactility/service/wifi/Wifi.h>
#include <tactility/lvgl_symbols_statusbar.h>
namespace tt::service::statusbar {
static const auto LOGGER = Logger("StatusbarService");
// SD card status
constexpr auto* STATUSBAR_ICON_SDCARD = "sdcard.png";
constexpr auto* STATUSBAR_ICON_SDCARD_ALERT = "sdcard_alert.png";
// Wifi status
constexpr auto* STATUSBAR_ICON_WIFI_OFF_WHITE = "wifi_off_white.png";
constexpr auto* STATUSBAR_ICON_WIFI_SCAN_WHITE = "wifi_scan_white.png";
constexpr auto* STATUSBAR_ICON_WIFI_SIGNAL_WEAK_WHITE = "wifi_signal_weak_white.png";
constexpr auto* STATUSBAR_ICON_WIFI_SIGNAL_MEDIUM_WHITE = "wifi_signal_medium_white.png";
constexpr auto* STATUSBAR_ICON_WIFI_SIGNAL_STRONG_WHITE = "wifi_signal_strong_white.png";
// Power status
constexpr auto* STATUSBAR_ICON_POWER_0 = "power_0.png";
constexpr auto* STATUSBAR_ICON_POWER_10 = "power_10.png";
constexpr auto* STATUSBAR_ICON_POWER_20 = "power_20.png";
constexpr auto* STATUSBAR_ICON_POWER_30 = "power_30.png";
constexpr auto* STATUSBAR_ICON_POWER_40 = "power_40.png";
constexpr auto* STATUSBAR_ICON_POWER_50 = "power_50.png";
constexpr auto* STATUSBAR_ICON_POWER_60 = "power_60.png";
constexpr auto* STATUSBAR_ICON_POWER_70 = "power_70.png";
constexpr auto* STATUSBAR_ICON_POWER_80 = "power_80.png";
constexpr auto* STATUSBAR_ICON_POWER_90 = "power_90.png";
constexpr auto* STATUSBAR_ICON_POWER_100 = "power_100.png";
// GPS
constexpr auto* STATUSBAR_ICON_GPS = "location.png";
extern const ServiceManifest manifest;
const char* getWifiStatusIconForRssi(int rssi) {
if (rssi >= -60) {
return STATUSBAR_ICON_WIFI_SIGNAL_STRONG_WHITE;
return LVGL_SYMBOL_SIGNAL_WIFI_4_BAR;
} else if (rssi >= -70) {
return STATUSBAR_ICON_WIFI_SIGNAL_MEDIUM_WHITE;
return LVGL_SYMBOL_NETWORK_WIFI_3_BAR;
} else if (rssi >= -80) {
return LVGL_SYMBOL_NETWORK_WIFI_2_BAR;
} else if (rssi >= -90) {
return LVGL_SYMBOL_NETWORK_WIFI_1_BAR;
} else {
return STATUSBAR_ICON_WIFI_SIGNAL_WEAK_WHITE;
return LVGL_SYMBOL_SIGNAL_WIFI_BAD;
}
}
static const char* getWifiStatusIcon(wifi::RadioState state, bool secure) {
static const char* getWifiStatusIcon(wifi::RadioState state) {
int rssi;
switch (state) {
using enum wifi::RadioState;
case On:
case OnPending:
case ConnectionPending:
return STATUSBAR_ICON_WIFI_SCAN_WHITE;
return LVGL_SYMBOL_SIGNAL_WIFI_0_BAR;
case OffPending:
case Off:
return STATUSBAR_ICON_WIFI_OFF_WHITE;
return LVGL_SYMBOL_SIGNAL_WIFI_OFF;
case ConnectionActive:
rssi = wifi::getRssi();
return getWifiStatusIconForRssi(rssi);
@@ -80,11 +60,11 @@ static const char* getSdCardStatusIcon(hal::sdcard::SdCardDevice::State state) {
switch (state) {
using enum hal::sdcard::SdCardDevice::State;
case Mounted:
return STATUSBAR_ICON_SDCARD;
return LVGL_SYMBOL_SD_CARD;
case Error:
case Unmounted:
case Timeout:
return STATUSBAR_ICON_SDCARD_ALERT;
return LVGL_SYMBOL_SD_CARD_ALERT;
default:
check(false, "Unhandled SdCard state");
}
@@ -113,27 +93,19 @@ static const char* getPowerStatusIcon() {
uint8_t charge = charge_level.valueAsUint8;
if (charge >= 95) {
return STATUSBAR_ICON_POWER_100;
} else if (charge >= 85) {
return STATUSBAR_ICON_POWER_90;
} else if (charge >= 75) {
return STATUSBAR_ICON_POWER_80;
} else if (charge >= 65) {
return STATUSBAR_ICON_POWER_70;
} else if (charge >= 55) {
return STATUSBAR_ICON_POWER_60;
} else if (charge >= 45) {
return STATUSBAR_ICON_POWER_50;
} else if (charge >= 35) {
return STATUSBAR_ICON_POWER_40;
} else if (charge >= 25) {
return STATUSBAR_ICON_POWER_30;
} else if (charge >= 15) {
return STATUSBAR_ICON_POWER_20;
} else if (charge >= 5) {
return STATUSBAR_ICON_POWER_10;
return LVGL_SYMBOL_BATTERY_ANDROID_FRAME_FULL;
} else if (charge >= 80) {
return LVGL_SYMBOL_BATTERY_ANDROID_FRAME_6;
} else if (charge >= 64) {
return LVGL_SYMBOL_BATTERY_ANDROID_FRAME_5;
} else if (charge >= 48) {
return LVGL_SYMBOL_BATTERY_ANDROID_FRAME_4;
} else if (charge >= 32) {
return LVGL_SYMBOL_BATTERY_ANDROID_FRAME_3;
} else if (charge >= 16) {
return LVGL_SYMBOL_BATTERY_ANDROID_FRAME_2;
} else {
return STATUSBAR_ICON_POWER_0;
return LVGL_SYMBOL_BATTERY_ANDROID_FRAME_1;
}
}
@@ -150,8 +122,6 @@ class StatusbarService final : public Service {
int8_t power_icon_id;
const char* power_last_icon = nullptr;
std::unique_ptr<ServicePaths> paths;
void lock() const {
mutex.lock();
}
@@ -165,8 +135,7 @@ class StatusbarService final : public Service {
bool show_icon = (gps_state == gps::State::OnPending) || (gps_state == gps::State::On);
if (gps_last_state != show_icon) {
if (show_icon) {
auto icon_path = "A:" + paths->getAssetsPath(STATUSBAR_ICON_GPS);
lvgl::statusbar_icon_set_image(gps_icon_id, icon_path);
lvgl::statusbar_icon_set_image(gps_icon_id, LVGL_SYMBOL_LOCATION_ON);
lvgl::statusbar_icon_set_visibility(gps_icon_id, true);
} else {
lvgl::statusbar_icon_set_visibility(gps_icon_id, false);
@@ -177,12 +146,10 @@ class StatusbarService final : public Service {
void updateWifiIcon() {
wifi::RadioState radio_state = wifi::getRadioState();
bool is_secure = wifi::isConnectionSecure();
const char* desired_icon = getWifiStatusIcon(radio_state, is_secure);
const char* desired_icon = getWifiStatusIcon(radio_state);
if (wifi_last_icon != desired_icon) {
if (desired_icon != nullptr) {
auto icon_path = "A:" + paths->getAssetsPath(desired_icon);
lvgl::statusbar_icon_set_image(wifi_icon_id, icon_path);
lvgl::statusbar_icon_set_image(wifi_icon_id, desired_icon);
lvgl::statusbar_icon_set_visibility(wifi_icon_id, true);
} else {
lvgl::statusbar_icon_set_visibility(wifi_icon_id, false);
@@ -195,8 +162,7 @@ class StatusbarService final : public Service {
const char* desired_icon = getPowerStatusIcon();
if (power_last_icon != desired_icon) {
if (desired_icon != nullptr) {
auto icon_path = "A:" + paths->getAssetsPath(desired_icon);
lvgl::statusbar_icon_set_image(power_icon_id, icon_path);
lvgl::statusbar_icon_set_image(power_icon_id, desired_icon);
lvgl::statusbar_icon_set_visibility(power_icon_id, true);
} else {
lvgl::statusbar_icon_set_visibility(power_icon_id, false);
@@ -214,8 +180,7 @@ class StatusbarService final : public Service {
if (state != hal::sdcard::SdCardDevice::State::Timeout) {
auto* desired_icon = getSdCardStatusIcon(state);
if (sdcard_last_icon != desired_icon) {
auto icon_path = "A:" + paths->getAssetsPath(desired_icon);
lvgl::statusbar_icon_set_image(sdcard_icon_id, icon_path);
lvgl::statusbar_icon_set_image(sdcard_icon_id, desired_icon);
lvgl::statusbar_icon_set_visibility(sdcard_icon_id, true);
sdcard_last_icon = desired_icon;
}
@@ -258,8 +223,6 @@ public:
return false;
}
paths = serviceContext.getPaths();
// TODO: Make thread-safe for LVGL
lvgl::statusbar_icon_set_visibility(wifi_icon_id, true);
@@ -8,16 +8,15 @@
#include <Tactility/MountPoints.h>
#include <Tactility/file/File.h>
#include <Tactility/Logger.h>
#include <Tactility/kernel/SystemEvents.h>
#include <Tactility/lvgl/Statusbar.h>
#include <Tactility/Mutex.h>
#include <Tactility/Tactility.h>
#include <Tactility/TactilityConfig.h>
#include <tactility/hal/Device.h>
#include <Tactility/app/AppRegistration.h>
#include <Tactility/app/AppManifest.h>
#include <Tactility/app/App.h>
#include <Tactility/hal/sdcard/SdCardDevice.h>
#include <Tactility/service/wifi/Wifi.h>
#include <Tactility/network/HttpdReq.h>
#include <Tactility/network/Url.h>
@@ -32,23 +31,25 @@
#include <lv_screenshot.h>
#endif
#include <esp_system.h>
#include <esp_heap_caps.h>
#include <esp_chip_info.h>
#include <esp_vfs_fat.h>
#include <esp_flash.h>
#include <esp_wifi.h>
#include <esp_netif.h>
#include <lwip/ip4_addr.h>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <sstream>
#include <tactility/lvgl_symbols_statusbar.h>
#include <atomic>
#include <cctype>
#include <cerrno>
#include <cstring>
#include <esp_chip_info.h>
#include <esp_flash.h>
#include <esp_heap_caps.h>
#include <esp_netif.h>
#include <esp_system.h>
#include <esp_vfs_fat.h>
#include <esp_wifi.h>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <iomanip>
#include <cctype>
#include <lwip/ip4_addr.h>
#include <mbedtls/base64.h>
#include <sstream>
namespace tt::service::webserver {
@@ -519,13 +520,9 @@ bool WebServerService::startServer() {
LOGGER.info("HTTP server started successfully on port {}", settings.webServerPort);
publish_event(this, WebServerEvent::WebServerStarted);
// Show statusbar icon (different icon for AP vs Station mode)
// Show statusbar icon
if (statusbarIconId >= 0) {
const char* icon_name = (settings.wifiMode == settings::webserver::WiFiMode::AccessPoint)
? "webserver_ap_white.png"
: "webserver_station_white.png";
auto icon_path = std::string("A:/system/service/Statusbar/assets/") + icon_name;
lvgl::statusbar_icon_set_image(statusbarIconId, icon_path);
lvgl::statusbar_icon_set_image(statusbarIconId, LVGL_SYMBOL_CLOUD);
lvgl::statusbar_icon_set_visibility(statusbarIconId, true);
LOGGER.info("WebServer statusbar icon shown ({} mode)",
settings.wifiMode == settings::webserver::WiFiMode::AccessPoint ? "AP" : "Station");