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,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");