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