12035f2f39
- Migrates tactility-firmware and tactility-bluetooth from ~/.hermes/skills/ into .claude/skills/ for repo co-location - Adds AGENTS.md with local workstation context, board table, board-direct build rule, Hermes PYTHONPATH pitfall, common Tactility pitfalls, and in-repo skill index Refs: personal Gitea mirror
65 lines
2.7 KiB
Markdown
65 lines
2.7 KiB
Markdown
# DisplaySettings Extension Recipe
|
|
|
|
Header: `Tactility/Include/Tactility/settings/DisplaySettings.h`
|
|
Impl: `Tactility/Source/settings/DisplaySettings.cpp`
|
|
|
|
## Field Types
|
|
|
|
Struct as of 2026-07-11:
|
|
```cpp
|
|
struct DisplaySettings {
|
|
Orientation orientation;
|
|
uint8_t gammaCurve;
|
|
uint8_t backlightDuty;
|
|
bool backlightTimeoutEnabled;
|
|
uint32_t backlightTimeoutMs;
|
|
ScreensaverType screensaverType = BouncingBalls;
|
|
bool disableScreensaverWhenCharging = false; // new
|
|
};
|
|
enum ScreensaverType { None, BouncingBalls, Mystify, MatrixRain, StackChan, McpScreen, Count };
|
|
```
|
|
|
|
## Adding a New Field — 5 Places in CPP
|
|
|
|
1. `SETTINGS_KEY_*` constant:
|
|
```cpp
|
|
constexpr auto* SETTINGS_KEY_DISABLE_SCREENSAVER_WHEN_CHARGING = "disableScreensaverWhenCharging";
|
|
```
|
|
|
|
2. `load()` — parse:
|
|
```cpp
|
|
bool disable_when_charging = false;
|
|
auto e = map.find(KEY_DISABLE);
|
|
if (e != map.end()) disable_when_charging = (e->second=="1"||e->second=="true"||e->second=="True");
|
|
...
|
|
settings.disableScreensaverWhenCharging = disable_when_charging;
|
|
```
|
|
|
|
3. `getDefault()` aggregate includes field.
|
|
|
|
4. `save()`:
|
|
```cpp
|
|
map[KEY_DISABLE] = settings.disableScreensaverWhenCharging ? "1":"0";
|
|
```
|
|
|
|
5. For enums: add `toString/fromString` cases. Sentinel `Count` is bounds check.
|
|
|
|
## Persistence Details
|
|
- File: `getUserDataPath()+"/settings/display.properties"` (Internal or SD depending on device.properties storage.userDataLocation)
|
|
- `loadPropertiesFile` → `map<string,string>`, tolerant parsing `atoi`/`strtoul`
|
|
- `savePropertiesFile` writes `key=value` lines. Parent dir created via `findOrCreateParentDirectory(path,0755)`
|
|
- `loadOrGetDefault()` tries load, else getDefault()
|
|
- Settings thread-safety: save dispatched off UI via `getMainDispatcher().dispatch([settings]{ save(settings); findService()->reloadSettings(); })`
|
|
|
|
## Reload into DisplayIdleService
|
|
`reloadSettings()` sets `atomic<bool> settingsReloadRequested`. Next `tick()` (50ms timer, Lower priority) checks `exchange(false)` and does `cachedDisplaySettings = loadOrGetDefault()` while holding LVGL lock.
|
|
|
|
## Display App UI Wiring
|
|
- Members stored as `lv_obj_t*` raw pointers, initialized nullptr
|
|
- `onShow()` loads settings `loadOrGetDefault()`, builds wrappers column flex
|
|
- Each row pattern: create wrapper, label left aligned, control right aligned (switch/dropdown/slider)
|
|
- Switch state: `lv_obj_has_state(sw, LV_STATE_CHECKED)`; set via `lv_obj_add_state(sw, LV_STATE_CHECKED)` before aligning
|
|
- Dependent disabling: parent `timeoutSwitch` toggles `timeoutDropdown`, `screensaverDropdown`, `disableWhenChargingWrapper` via `LV_STATE_DISABLED`
|
|
- Event callbacks static, retrieve app via `lv_event_get_user_data(event)` cast to DisplayApp*
|
|
- `onHide()` only saves if `displaySettingsUpdated==true`, dispatches async
|