mirror of
https://github.com/ByteWelder/Tactility.git
synced 2026-10-17 02:45:05 +00:00
Theme settings app and memory optimisation
This commit is contained in:
parent
7638602201
commit
2522f5da71
42
Modules/lvgl-module/include/lvgl/theme.h
Normal file
42
Modules/lvgl-module/include/lvgl/theme.h
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <lvgl.h>
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/** User-configurable theme settings */
|
||||||
|
typedef struct {
|
||||||
|
bool is_dark;
|
||||||
|
/** Use the monochrome theme, also on colour displays. Monochrome and greyscale displays always use it. */
|
||||||
|
bool is_mono;
|
||||||
|
/** Slow-refresh displays (e.g. e-paper) never show animations */
|
||||||
|
bool animations_enabled;
|
||||||
|
lv_color_t color_primary;
|
||||||
|
lv_color_t color_secondary;
|
||||||
|
lv_color_t color_error;
|
||||||
|
} LvglThemeSettings;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Gets the theme settings, or the defaults when no settings were set.
|
||||||
|
* @param[out] settings the current theme settings
|
||||||
|
*/
|
||||||
|
void lvgl_theme_get_settings(LvglThemeSettings* settings);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Gets the default theme settings for this device.
|
||||||
|
* @param[out] settings the default theme settings
|
||||||
|
*/
|
||||||
|
void lvgl_theme_get_default_settings(LvglThemeSettings* settings);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Sets the theme settings. They are used for displays that are added afterwards, e.g. when LVGL (re)starts.
|
||||||
|
* @param[in] settings the theme settings
|
||||||
|
*/
|
||||||
|
void lvgl_theme_set_settings(const LvglThemeSettings* settings);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
20
Modules/lvgl-module/include/lvgl/widgets/chip.h
Normal file
20
Modules/lvgl-module/include/lvgl/widgets/chip.h
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <lvgl.h>
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Creates a chip: a compact button that shows a choice. The theme shows it as selected when it has LV_STATE_CHECKED.
|
||||||
|
* The chip doesn't toggle by itself, so the app decides which chips are selected (e.g. one of several options).
|
||||||
|
* @param[in] parent the parent object for the new chip
|
||||||
|
* @return the created chip
|
||||||
|
*/
|
||||||
|
lv_obj_t* lvgl_chip_create(lv_obj_t* parent);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
24
Modules/lvgl-module/private/lvgl/theme_private.h
Normal file
24
Modules/lvgl-module/private/lvgl/theme_private.h
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <lvgl.h>
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the theme for a display. When the display needs the other theme than the active one,
|
||||||
|
* the active theme is deinitialized first, so only one theme is allocated.
|
||||||
|
* @param[in] display the display that the theme is for
|
||||||
|
* @param[in] isMonoDisplay the display can only show monochrome or greyscale
|
||||||
|
* @param[in] supportsAnimations the display can show animations (e.g. not e-paper)
|
||||||
|
* @return the theme
|
||||||
|
*/
|
||||||
|
lv_theme_t* lvgl_theme_init_for_display(lv_display_t* display, bool isMonoDisplay, bool supportsAnimations);
|
||||||
|
|
||||||
|
/** Deinitializes the active theme. LVGL frees its memory when it stops, which includes the theme. */
|
||||||
|
void lvgl_theme_deinit(void);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
@ -13,6 +13,8 @@
|
|||||||
|
|
||||||
#include <lvgl.h>
|
#include <lvgl.h>
|
||||||
|
|
||||||
|
#include <lvgl/theme_private.h>
|
||||||
|
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
|
|
||||||
constexpr auto* TAG = "lvgl";
|
constexpr auto* TAG = "lvgl";
|
||||||
@ -268,6 +270,8 @@ void lvgl_devices_detach() {
|
|||||||
display = lv_disp_get_next(NULL);
|
display = lv_disp_get_next(NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
lvgl_theme_deinit();
|
||||||
|
|
||||||
lvgl_unlock();
|
lvgl_unlock();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
#include <lvgl/lvgl.h>
|
#include <lvgl/lvgl.h>
|
||||||
#include <lvgl/ppa.h>
|
#include <lvgl/ppa.h>
|
||||||
|
#include <lvgl/theme_private.h>
|
||||||
|
|
||||||
#include <tactility/device.h>
|
#include <tactility/device.h>
|
||||||
#include <tactility/driver.h>
|
#include <tactility/driver.h>
|
||||||
@ -11,8 +12,6 @@
|
|||||||
|
|
||||||
#include <lvgl/devices/device_context.h>
|
#include <lvgl/devices/device_context.h>
|
||||||
|
|
||||||
#include "../themes/lv_theme_material.h"
|
|
||||||
#include "../themes/lv_theme_material_mono.h"
|
|
||||||
|
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
|
||||||
@ -24,31 +23,10 @@ constexpr auto* TAG = "lvgl_display";
|
|||||||
|
|
||||||
// The themes are generated with LSS from Modules/lvgl-module/themes/*.lss
|
// The themes are generated with LSS from Modules/lvgl-module/themes/*.lss
|
||||||
static void lvgl_display_apply_theme(lv_display_t* disp, struct Device* device, lv_color_format_t color_format) {
|
static void lvgl_display_apply_theme(lv_display_t* disp, struct Device* device, lv_color_format_t color_format) {
|
||||||
#if defined(LV_THEME_DEFAULT_DARK) && LV_THEME_DEFAULT_DARK
|
bool is_mono_display = color_format == LV_COLOR_FORMAT_I1 || color_format == LV_COLOR_FORMAT_L8;
|
||||||
bool is_dark = true;
|
|
||||||
#else
|
|
||||||
bool is_dark = false;
|
|
||||||
#endif
|
|
||||||
bool is_compact = lvgl_get_ui_density() == LVGL_UI_DENSITY_COMPACT;
|
|
||||||
// Slow-refresh panels (e-paper) can't show animations
|
// Slow-refresh panels (e-paper) can't show animations
|
||||||
bool animations_enabled = !display_has_capability(device, DISPLAY_CAPABILITY_SLOW_REFRESH);
|
bool supports_animations = !display_has_capability(device, DISPLAY_CAPABILITY_SLOW_REFRESH);
|
||||||
|
lv_display_set_theme(disp, lvgl_theme_init_for_display(disp, is_mono_display, supports_animations));
|
||||||
lv_theme_t* theme;
|
|
||||||
if (color_format == LV_COLOR_FORMAT_I1 || color_format == LV_COLOR_FORMAT_L8) {
|
|
||||||
lv_theme_material_mono_config_t config;
|
|
||||||
lv_theme_material_mono_config_init(&config);
|
|
||||||
config.is_compact = is_compact;
|
|
||||||
config.animations_enabled = animations_enabled;
|
|
||||||
theme = lv_theme_material_mono_init(disp, &config);
|
|
||||||
} else {
|
|
||||||
lv_theme_material_config_t config;
|
|
||||||
lv_theme_material_config_init(&config);
|
|
||||||
config.is_dark = is_dark;
|
|
||||||
config.is_compact = is_compact;
|
|
||||||
config.animations_enabled = animations_enabled;
|
|
||||||
theme = lv_theme_material_init(disp, &config);
|
|
||||||
}
|
|
||||||
lv_display_set_theme(disp, theme);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
struct LvglDisplayCtx {
|
struct LvglDisplayCtx {
|
||||||
|
|||||||
@ -10,7 +10,9 @@
|
|||||||
#include <lvgl/devices/pointer.h>
|
#include <lvgl/devices/pointer.h>
|
||||||
|
|
||||||
#include <lvgl/widgets/sliderbox.h>
|
#include <lvgl/widgets/sliderbox.h>
|
||||||
|
#include <lvgl/theme.h>
|
||||||
#include <lvgl/widgets/card.h>
|
#include <lvgl/widgets/card.h>
|
||||||
|
#include <lvgl/widgets/chip.h>
|
||||||
#include <lvgl/widgets/icon_button.h>
|
#include <lvgl/widgets/icon_button.h>
|
||||||
#include <lvgl/widgets/spinner.h>
|
#include <lvgl/widgets/spinner.h>
|
||||||
#include <lvgl/widgets/toolbar.h>
|
#include <lvgl/widgets/toolbar.h>
|
||||||
@ -58,8 +60,14 @@ const struct ModuleSymbol lvgl_module_symbols[] = {
|
|||||||
DEFINE_MODULE_SYMBOL(lvgl_pointer_add),
|
DEFINE_MODULE_SYMBOL(lvgl_pointer_add),
|
||||||
DEFINE_MODULE_SYMBOL(lvgl_pointer_get_slot_index),
|
DEFINE_MODULE_SYMBOL(lvgl_pointer_get_slot_index),
|
||||||
DEFINE_MODULE_SYMBOL(lvgl_pointer_remove),
|
DEFINE_MODULE_SYMBOL(lvgl_pointer_remove),
|
||||||
|
// lvgl_theme
|
||||||
|
DEFINE_MODULE_SYMBOL(lvgl_theme_get_settings),
|
||||||
|
DEFINE_MODULE_SYMBOL(lvgl_theme_get_default_settings),
|
||||||
|
DEFINE_MODULE_SYMBOL(lvgl_theme_set_settings),
|
||||||
// lvgl_card
|
// lvgl_card
|
||||||
DEFINE_MODULE_SYMBOL(lvgl_card_create),
|
DEFINE_MODULE_SYMBOL(lvgl_card_create),
|
||||||
|
// lvgl_chip
|
||||||
|
DEFINE_MODULE_SYMBOL(lvgl_chip_create),
|
||||||
// lvgl_icon_button
|
// lvgl_icon_button
|
||||||
DEFINE_MODULE_SYMBOL(lvgl_icon_button_create),
|
DEFINE_MODULE_SYMBOL(lvgl_icon_button_create),
|
||||||
DEFINE_MODULE_SYMBOL(lvgl_icon_button_create_variant),
|
DEFINE_MODULE_SYMBOL(lvgl_icon_button_create_variant),
|
||||||
|
|||||||
124
Modules/lvgl-module/source/theme.cpp
Normal file
124
Modules/lvgl-module/source/theme.cpp
Normal file
@ -0,0 +1,124 @@
|
|||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
#include <lvgl/theme.h>
|
||||||
|
#include <lvgl/theme_private.h>
|
||||||
|
|
||||||
|
#include <lvgl/lvgl.h>
|
||||||
|
#include <tactility/log.h>
|
||||||
|
|
||||||
|
#include "themes/lv_theme_material.h"
|
||||||
|
#include "themes/lv_theme_material_mono.h"
|
||||||
|
|
||||||
|
constexpr auto* TAG = "lvgl_theme";
|
||||||
|
|
||||||
|
enum class ActiveTheme {
|
||||||
|
None,
|
||||||
|
Material,
|
||||||
|
MaterialMono
|
||||||
|
};
|
||||||
|
|
||||||
|
static LvglThemeSettings theme_settings;
|
||||||
|
static bool theme_settings_set = false;
|
||||||
|
static ActiveTheme active_theme = ActiveTheme::None;
|
||||||
|
|
||||||
|
void lvgl_theme_get_default_settings(LvglThemeSettings* settings) {
|
||||||
|
lv_theme_material_config_t material_config;
|
||||||
|
lv_theme_material_config_init(&material_config);
|
||||||
|
#if defined(LV_THEME_DEFAULT_DARK) && LV_THEME_DEFAULT_DARK
|
||||||
|
settings->is_dark = true;
|
||||||
|
#else
|
||||||
|
settings->is_dark = false;
|
||||||
|
#endif
|
||||||
|
settings->is_mono = false;
|
||||||
|
settings->animations_enabled = true;
|
||||||
|
settings->color_primary = material_config.color_primary;
|
||||||
|
settings->color_secondary = material_config.color_secondary;
|
||||||
|
settings->color_error = material_config.color_error;
|
||||||
|
}
|
||||||
|
|
||||||
|
void lvgl_theme_get_settings(LvglThemeSettings* settings) {
|
||||||
|
if (theme_settings_set) {
|
||||||
|
*settings = theme_settings;
|
||||||
|
} else {
|
||||||
|
lvgl_theme_get_default_settings(settings);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void lvgl_theme_set_settings(const LvglThemeSettings* settings) {
|
||||||
|
theme_settings = *settings;
|
||||||
|
theme_settings_set = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static lv_theme_t* get_active_theme() {
|
||||||
|
switch (active_theme) {
|
||||||
|
case ActiveTheme::Material:
|
||||||
|
return lv_theme_material_get();
|
||||||
|
case ActiveTheme::MaterialMono:
|
||||||
|
return lv_theme_material_mono_get();
|
||||||
|
default:
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void lvgl_theme_deinit() {
|
||||||
|
switch (active_theme) {
|
||||||
|
case ActiveTheme::Material:
|
||||||
|
lv_theme_material_deinit();
|
||||||
|
break;
|
||||||
|
case ActiveTheme::MaterialMono:
|
||||||
|
lv_theme_material_mono_deinit();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
active_theme = ActiveTheme::None;
|
||||||
|
}
|
||||||
|
|
||||||
|
lv_theme_t* lvgl_theme_init_for_display(lv_display_t* display, bool isMonoDisplay, bool supportsAnimations) {
|
||||||
|
LvglThemeSettings settings;
|
||||||
|
lvgl_theme_get_settings(&settings);
|
||||||
|
const bool is_compact = lvgl_get_ui_density() == LVGL_UI_DENSITY_COMPACT;
|
||||||
|
const bool animations_enabled = settings.animations_enabled && supportsAnimations;
|
||||||
|
const ActiveTheme required_theme = (isMonoDisplay || settings.is_mono) ? ActiveTheme::MaterialMono : ActiveTheme::Material;
|
||||||
|
|
||||||
|
// Only one theme is allocated at a time
|
||||||
|
lv_theme_t* replaced_theme = nullptr;
|
||||||
|
if (active_theme != ActiveTheme::None && active_theme != required_theme) {
|
||||||
|
replaced_theme = get_active_theme();
|
||||||
|
lvgl_theme_deinit();
|
||||||
|
}
|
||||||
|
|
||||||
|
lv_theme_t* theme;
|
||||||
|
if (required_theme == ActiveTheme::MaterialMono) {
|
||||||
|
lv_theme_material_mono_config_t config;
|
||||||
|
lv_theme_material_mono_config_init(&config);
|
||||||
|
config.is_dark = settings.is_dark;
|
||||||
|
config.is_compact = is_compact;
|
||||||
|
config.animations_enabled = animations_enabled;
|
||||||
|
theme = lv_theme_material_mono_init(display, &config);
|
||||||
|
} else {
|
||||||
|
lv_theme_material_config_t config;
|
||||||
|
lv_theme_material_config_init(&config);
|
||||||
|
config.is_dark = settings.is_dark;
|
||||||
|
config.is_compact = is_compact;
|
||||||
|
config.animations_enabled = animations_enabled;
|
||||||
|
config.color_primary = settings.color_primary;
|
||||||
|
config.color_secondary = settings.color_secondary;
|
||||||
|
config.color_error = settings.color_error;
|
||||||
|
theme = lv_theme_material_init(display, &config);
|
||||||
|
}
|
||||||
|
active_theme = required_theme;
|
||||||
|
|
||||||
|
// Other displays mustn't keep the deinitialized theme. Their widgets keep the old styles until they're recreated.
|
||||||
|
if (replaced_theme != nullptr) {
|
||||||
|
LOG_W(TAG, "Displays need different themes: switching all displays to the new theme");
|
||||||
|
lv_display_t* other_display = lv_display_get_next(nullptr);
|
||||||
|
while (other_display != nullptr) {
|
||||||
|
if (lv_display_get_theme(other_display) == replaced_theme) {
|
||||||
|
lv_display_set_theme(other_display, theme);
|
||||||
|
}
|
||||||
|
other_display = lv_display_get_next(other_display);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return theme;
|
||||||
|
}
|
||||||
@ -14,6 +14,7 @@ extern const lv_obj_class_t lvgl_icon_button_filled_class;
|
|||||||
extern const lv_obj_class_t lvgl_icon_button_tonal_class;
|
extern const lv_obj_class_t lvgl_icon_button_tonal_class;
|
||||||
extern const lv_obj_class_t lvgl_sliderbox_class;
|
extern const lv_obj_class_t lvgl_sliderbox_class;
|
||||||
extern const lv_obj_class_t lvgl_card_class;
|
extern const lv_obj_class_t lvgl_card_class;
|
||||||
|
extern const lv_obj_class_t lvgl_chip_class;
|
||||||
|
|
||||||
#define NEUTRAL_4 lv_color_hex(0x0E0E0E)
|
#define NEUTRAL_4 lv_color_hex(0x0E0E0E)
|
||||||
#define NEUTRAL_6 lv_color_hex(0x131313)
|
#define NEUTRAL_6 lv_color_hex(0x131313)
|
||||||
@ -128,6 +129,13 @@ extern const lv_obj_class_t lvgl_card_class;
|
|||||||
#define CARD_SHAPE SHAPE_CONTAINER
|
#define CARD_SHAPE SHAPE_CONTAINER
|
||||||
#define CARD_PAD DENSITY_CONTAINER_PAD
|
#define CARD_PAD DENSITY_CONTAINER_PAD
|
||||||
#define CARD_GAP DENSITY_CONTAINER_GAP
|
#define CARD_GAP DENSITY_CONTAINER_GAP
|
||||||
|
#define CHIP_LABEL_COLOR COLOR_ON_SURFACE_VARIANT
|
||||||
|
#define CHIP_BORDER_COLOR COLOR_OUTLINE
|
||||||
|
#define CHIP_SELECTED_COLOR COLOR_SECONDARY_CONTAINER
|
||||||
|
#define CHIP_SELECTED_LABEL_COLOR COLOR_ON_SECONDARY_CONTAINER
|
||||||
|
#define CHIP_SHAPE (theme->config.is_compact ? CORNER_COMPACT : CORNER_SMALL)
|
||||||
|
#define CHIP_PAD_HOR (theme->config.is_compact ? 3 : SPACE_4)
|
||||||
|
#define CHIP_PAD_VER (theme->config.is_compact ? 2 : LV_DPX_CALC(theme->disp_dpi, 6))
|
||||||
#define SCROLLBAR_COLOR COLOR_OUTLINE
|
#define SCROLLBAR_COLOR COLOR_OUTLINE
|
||||||
#define SCROLLBAR_OPACITY (LV_OPA_40)
|
#define SCROLLBAR_OPACITY (LV_OPA_40)
|
||||||
#define SCROLLBAR_SCROLLED_OPACITY (LV_OPA_70)
|
#define SCROLLBAR_SCROLLED_OPACITY (LV_OPA_70)
|
||||||
@ -324,6 +332,8 @@ typedef struct {
|
|||||||
lv_style_t screen;
|
lv_style_t screen;
|
||||||
lv_style_t container;
|
lv_style_t container;
|
||||||
lv_style_t card;
|
lv_style_t card;
|
||||||
|
lv_style_t chip;
|
||||||
|
lv_style_t chip_selected;
|
||||||
#if LV_USE_BUTTON
|
#if LV_USE_BUTTON
|
||||||
lv_style_t button;
|
lv_style_t button;
|
||||||
#endif
|
#endif
|
||||||
@ -747,6 +757,21 @@ static void style_init(my_theme_t * theme)
|
|||||||
lv_style_set_pad_all(&theme->styles.card, CARD_PAD);
|
lv_style_set_pad_all(&theme->styles.card, CARD_PAD);
|
||||||
lv_style_set_pad_gap(&theme->styles.card, CARD_GAP);
|
lv_style_set_pad_gap(&theme->styles.card, CARD_GAP);
|
||||||
|
|
||||||
|
style_init_reset(&theme->styles.chip);
|
||||||
|
lv_style_set_bg_opa(&theme->styles.chip, LV_OPA_TRANSP);
|
||||||
|
lv_style_set_text_color(&theme->styles.chip, CHIP_LABEL_COLOR);
|
||||||
|
lv_style_set_border_color(&theme->styles.chip, CHIP_BORDER_COLOR);
|
||||||
|
lv_style_set_border_width(&theme->styles.chip, DENSITY_BORDER);
|
||||||
|
lv_style_set_radius(&theme->styles.chip, CHIP_SHAPE);
|
||||||
|
lv_style_set_pad_hor(&theme->styles.chip, CHIP_PAD_HOR);
|
||||||
|
lv_style_set_pad_ver(&theme->styles.chip, CHIP_PAD_VER);
|
||||||
|
|
||||||
|
style_init_reset(&theme->styles.chip_selected);
|
||||||
|
lv_style_set_bg_color(&theme->styles.chip_selected, CHIP_SELECTED_COLOR);
|
||||||
|
lv_style_set_bg_opa(&theme->styles.chip_selected, LV_OPA_COVER);
|
||||||
|
lv_style_set_text_color(&theme->styles.chip_selected, CHIP_SELECTED_LABEL_COLOR);
|
||||||
|
lv_style_set_border_color(&theme->styles.chip_selected, CHIP_SELECTED_COLOR);
|
||||||
|
|
||||||
#if LV_USE_BUTTON
|
#if LV_USE_BUTTON
|
||||||
style_init_reset(&theme->styles.button);
|
style_init_reset(&theme->styles.button);
|
||||||
lv_style_set_bg_color(&theme->styles.button, BUTTON_CONTAINER_COLOR);
|
lv_style_set_bg_color(&theme->styles.button, BUTTON_CONTAINER_COLOR);
|
||||||
@ -1828,6 +1853,18 @@ static void theme_apply(lv_theme_t * th, lv_obj_t * obj)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
if(lv_obj_check_type(obj, &lvgl_chip_class)) {
|
||||||
|
/* chip */
|
||||||
|
lv_obj_add_style(obj, &theme->styles.chip, 0);
|
||||||
|
lv_obj_add_style(obj, &theme->styles.transition_exit, 0);
|
||||||
|
lv_obj_add_style(obj, &theme->styles.state_pressed, LV_STATE_PRESSED);
|
||||||
|
lv_obj_add_style(obj, &theme->styles.transition_enter, LV_STATE_PRESSED);
|
||||||
|
lv_obj_add_style(obj, &theme->styles.chip_selected, LV_STATE_CHECKED);
|
||||||
|
lv_obj_add_style(obj, &theme->styles.focus_ring, LV_STATE_FOCUS_KEY);
|
||||||
|
lv_obj_add_style(obj, &theme->styles.edit_ring, LV_STATE_EDITED);
|
||||||
|
lv_obj_add_style(obj, &theme->styles.disabled, LV_STATE_DISABLED);
|
||||||
|
return;
|
||||||
|
}
|
||||||
if(lv_obj_check_type(obj, &lvgl_card_class)) {
|
if(lv_obj_check_type(obj, &lvgl_card_class)) {
|
||||||
/* card */
|
/* card */
|
||||||
lv_obj_add_style(obj, &theme->styles.card, 0);
|
lv_obj_add_style(obj, &theme->styles.card, 0);
|
||||||
|
|||||||
@ -14,6 +14,7 @@ extern const lv_obj_class_t lvgl_icon_button_filled_class;
|
|||||||
extern const lv_obj_class_t lvgl_icon_button_tonal_class;
|
extern const lv_obj_class_t lvgl_icon_button_tonal_class;
|
||||||
extern const lv_obj_class_t lvgl_sliderbox_class;
|
extern const lv_obj_class_t lvgl_sliderbox_class;
|
||||||
extern const lv_obj_class_t lvgl_card_class;
|
extern const lv_obj_class_t lvgl_card_class;
|
||||||
|
extern const lv_obj_class_t lvgl_chip_class;
|
||||||
|
|
||||||
#define BLACK lv_color_black()
|
#define BLACK lv_color_black()
|
||||||
#define WHITE lv_color_white()
|
#define WHITE lv_color_white()
|
||||||
@ -82,6 +83,9 @@ extern const lv_obj_class_t lvgl_card_class;
|
|||||||
#define CARD_SHAPE SHAPE_CONTAINER
|
#define CARD_SHAPE SHAPE_CONTAINER
|
||||||
#define CARD_PAD DENSITY_CONTAINER_PAD
|
#define CARD_PAD DENSITY_CONTAINER_PAD
|
||||||
#define CARD_GAP DENSITY_CONTAINER_GAP
|
#define CARD_GAP DENSITY_CONTAINER_GAP
|
||||||
|
#define CHIP_SHAPE (theme->config.is_compact ? CORNER_COMPACT : CORNER_SMALL)
|
||||||
|
#define CHIP_PAD_HOR (theme->config.is_compact ? 3 : SPACE_4)
|
||||||
|
#define CHIP_PAD_VER (theme->config.is_compact ? 2 : SPACE_2)
|
||||||
#define SCROLLBAR_COLOR COLOR_ON_SURFACE
|
#define SCROLLBAR_COLOR COLOR_ON_SURFACE
|
||||||
#define SCROLLBAR_WIDTH (theme->config.is_compact ? 2 : LV_DPX_CALC(theme->disp_dpi, 3))
|
#define SCROLLBAR_WIDTH (theme->config.is_compact ? 2 : LV_DPX_CALC(theme->disp_dpi, 3))
|
||||||
#define SCROLLBAR_PAD 1
|
#define SCROLLBAR_PAD 1
|
||||||
@ -209,6 +213,7 @@ typedef struct {
|
|||||||
lv_style_t screen;
|
lv_style_t screen;
|
||||||
lv_style_t container;
|
lv_style_t container;
|
||||||
lv_style_t card;
|
lv_style_t card;
|
||||||
|
lv_style_t chip;
|
||||||
#if LV_USE_BUTTON || LV_USE_MSGBOX
|
#if LV_USE_BUTTON || LV_USE_MSGBOX
|
||||||
lv_style_t button;
|
lv_style_t button;
|
||||||
#endif
|
#endif
|
||||||
@ -541,6 +546,16 @@ static void style_init(my_theme_t * theme)
|
|||||||
lv_style_set_pad_all(&theme->styles.card, CARD_PAD);
|
lv_style_set_pad_all(&theme->styles.card, CARD_PAD);
|
||||||
lv_style_set_pad_gap(&theme->styles.card, CARD_GAP);
|
lv_style_set_pad_gap(&theme->styles.card, CARD_GAP);
|
||||||
|
|
||||||
|
style_init_reset(&theme->styles.chip);
|
||||||
|
lv_style_set_bg_color(&theme->styles.chip, COLOR_SURFACE);
|
||||||
|
lv_style_set_bg_opa(&theme->styles.chip, LV_OPA_COVER);
|
||||||
|
lv_style_set_text_color(&theme->styles.chip, COLOR_ON_SURFACE);
|
||||||
|
lv_style_set_border_color(&theme->styles.chip, COLOR_OUTLINE);
|
||||||
|
lv_style_set_border_width(&theme->styles.chip, BORDER);
|
||||||
|
lv_style_set_radius(&theme->styles.chip, CHIP_SHAPE);
|
||||||
|
lv_style_set_pad_hor(&theme->styles.chip, CHIP_PAD_HOR);
|
||||||
|
lv_style_set_pad_ver(&theme->styles.chip, CHIP_PAD_VER);
|
||||||
|
|
||||||
#if LV_USE_BUTTON || LV_USE_MSGBOX
|
#if LV_USE_BUTTON || LV_USE_MSGBOX
|
||||||
style_init_reset(&theme->styles.button);
|
style_init_reset(&theme->styles.button);
|
||||||
lv_style_set_bg_color(&theme->styles.button, BUTTON_CONTAINER_COLOR);
|
lv_style_set_bg_color(&theme->styles.button, BUTTON_CONTAINER_COLOR);
|
||||||
@ -1431,6 +1446,16 @@ static void theme_apply(lv_theme_t * th, lv_obj_t * obj)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
if(lv_obj_check_type(obj, &lvgl_chip_class)) {
|
||||||
|
/* chip */
|
||||||
|
lv_obj_add_style(obj, &theme->styles.chip, 0);
|
||||||
|
lv_obj_add_style(obj, &theme->styles.inverted, LV_STATE_CHECKED);
|
||||||
|
lv_obj_add_style(obj, &theme->styles.inverted, LV_STATE_PRESSED);
|
||||||
|
lv_obj_add_style(obj, &theme->styles.focus_ring, LV_STATE_FOCUS_KEY);
|
||||||
|
lv_obj_add_style(obj, &theme->styles.edit_ring, LV_STATE_EDITED);
|
||||||
|
lv_obj_add_style(obj, &theme->styles.disabled, LV_STATE_DISABLED);
|
||||||
|
return;
|
||||||
|
}
|
||||||
if(lv_obj_check_type(obj, &lvgl_card_class)) {
|
if(lv_obj_check_type(obj, &lvgl_card_class)) {
|
||||||
/* card */
|
/* card */
|
||||||
lv_obj_add_style(obj, &theme->styles.card, 0);
|
lv_obj_add_style(obj, &theme->styles.card, 0);
|
||||||
|
|||||||
26
Modules/lvgl-module/source/widgets/chip.cpp
Normal file
26
Modules/lvgl-module/source/widgets/chip.cpp
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
#define LV_USE_PRIVATE_API 1 // For the lv_obj_class_t declaration
|
||||||
|
|
||||||
|
#include <lvgl/widgets/chip.h>
|
||||||
|
|
||||||
|
// Not static: the theme styles chips by this class
|
||||||
|
extern "C" const lv_obj_class_t lvgl_chip_class = {
|
||||||
|
.base_class = &lv_button_class,
|
||||||
|
.constructor_cb = nullptr,
|
||||||
|
.destructor_cb = nullptr,
|
||||||
|
.event_cb = nullptr,
|
||||||
|
.user_data = nullptr,
|
||||||
|
.name = "chip",
|
||||||
|
.width_def = 0,
|
||||||
|
.height_def = 0,
|
||||||
|
.editable = LV_OBJ_CLASS_EDITABLE_INHERIT,
|
||||||
|
.group_def = LV_OBJ_CLASS_GROUP_DEF_INHERIT,
|
||||||
|
.instance_size = 0,
|
||||||
|
.theme_inheritable = LV_OBJ_CLASS_THEME_INHERITABLE_FALSE
|
||||||
|
};
|
||||||
|
|
||||||
|
lv_obj_t* lvgl_chip_create(lv_obj_t* parent) {
|
||||||
|
lv_obj_t* obj = lv_obj_class_create_obj(&lvgl_chip_class, parent);
|
||||||
|
lv_obj_class_init_obj(obj);
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
@ -27,6 +27,7 @@
|
|||||||
@widget icon_button_tonal: lvgl_icon_button_tonal_class;
|
@widget icon_button_tonal: lvgl_icon_button_tonal_class;
|
||||||
@widget sliderbox: lvgl_sliderbox_class;
|
@widget sliderbox: lvgl_sliderbox_class;
|
||||||
@widget card: lvgl_card_class;
|
@widget card: lvgl_card_class;
|
||||||
|
@widget chip: lvgl_chip_class;
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// 1. Base tokens
|
// 1. Base tokens
|
||||||
@ -188,6 +189,15 @@
|
|||||||
@card-pad: @density-container-pad;
|
@card-pad: @density-container-pad;
|
||||||
@card-gap: @density-container-gap;
|
@card-gap: @density-container-gap;
|
||||||
|
|
||||||
|
// Chip (lvgl_chip_create): Material 3 filter chip, selected when checked
|
||||||
|
@chip-label-color: @color-on-surface-variant;
|
||||||
|
@chip-border-color: @color-outline;
|
||||||
|
@chip-selected-color: @color-secondary-container;
|
||||||
|
@chip-selected-label-color: @color-on-secondary-container;
|
||||||
|
@chip-shape: if($isCompact, @corner-compact, @corner-small);
|
||||||
|
@chip-pad-hor: if($isCompact, 3, @space-4);
|
||||||
|
@chip-pad-ver: if($isCompact, 2, 6dp);
|
||||||
|
|
||||||
// Scrollbar
|
// Scrollbar
|
||||||
@scrollbar-color: @color-outline;
|
@scrollbar-color: @color-outline;
|
||||||
@scrollbar-opacity: 40%;
|
@scrollbar-opacity: 40%;
|
||||||
@ -551,6 +561,23 @@ card {
|
|||||||
padGap: @card-gap;
|
padGap: @card-gap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
chip {
|
||||||
|
bgOpa: transp;
|
||||||
|
textColor: @chip-label-color;
|
||||||
|
borderColor: @chip-border-color;
|
||||||
|
borderWidth: @density-border;
|
||||||
|
radius: @chip-shape;
|
||||||
|
padHor: @chip-pad-hor;
|
||||||
|
padVer: @chip-pad-ver;
|
||||||
|
}
|
||||||
|
|
||||||
|
chip_selected {
|
||||||
|
bgColor: @chip-selected-color;
|
||||||
|
bgOpa: cover;
|
||||||
|
textColor: @chip-selected-label-color;
|
||||||
|
borderColor: @chip-selected-color;
|
||||||
|
}
|
||||||
|
|
||||||
button {
|
button {
|
||||||
bgColor: @button-container-color;
|
bgColor: @button-container-color;
|
||||||
bgOpa: cover;
|
bgOpa: cover;
|
||||||
@ -1353,6 +1380,17 @@ apply(checkbox) {
|
|||||||
disabled: state.disabled;
|
disabled: state.disabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
apply(chip) {
|
||||||
|
chip: default;
|
||||||
|
transition_exit: default;
|
||||||
|
state_pressed: state.pressed;
|
||||||
|
transition_enter: state.pressed;
|
||||||
|
chip_selected: state.checked;
|
||||||
|
focus_ring: state.focusKey;
|
||||||
|
edit_ring: state.edited;
|
||||||
|
disabled: state.disabled;
|
||||||
|
}
|
||||||
|
|
||||||
apply(card) {
|
apply(card) {
|
||||||
card: default;
|
card: default;
|
||||||
scrollbar: part.scrollbar;
|
scrollbar: part.scrollbar;
|
||||||
|
|||||||
@ -28,6 +28,7 @@
|
|||||||
@widget icon_button_tonal: lvgl_icon_button_tonal_class;
|
@widget icon_button_tonal: lvgl_icon_button_tonal_class;
|
||||||
@widget sliderbox: lvgl_sliderbox_class;
|
@widget sliderbox: lvgl_sliderbox_class;
|
||||||
@widget card: lvgl_card_class;
|
@widget card: lvgl_card_class;
|
||||||
|
@widget chip: lvgl_chip_class;
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// 1. Base tokens
|
// 1. Base tokens
|
||||||
@ -135,6 +136,11 @@
|
|||||||
@card-pad: @density-container-pad;
|
@card-pad: @density-container-pad;
|
||||||
@card-gap: @density-container-gap;
|
@card-gap: @density-container-gap;
|
||||||
|
|
||||||
|
// Chip (lvgl_chip_create): outlined, inverted when selected
|
||||||
|
@chip-shape: if($isCompact, @corner-compact, @corner-small);
|
||||||
|
@chip-pad-hor: if($isCompact, 3, @space-4);
|
||||||
|
@chip-pad-ver: if($isCompact, 2, @space-2);
|
||||||
|
|
||||||
// Scrollbar
|
// Scrollbar
|
||||||
@scrollbar-color: @color-on-surface;
|
@scrollbar-color: @color-on-surface;
|
||||||
@scrollbar-width: if($isCompact, 2, 3dp);
|
@scrollbar-width: if($isCompact, 2, 3dp);
|
||||||
@ -381,6 +387,17 @@ card {
|
|||||||
padGap: @card-gap;
|
padGap: @card-gap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
chip {
|
||||||
|
bgColor: @color-surface;
|
||||||
|
bgOpa: cover;
|
||||||
|
textColor: @color-on-surface;
|
||||||
|
borderColor: @color-outline;
|
||||||
|
borderWidth: @border;
|
||||||
|
radius: @chip-shape;
|
||||||
|
padHor: @chip-pad-hor;
|
||||||
|
padVer: @chip-pad-ver;
|
||||||
|
}
|
||||||
|
|
||||||
button {
|
button {
|
||||||
bgColor: @button-container-color;
|
bgColor: @button-container-color;
|
||||||
bgOpa: cover;
|
bgOpa: cover;
|
||||||
@ -1026,6 +1043,15 @@ apply(checkbox) {
|
|||||||
disabled: part.indicator, state.disabled;
|
disabled: part.indicator, state.disabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
apply(chip) {
|
||||||
|
chip: default;
|
||||||
|
inverted: state.checked;
|
||||||
|
inverted: state.pressed;
|
||||||
|
focus_ring: state.focusKey;
|
||||||
|
edit_ring: state.edited;
|
||||||
|
disabled: state.disabled;
|
||||||
|
}
|
||||||
|
|
||||||
apply(card) {
|
apply(card) {
|
||||||
card: default;
|
card: default;
|
||||||
scrollbar: part.scrollbar;
|
scrollbar: part.scrollbar;
|
||||||
|
|||||||
@ -1,10 +1,18 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
#include <optional>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
namespace tt::settings::appearance {
|
namespace tt::settings::appearance {
|
||||||
|
|
||||||
|
enum class ThemeMode {
|
||||||
|
/** Light or dark, as configured for the device */
|
||||||
|
DeviceDefault,
|
||||||
|
Light,
|
||||||
|
Dark
|
||||||
|
};
|
||||||
|
|
||||||
struct AppearanceSettings {
|
struct AppearanceSettings {
|
||||||
/** Default text font size in pixels, or 0 for the device's default size */
|
/** Default text font size in pixels, or 0 for the device's default size */
|
||||||
uint16_t fontSize = 0;
|
uint16_t fontSize = 0;
|
||||||
@ -12,6 +20,14 @@ struct AppearanceSettings {
|
|||||||
std::string regularFontPath;
|
std::string regularFontPath;
|
||||||
/** TrueType font for monospace text, or empty for the system font */
|
/** TrueType font for monospace text, or empty for the system font */
|
||||||
std::string monoFontPath;
|
std::string monoFontPath;
|
||||||
|
ThemeMode themeMode = ThemeMode::DeviceDefault;
|
||||||
|
/** Use the monochrome theme, also on colour displays */
|
||||||
|
bool monoTheme = false;
|
||||||
|
bool animationsEnabled = true;
|
||||||
|
/** Theme colours as 0xRRGGBB, or empty for the theme's default colour */
|
||||||
|
std::optional<uint32_t> primaryColor;
|
||||||
|
std::optional<uint32_t> secondaryColor;
|
||||||
|
std::optional<uint32_t> errorColor;
|
||||||
|
|
||||||
bool operator==(const AppearanceSettings&) const = default;
|
bool operator==(const AppearanceSettings&) const = default;
|
||||||
};
|
};
|
||||||
|
|||||||
13
Tactility/Private/Tactility/lvgl/Theme.h
Normal file
13
Tactility/Private/Tactility/lvgl/Theme.h
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <Tactility/settings/AppearanceSettings.h>
|
||||||
|
|
||||||
|
namespace tt::lvgl {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the theme settings from the appearance settings.
|
||||||
|
* They take effect for displays that are added afterwards, so call this before LVGL (re)starts.
|
||||||
|
*/
|
||||||
|
void configureTheme(const settings::appearance::AppearanceSettings& settings);
|
||||||
|
|
||||||
|
}
|
||||||
@ -2,6 +2,7 @@
|
|||||||
#include <Tactility/file/File.h>
|
#include <Tactility/file/File.h>
|
||||||
#include <Tactility/lvgl/Fonts.h>
|
#include <Tactility/lvgl/Fonts.h>
|
||||||
#include <Tactility/lvgl/Lvgl.h>
|
#include <Tactility/lvgl/Lvgl.h>
|
||||||
|
#include <Tactility/lvgl/Theme.h>
|
||||||
#include <Tactility/settings/AppearanceSettings.h>
|
#include <Tactility/settings/AppearanceSettings.h>
|
||||||
|
|
||||||
#include <app/event.h>
|
#include <app/event.h>
|
||||||
@ -19,6 +20,9 @@
|
|||||||
|
|
||||||
#include <lvgl.h>
|
#include <lvgl.h>
|
||||||
#include <lvgl/lvgl.h>
|
#include <lvgl/lvgl.h>
|
||||||
|
#include <lvgl/theme.h>
|
||||||
|
#include <lvgl/widgets/card.h>
|
||||||
|
#include <lvgl/widgets/chip.h>
|
||||||
#include <lvgl/widgets/toolbar.h>
|
#include <lvgl/widgets/toolbar.h>
|
||||||
|
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
@ -42,6 +46,33 @@ enum class FontSlot {
|
|||||||
Mono
|
Mono
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct NamedColor {
|
||||||
|
const char* name;
|
||||||
|
lv_palette_t palette;
|
||||||
|
};
|
||||||
|
|
||||||
|
constexpr NamedColor PALETTE_COLORS[] = {
|
||||||
|
{ "Red", LV_PALETTE_RED },
|
||||||
|
{ "Pink", LV_PALETTE_PINK },
|
||||||
|
{ "Purple", LV_PALETTE_PURPLE },
|
||||||
|
{ "Deep purple", LV_PALETTE_DEEP_PURPLE },
|
||||||
|
{ "Indigo", LV_PALETTE_INDIGO },
|
||||||
|
{ "Blue", LV_PALETTE_BLUE },
|
||||||
|
{ "Light blue", LV_PALETTE_LIGHT_BLUE },
|
||||||
|
{ "Cyan", LV_PALETTE_CYAN },
|
||||||
|
{ "Teal", LV_PALETTE_TEAL },
|
||||||
|
{ "Green", LV_PALETTE_GREEN },
|
||||||
|
{ "Light green", LV_PALETTE_LIGHT_GREEN },
|
||||||
|
{ "Lime", LV_PALETTE_LIME },
|
||||||
|
{ "Yellow", LV_PALETTE_YELLOW },
|
||||||
|
{ "Amber", LV_PALETTE_AMBER },
|
||||||
|
{ "Orange", LV_PALETTE_ORANGE },
|
||||||
|
{ "Deep orange", LV_PALETTE_DEEP_ORANGE },
|
||||||
|
{ "Brown", LV_PALETTE_BROWN },
|
||||||
|
{ "Blue grey", LV_PALETTE_BLUE_GREY },
|
||||||
|
{ "Grey", LV_PALETTE_GREY },
|
||||||
|
};
|
||||||
|
|
||||||
struct FontRowWidgets {
|
struct FontRowWidgets {
|
||||||
lv_obj_t* defaultButton = nullptr;
|
lv_obj_t* defaultButton = nullptr;
|
||||||
lv_obj_t* fileLabel = nullptr;
|
lv_obj_t* fileLabel = nullptr;
|
||||||
@ -80,6 +111,13 @@ struct Context {
|
|||||||
FontRowWidgets regularRow;
|
FontRowWidgets regularRow;
|
||||||
FontRowWidgets monoRow;
|
FontRowWidgets monoRow;
|
||||||
lv_obj_t* errorLabel = nullptr;
|
lv_obj_t* errorLabel = nullptr;
|
||||||
|
lv_obj_t* lightChip = nullptr;
|
||||||
|
lv_obj_t* darkChip = nullptr;
|
||||||
|
lv_obj_t* regularThemeChip = nullptr;
|
||||||
|
lv_obj_t* monoThemeChip = nullptr;
|
||||||
|
lv_obj_t* colorRows[3] = {};
|
||||||
|
/** The display can only show the mono theme */
|
||||||
|
bool isMonoDisplay = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
lvgl::FontConfiguration toFontConfiguration(const settings::appearance::AppearanceSettings& settings) {
|
lvgl::FontConfiguration toFontConfiguration(const settings::appearance::AppearanceSettings& settings) {
|
||||||
@ -120,6 +158,47 @@ void updateFontRow(const FontRowWidgets& row, const std::string& path, size_t ch
|
|||||||
setHidden(row.warningLabel, !large);
|
setHidden(row.warningLabel, !large);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool isDeviceDefaultDark() {
|
||||||
|
LvglThemeSettings defaults;
|
||||||
|
lvgl_theme_get_default_settings(&defaults);
|
||||||
|
return defaults.is_dark;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isDark(const settings::appearance::AppearanceSettings& settings) {
|
||||||
|
switch (settings.themeMode) {
|
||||||
|
case settings::appearance::ThemeMode::Light:
|
||||||
|
return false;
|
||||||
|
case settings::appearance::ThemeMode::Dark:
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return isDeviceDefaultDark();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void setChecked(lv_obj_t* object, bool checked) {
|
||||||
|
if (checked) {
|
||||||
|
lv_obj_add_state(object, LV_STATE_CHECKED);
|
||||||
|
} else {
|
||||||
|
lv_obj_remove_state(object, LV_STATE_CHECKED);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void updateThemeWidgets(Context* ctx) {
|
||||||
|
const bool dark = isDark(ctx->pendingSettings);
|
||||||
|
setChecked(ctx->lightChip, !dark);
|
||||||
|
setChecked(ctx->darkChip, dark);
|
||||||
|
if (ctx->regularThemeChip != nullptr) {
|
||||||
|
setChecked(ctx->regularThemeChip, !ctx->pendingSettings.monoTheme);
|
||||||
|
setChecked(ctx->monoThemeChip, ctx->pendingSettings.monoTheme);
|
||||||
|
}
|
||||||
|
// The mono theme doesn't use colours
|
||||||
|
for (auto* row : ctx->colorRows) {
|
||||||
|
if (row != nullptr) {
|
||||||
|
setHidden(row, ctx->pendingSettings.monoTheme);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** Updates the widgets to the pending settings. Requires the LVGL lock. */
|
/** Updates the widgets to the pending settings. Requires the LVGL lock. */
|
||||||
void updateWidgets(Context* ctx) {
|
void updateWidgets(Context* ctx) {
|
||||||
if (ctx->applyButton == nullptr) {
|
if (ctx->applyButton == nullptr) {
|
||||||
@ -131,6 +210,64 @@ void updateWidgets(Context* ctx) {
|
|||||||
updateFontRow(ctx->regularRow, ctx->pendingSettings.regularFontPath, ctx->regularCharacterCount);
|
updateFontRow(ctx->regularRow, ctx->pendingSettings.regularFontPath, ctx->regularCharacterCount);
|
||||||
updateFontRow(ctx->monoRow, ctx->pendingSettings.monoFontPath, ctx->monoCharacterCount);
|
updateFontRow(ctx->monoRow, ctx->pendingSettings.monoFontPath, ctx->monoCharacterCount);
|
||||||
setHidden(ctx->errorLabel, !ctx->applyFailed);
|
setHidden(ctx->errorLabel, !ctx->applyFailed);
|
||||||
|
updateThemeWidgets(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
void setDark(Context* ctx, bool dark) {
|
||||||
|
// The device default is kept when it matches, so it doesn't count as a change
|
||||||
|
if (dark == isDeviceDefaultDark()) {
|
||||||
|
ctx->pendingSettings.themeMode = settings::appearance::ThemeMode::DeviceDefault;
|
||||||
|
} else {
|
||||||
|
ctx->pendingSettings.themeMode = dark ? settings::appearance::ThemeMode::Dark : settings::appearance::ThemeMode::Light;
|
||||||
|
}
|
||||||
|
updateWidgets(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
void onLightPressed(lv_event_t* event) {
|
||||||
|
setDark(static_cast<Context*>(lv_event_get_user_data(event)), false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void onDarkPressed(lv_event_t* event) {
|
||||||
|
setDark(static_cast<Context*>(lv_event_get_user_data(event)), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void onRegularThemePressed(lv_event_t* event) {
|
||||||
|
auto* ctx = static_cast<Context*>(lv_event_get_user_data(event));
|
||||||
|
ctx->pendingSettings.monoTheme = false;
|
||||||
|
updateWidgets(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
void onMonoThemePressed(lv_event_t* event) {
|
||||||
|
auto* ctx = static_cast<Context*>(lv_event_get_user_data(event));
|
||||||
|
ctx->pendingSettings.monoTheme = true;
|
||||||
|
updateWidgets(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
void onAnimationsChanged(lv_event_t* event) {
|
||||||
|
auto* ctx = static_cast<Context*>(lv_event_get_user_data(event));
|
||||||
|
auto* checkbox = static_cast<lv_obj_t*>(lv_event_get_target(event));
|
||||||
|
ctx->pendingSettings.animationsEnabled = lv_obj_has_state(checkbox, LV_STATE_CHECKED);
|
||||||
|
updateWidgets(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t getPaletteColor(lv_palette_t palette) {
|
||||||
|
return lv_color_to_u32(lv_palette_main(palette)) & 0xFFFFFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr size_t PALETTE_COLOR_COUNT = sizeof(PALETTE_COLORS) / sizeof(PALETTE_COLORS[0]);
|
||||||
|
|
||||||
|
void onColorChanged(lv_event_t* event) {
|
||||||
|
auto* ctx = static_cast<Context*>(lv_event_get_user_data(event));
|
||||||
|
auto* dropdown = static_cast<lv_obj_t*>(lv_event_get_target(event));
|
||||||
|
auto* color = static_cast<std::optional<uint32_t>*>(lv_obj_get_user_data(dropdown));
|
||||||
|
const uint32_t selected = lv_dropdown_get_selected(dropdown);
|
||||||
|
if (selected == 0) {
|
||||||
|
color->reset();
|
||||||
|
} else if (selected <= PALETTE_COLOR_COUNT) {
|
||||||
|
*color = getPaletteColor(PALETTE_COLORS[selected - 1].palette);
|
||||||
|
}
|
||||||
|
// Any other option is the custom colour from the settings file, which stays unchanged
|
||||||
|
updateWidgets(ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
void onBackPressed(lv_event_t* event) {
|
void onBackPressed(lv_event_t* event) {
|
||||||
@ -194,20 +331,99 @@ void onDefaultMonoPressed(lv_event_t* event) {
|
|||||||
updateWidgets(ctx);
|
updateWidgets(ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** An unstyled row, so it doesn't paint over the card. Its items are spaced like the card's content. */
|
||||||
lv_obj_t* createRow(lv_obj_t* parent) {
|
lv_obj_t* createRow(lv_obj_t* parent) {
|
||||||
auto* row = lv_obj_create(parent);
|
auto* row = lv_obj_create(parent);
|
||||||
|
lv_obj_remove_style_all(row);
|
||||||
lv_obj_set_size(row, LV_PCT(100), LV_SIZE_CONTENT);
|
lv_obj_set_size(row, LV_PCT(100), LV_SIZE_CONTENT);
|
||||||
lv_obj_set_flex_flow(row, LV_FLEX_FLOW_ROW);
|
lv_obj_set_flex_flow(row, LV_FLEX_FLOW_ROW);
|
||||||
lv_obj_set_flex_align(row, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);
|
lv_obj_set_flex_align(row, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);
|
||||||
lv_obj_set_style_pad_all(row, 0, LV_STATE_DEFAULT);
|
lv_obj_set_style_pad_column(row, lv_obj_get_style_pad_column(parent, LV_PART_MAIN), LV_STATE_DEFAULT);
|
||||||
// Room for the focus outline of the buttons, which is clipped to the row in compact mode
|
// Room for the focus outline of the buttons, which is clipped to the row in compact mode
|
||||||
lv_obj_set_style_pad_ver(row, 3, LV_STATE_DEFAULT);
|
lv_obj_set_style_pad_ver(row, 3, LV_STATE_DEFAULT);
|
||||||
lv_obj_set_style_pad_hor(row, 3, LV_STATE_DEFAULT);
|
lv_obj_set_style_pad_hor(row, 3, LV_STATE_DEFAULT);
|
||||||
lv_obj_set_style_border_width(row, 0, LV_STATE_DEFAULT);
|
|
||||||
lv_obj_remove_flag(row, LV_OBJ_FLAG_SCROLLABLE);
|
lv_obj_remove_flag(row, LV_OBJ_FLAG_SCROLLABLE);
|
||||||
return row;
|
return row;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** A title above a card, and the card */
|
||||||
|
lv_obj_t* createSection(lv_obj_t* parent, const char* title) {
|
||||||
|
auto* title_label = lv_label_create(parent);
|
||||||
|
lv_label_set_text(title_label, title);
|
||||||
|
|
||||||
|
auto* card = lvgl_card_create(parent);
|
||||||
|
lv_obj_set_size(card, LV_PCT(100), LV_SIZE_CONTENT);
|
||||||
|
lv_obj_set_flex_flow(card, LV_FLEX_FLOW_COLUMN);
|
||||||
|
return card;
|
||||||
|
}
|
||||||
|
|
||||||
|
lv_obj_t* createLabeledRow(lv_obj_t* parent, const char* text) {
|
||||||
|
auto* row = createRow(parent);
|
||||||
|
auto* label = lv_label_create(row);
|
||||||
|
lv_label_set_text(label, text);
|
||||||
|
lv_obj_set_flex_grow(label, 1);
|
||||||
|
return row;
|
||||||
|
}
|
||||||
|
|
||||||
|
lv_obj_t* createChip(lv_obj_t* parent, const char* text, lv_event_cb_t callback, Context* ctx) {
|
||||||
|
auto* chip = lvgl_chip_create(parent);
|
||||||
|
auto* label = lv_label_create(chip);
|
||||||
|
lv_label_set_text(label, text);
|
||||||
|
lv_obj_add_event_cb(chip, callback, LV_EVENT_SHORT_CLICKED, ctx);
|
||||||
|
return chip;
|
||||||
|
}
|
||||||
|
|
||||||
|
lv_obj_t* createColorRow(lv_obj_t* parent, const char* text, std::optional<uint32_t>* color, Context* ctx) {
|
||||||
|
auto* row = createLabeledRow(parent, text);
|
||||||
|
auto* dropdown = lv_dropdown_create(row);
|
||||||
|
|
||||||
|
std::string options = "Default";
|
||||||
|
uint32_t selected = 0;
|
||||||
|
for (size_t i = 0; i < PALETTE_COLOR_COUNT; i++) {
|
||||||
|
options += "\n";
|
||||||
|
options += PALETTE_COLORS[i].name;
|
||||||
|
if (color->has_value() && **color == getPaletteColor(PALETTE_COLORS[i].palette)) {
|
||||||
|
selected = i + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// A colour that isn't in the palette was set in the settings file
|
||||||
|
if (color->has_value() && selected == 0) {
|
||||||
|
options += "\nCustom";
|
||||||
|
selected = PALETTE_COLOR_COUNT + 1;
|
||||||
|
}
|
||||||
|
lv_dropdown_set_options(dropdown, options.c_str());
|
||||||
|
lv_dropdown_set_selected(dropdown, selected);
|
||||||
|
lv_obj_set_user_data(dropdown, color);
|
||||||
|
lv_obj_add_event_cb(dropdown, onColorChanged, LV_EVENT_VALUE_CHANGED, ctx);
|
||||||
|
return row;
|
||||||
|
}
|
||||||
|
|
||||||
|
void createThemeCard(lv_obj_t* parent, Context* ctx) {
|
||||||
|
auto* card = createSection(parent, "Theme");
|
||||||
|
|
||||||
|
auto* mode_row = createLabeledRow(card, "Mode");
|
||||||
|
ctx->lightChip = createChip(mode_row, "Light", onLightPressed, ctx);
|
||||||
|
ctx->darkChip = createChip(mode_row, "Dark", onDarkPressed, ctx);
|
||||||
|
|
||||||
|
// Monochrome and greyscale displays always use the mono theme, and it doesn't use colours
|
||||||
|
if (!ctx->isMonoDisplay) {
|
||||||
|
auto* style_row = createLabeledRow(card, "Style");
|
||||||
|
ctx->regularThemeChip = createChip(style_row, "Regular", onRegularThemePressed, ctx);
|
||||||
|
ctx->monoThemeChip = createChip(style_row, "Mono", onMonoThemePressed, ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto* animations_checkbox = lv_checkbox_create(card);
|
||||||
|
lv_checkbox_set_text(animations_checkbox, "Animations");
|
||||||
|
setChecked(animations_checkbox, ctx->pendingSettings.animationsEnabled);
|
||||||
|
lv_obj_add_event_cb(animations_checkbox, onAnimationsChanged, LV_EVENT_VALUE_CHANGED, ctx);
|
||||||
|
|
||||||
|
if (!ctx->isMonoDisplay) {
|
||||||
|
ctx->colorRows[0] = createColorRow(card, "Primary color", &ctx->pendingSettings.primaryColor, ctx);
|
||||||
|
ctx->colorRows[1] = createColorRow(card, "Secondary color", &ctx->pendingSettings.secondaryColor, ctx);
|
||||||
|
ctx->colorRows[2] = createColorRow(card, "Error color", &ctx->pendingSettings.errorColor, ctx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
lv_obj_t* createButton(lv_obj_t* parent, const char* text, lv_event_cb_t callback, Context* ctx) {
|
lv_obj_t* createButton(lv_obj_t* parent, const char* text, lv_event_cb_t callback, Context* ctx) {
|
||||||
auto* button = lv_button_create(parent);
|
auto* button = lv_button_create(parent);
|
||||||
auto* label = lv_label_create(button);
|
auto* label = lv_label_create(button);
|
||||||
@ -257,7 +473,12 @@ void createWidgets(lv_obj_t* parent, void* userData) {
|
|||||||
lv_obj_set_flex_flow(content, LV_FLEX_FLOW_COLUMN);
|
lv_obj_set_flex_flow(content, LV_FLEX_FLOW_COLUMN);
|
||||||
lv_obj_set_style_border_width(content, 0, LV_STATE_DEFAULT);
|
lv_obj_set_style_border_width(content, 0, LV_STATE_DEFAULT);
|
||||||
|
|
||||||
auto* font_size_row = createRow(content);
|
const auto color_format = lv_display_get_color_format(lv_obj_get_display(parent));
|
||||||
|
ctx->isMonoDisplay = color_format == LV_COLOR_FORMAT_I1 || color_format == LV_COLOR_FORMAT_L8;
|
||||||
|
createThemeCard(content, ctx);
|
||||||
|
|
||||||
|
auto* fonts_card = createSection(content, "Fonts");
|
||||||
|
auto* font_size_row = createRow(fonts_card);
|
||||||
auto* font_size_label = lv_label_create(font_size_row);
|
auto* font_size_label = lv_label_create(font_size_row);
|
||||||
lv_label_set_text(font_size_label, "Font size");
|
lv_label_set_text(font_size_label, "Font size");
|
||||||
lv_obj_set_flex_grow(font_size_label, 1);
|
lv_obj_set_flex_grow(font_size_label, 1);
|
||||||
@ -277,10 +498,10 @@ void createWidgets(lv_obj_t* parent, void* userData) {
|
|||||||
lv_obj_move_to_index(decrement_button, lv_obj_get_index(font_size_spinbox));
|
lv_obj_move_to_index(decrement_button, lv_obj_get_index(font_size_spinbox));
|
||||||
createStepButton(font_size_row, LV_SYMBOL_PLUS, onFontSizeIncrementPressed, font_size_spinbox);
|
createStepButton(font_size_row, LV_SYMBOL_PLUS, onFontSizeIncrementPressed, font_size_spinbox);
|
||||||
|
|
||||||
ctx->regularRow = createFontRow(content, "Regular font", onSelectRegularPressed, onDefaultRegularPressed, ctx);
|
ctx->regularRow = createFontRow(fonts_card, "Regular font", onSelectRegularPressed, onDefaultRegularPressed, ctx);
|
||||||
ctx->monoRow = createFontRow(content, "Mono font", onSelectMonoPressed, onDefaultMonoPressed, ctx);
|
ctx->monoRow = createFontRow(fonts_card, "Mono font", onSelectMonoPressed, onDefaultMonoPressed, ctx);
|
||||||
|
|
||||||
ctx->errorLabel = lv_label_create(content);
|
ctx->errorLabel = lv_label_create(fonts_card);
|
||||||
lv_obj_set_width(ctx->errorLabel, LV_PCT(100));
|
lv_obj_set_width(ctx->errorLabel, LV_PCT(100));
|
||||||
lv_label_set_long_mode(ctx->errorLabel, LV_LABEL_LONG_MODE_WRAP);
|
lv_label_set_long_mode(ctx->errorLabel, LV_LABEL_LONG_MODE_WRAP);
|
||||||
lv_label_set_text(ctx->errorLabel, "Failed to create the fonts. Check that the selected files are TrueType fonts.");
|
lv_label_set_text(ctx->errorLabel, "Failed to create the fonts. Check that the selected files are TrueType fonts.");
|
||||||
@ -296,6 +517,13 @@ void destroyWidgets(void* userData) {
|
|||||||
ctx->regularRow = {};
|
ctx->regularRow = {};
|
||||||
ctx->monoRow = {};
|
ctx->monoRow = {};
|
||||||
ctx->errorLabel = nullptr;
|
ctx->errorLabel = nullptr;
|
||||||
|
ctx->lightChip = nullptr;
|
||||||
|
ctx->darkChip = nullptr;
|
||||||
|
ctx->regularThemeChip = nullptr;
|
||||||
|
ctx->monoThemeChip = nullptr;
|
||||||
|
for (auto*& row : ctx->colorRows) {
|
||||||
|
row = nullptr;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int32_t applyThreadMain(void* context) {
|
int32_t applyThreadMain(void* context) {
|
||||||
@ -313,6 +541,8 @@ void cancelApply(Context* ctx) {
|
|||||||
lvgl_unlock();
|
lvgl_unlock();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void finishApply(Context* ctx);
|
||||||
|
|
||||||
void startApply(Context* ctx) {
|
void startApply(Context* ctx) {
|
||||||
if (ctx->applying || ctx->applyThread != nullptr) {
|
if (ctx->applying || ctx->applyThread != nullptr) {
|
||||||
return;
|
return;
|
||||||
@ -320,11 +550,19 @@ void startApply(Context* ctx) {
|
|||||||
|
|
||||||
lvgl_lock();
|
lvgl_lock();
|
||||||
ctx->applyConfiguration = toFontConfiguration(ctx->pendingSettings);
|
ctx->applyConfiguration = toFontConfiguration(ctx->pendingSettings);
|
||||||
|
const bool fonts_changed = ctx->applyConfiguration != toFontConfiguration(ctx->savedSettings);
|
||||||
ctx->applying = true;
|
ctx->applying = true;
|
||||||
ctx->applyFailed = false;
|
ctx->applyFailed = false;
|
||||||
updateWidgets(ctx);
|
updateWidgets(ctx);
|
||||||
lvgl_unlock();
|
lvgl_unlock();
|
||||||
|
|
||||||
|
// Theme changes don't need new fonts
|
||||||
|
if (!fonts_changed) {
|
||||||
|
ctx->cacheUpdateSucceeded = true;
|
||||||
|
finishApply(ctx);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
ctx->applyThread = thread_alloc();
|
ctx->applyThread = thread_alloc();
|
||||||
if (ctx->applyThread == nullptr) {
|
if (ctx->applyThread == nullptr) {
|
||||||
LOG_E(TAG, "Failed to create thread");
|
LOG_E(TAG, "Failed to create thread");
|
||||||
@ -378,8 +616,9 @@ void finishApply(Context* ctx) {
|
|||||||
lvgl_unlock();
|
lvgl_unlock();
|
||||||
|
|
||||||
// The window is rebuilt from the saved settings when LVGL starts again
|
// The window is rebuilt from the saved settings when LVGL starts again
|
||||||
LOG_I(TAG, "Restarting LVGL with the new fonts");
|
LOG_I(TAG, "Restarting LVGL with the new settings");
|
||||||
lvgl::stop();
|
lvgl::stop();
|
||||||
|
lvgl::configureTheme(applied_settings);
|
||||||
lvgl::loadFonts(ctx->applyConfiguration);
|
lvgl::loadFonts(ctx->applyConfiguration);
|
||||||
lvgl::start();
|
lvgl::start();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -23,6 +23,8 @@
|
|||||||
#include <Tactility/hal/usb/Usb.h>
|
#include <Tactility/hal/usb/Usb.h>
|
||||||
#include <Tactility/lvgl/Fonts.h>
|
#include <Tactility/lvgl/Fonts.h>
|
||||||
#include <Tactility/lvgl/Lvgl.h>
|
#include <Tactility/lvgl/Lvgl.h>
|
||||||
|
#include <Tactility/lvgl/Theme.h>
|
||||||
|
#include <Tactility/settings/AppearanceSettings.h>
|
||||||
#include <Tactility/settings/BootSettings.h>
|
#include <Tactility/settings/BootSettings.h>
|
||||||
#include <Tactility/settings/DisplaySettings.h>
|
#include <Tactility/settings/DisplaySettings.h>
|
||||||
|
|
||||||
@ -271,6 +273,7 @@ bool bootInit(TickType_t startTime) {
|
|||||||
|
|
||||||
LOG_I(TAG, "Loading fonts");
|
LOG_I(TAG, "Loading fonts");
|
||||||
lvgl::loadFonts(lvgl::loadFontConfiguration());
|
lvgl::loadFonts(lvgl::loadFontConfiguration());
|
||||||
|
lvgl::configureTheme(settings::appearance::loadOrGetDefault());
|
||||||
|
|
||||||
screen.end();
|
screen.end();
|
||||||
|
|
||||||
|
|||||||
27
Tactility/Source/lvgl/Theme.cpp
Normal file
27
Tactility/Source/lvgl/Theme.cpp
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
#include <Tactility/lvgl/Theme.h>
|
||||||
|
|
||||||
|
#include <lvgl/theme.h>
|
||||||
|
|
||||||
|
namespace tt::lvgl {
|
||||||
|
|
||||||
|
void configureTheme(const settings::appearance::AppearanceSettings& settings) {
|
||||||
|
LvglThemeSettings theme_settings;
|
||||||
|
lvgl_theme_get_default_settings(&theme_settings);
|
||||||
|
if (settings.themeMode != settings::appearance::ThemeMode::DeviceDefault) {
|
||||||
|
theme_settings.is_dark = settings.themeMode == settings::appearance::ThemeMode::Dark;
|
||||||
|
}
|
||||||
|
theme_settings.is_mono = settings.monoTheme;
|
||||||
|
theme_settings.animations_enabled = settings.animationsEnabled;
|
||||||
|
if (settings.primaryColor.has_value()) {
|
||||||
|
theme_settings.color_primary = lv_color_hex(*settings.primaryColor);
|
||||||
|
}
|
||||||
|
if (settings.secondaryColor.has_value()) {
|
||||||
|
theme_settings.color_secondary = lv_color_hex(*settings.secondaryColor);
|
||||||
|
}
|
||||||
|
if (settings.errorColor.has_value()) {
|
||||||
|
theme_settings.color_error = lv_color_hex(*settings.errorColor);
|
||||||
|
}
|
||||||
|
lvgl_theme_set_settings(&theme_settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -5,6 +5,8 @@
|
|||||||
|
|
||||||
#include <app/paths.h>
|
#include <app/paths.h>
|
||||||
|
|
||||||
|
#include <cinttypes>
|
||||||
|
#include <cstdio>
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
#include <map>
|
#include <map>
|
||||||
|
|
||||||
@ -13,6 +15,28 @@ namespace tt::settings::appearance {
|
|||||||
constexpr auto* SETTINGS_KEY_FONT_SIZE = "fontSize";
|
constexpr auto* SETTINGS_KEY_FONT_SIZE = "fontSize";
|
||||||
constexpr auto* SETTINGS_KEY_REGULAR_FONT_PATH = "regularFontPath";
|
constexpr auto* SETTINGS_KEY_REGULAR_FONT_PATH = "regularFontPath";
|
||||||
constexpr auto* SETTINGS_KEY_MONO_FONT_PATH = "monoFontPath";
|
constexpr auto* SETTINGS_KEY_MONO_FONT_PATH = "monoFontPath";
|
||||||
|
constexpr auto* SETTINGS_KEY_THEME_MODE = "themeMode";
|
||||||
|
constexpr auto* SETTINGS_KEY_MONO_THEME = "monoTheme";
|
||||||
|
constexpr auto* SETTINGS_KEY_ANIMATIONS = "animations";
|
||||||
|
constexpr auto* SETTINGS_KEY_PRIMARY_COLOR = "primaryColor";
|
||||||
|
constexpr auto* SETTINGS_KEY_SECONDARY_COLOR = "secondaryColor";
|
||||||
|
constexpr auto* SETTINGS_KEY_ERROR_COLOR = "errorColor";
|
||||||
|
|
||||||
|
static std::optional<uint32_t> parseColor(const std::map<std::string, std::string>& map, const char* key) {
|
||||||
|
const auto entry = map.find(key);
|
||||||
|
if (entry == map.end() || entry->second.empty()) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
return static_cast<uint32_t>(std::strtoul(entry->second.c_str(), nullptr, 16)) & 0xFFFFFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void putColor(std::map<std::string, std::string>& map, const char* key, std::optional<uint32_t> color) {
|
||||||
|
if (color.has_value()) {
|
||||||
|
char text[8];
|
||||||
|
std::snprintf(text, sizeof(text), "%06" PRIX32, *color);
|
||||||
|
map[key] = text;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static std::string getSettingsFilePath() {
|
static std::string getSettingsFilePath() {
|
||||||
char path[256];
|
char path[256];
|
||||||
@ -43,6 +67,22 @@ bool load(AppearanceSettings& settings) {
|
|||||||
if (const auto entry = map.find(SETTINGS_KEY_MONO_FONT_PATH); entry != map.end()) {
|
if (const auto entry = map.find(SETTINGS_KEY_MONO_FONT_PATH); entry != map.end()) {
|
||||||
settings.monoFontPath = entry->second;
|
settings.monoFontPath = entry->second;
|
||||||
}
|
}
|
||||||
|
if (const auto entry = map.find(SETTINGS_KEY_THEME_MODE); entry != map.end()) {
|
||||||
|
if (entry->second == "light") {
|
||||||
|
settings.themeMode = ThemeMode::Light;
|
||||||
|
} else if (entry->second == "dark") {
|
||||||
|
settings.themeMode = ThemeMode::Dark;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (const auto entry = map.find(SETTINGS_KEY_MONO_THEME); entry != map.end()) {
|
||||||
|
settings.monoTheme = entry->second == "true";
|
||||||
|
}
|
||||||
|
if (const auto entry = map.find(SETTINGS_KEY_ANIMATIONS); entry != map.end()) {
|
||||||
|
settings.animationsEnabled = entry->second != "false";
|
||||||
|
}
|
||||||
|
settings.primaryColor = parseColor(map, SETTINGS_KEY_PRIMARY_COLOR);
|
||||||
|
settings.secondaryColor = parseColor(map, SETTINGS_KEY_SECONDARY_COLOR);
|
||||||
|
settings.errorColor = parseColor(map, SETTINGS_KEY_ERROR_COLOR);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -63,6 +103,14 @@ bool save(const AppearanceSettings& settings) {
|
|||||||
map[SETTINGS_KEY_FONT_SIZE] = std::to_string(settings.fontSize);
|
map[SETTINGS_KEY_FONT_SIZE] = std::to_string(settings.fontSize);
|
||||||
map[SETTINGS_KEY_REGULAR_FONT_PATH] = settings.regularFontPath;
|
map[SETTINGS_KEY_REGULAR_FONT_PATH] = settings.regularFontPath;
|
||||||
map[SETTINGS_KEY_MONO_FONT_PATH] = settings.monoFontPath;
|
map[SETTINGS_KEY_MONO_FONT_PATH] = settings.monoFontPath;
|
||||||
|
if (settings.themeMode != ThemeMode::DeviceDefault) {
|
||||||
|
map[SETTINGS_KEY_THEME_MODE] = settings.themeMode == ThemeMode::Dark ? "dark" : "light";
|
||||||
|
}
|
||||||
|
map[SETTINGS_KEY_MONO_THEME] = settings.monoTheme ? "true" : "false";
|
||||||
|
map[SETTINGS_KEY_ANIMATIONS] = settings.animationsEnabled ? "true" : "false";
|
||||||
|
putColor(map, SETTINGS_KEY_PRIMARY_COLOR, settings.primaryColor);
|
||||||
|
putColor(map, SETTINGS_KEY_SECONDARY_COLOR, settings.secondaryColor);
|
||||||
|
putColor(map, SETTINGS_KEY_ERROR_COLOR, settings.errorColor);
|
||||||
return file::savePropertiesFile(settings_path, map);
|
return file::savePropertiesFile(settings_path, map);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user