Skip to content

Commit 07ed391

Browse files
committed
2026.06.12
1 parent 4b0fe58 commit 07ed391

9 files changed

Lines changed: 186 additions & 123 deletions

File tree

.github/ai-coding-guide.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,10 @@
4848
- **If the change touches ANY firmware file**: You **MUST READ** `.github/code-summary.md` before writing code. Also check `.github/code-issues.md` for known issues in the affected area.
4949
- **If the change touches ONLY non-firmware files** (workflows, docs, build scripts, config generators, Home Assistant, images, etc.): `code-summary.md` review is **NOT** required. Still check `code-issues.md` if relevant.
5050
- **One-line/trivial fixes** (typos, formatting) are exempt regardless of file type.
51+
- **What `.github/code-issues.md` is for**: it tracks **open** issues that still need investigation, or that are blocked — for example, waiting on hardware the maintainer does not own. It is **not** a changelog.
52+
- Do **not** add entries for problems found and fixed in the same change set.
53+
- Do **not** add general rules or subsystem documentation there; that belongs in `code-summary.md` (Rule #4).
54+
- Do read it before editing an affected area, and **update or close an existing entry** if your change fixes it or alters the code that entry describes.
5155

5256
**Enforcement and AI Behavior**
5357
- Always validate proposed changes against these rules **before every action**.

.github/code-summary.md

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -754,6 +754,35 @@ Important rendering invariants:
754754
- Never call `startWrite()`/`endWrite()` in `write()` or `writePixel`/`writeFillRect` overrides — SPI nesting causes hangs on Adafruit TFT drivers.
755755
- The `gfxFont == NULL` case (YO_MONO / display font) runs through `_writeGlyph` using DisplayFont, NOT the built-in glcdfont. This means glyph metrics (yAdvance, yOffset, xAdvance) come from DisplayFont.
756756

757+
## Display Widget Memory Ownership
758+
759+
The widget classes mix two allocation conventions; release must match allocation:
760+
761+
| Member | Owner | Allocated with | Released with |
762+
|---|---|---|---|
763+
| `_text`, `_oldtext` | `TextWidget` (and `NumWidget`, which duplicates it) | `malloc` | `free` |
764+
| `_sep`, `_window` | `ScrollWidget` | `malloc` | `free` |
765+
| `_fb` | `ScrollWidget`, `ClockWidget` | `new psFrameBuffer` | `delete` |
766+
| `_canvas` | `VuWidget` | `new Canvas` | `delete` |
767+
768+
Rules:
769+
- Never `free()` a `new`-allocated object: `free()` skips the destructor and leaks the internal buffer.
770+
- `init()` is re-called on every layout/theme change, so it must release its previous buffers first.
771+
- Resize an existing `psFrameBuffer` with `freeBuffer()` then `begin()`, never by allocating a second one.
772+
773+
### Optional widget guard fields
774+
775+
Each config type has its own field that makes a widget meaningful, and that is what the creation guard in `display.cpp` must test — never a coordinate, since `{0,0}` is a legitimate position:
776+
777+
| Config type | Fields | Guard field |
778+
|---|---|---|
779+
| `WidgetConfig` | `left`, `top`, `textsize`, `align` | `textsize > 0` |
780+
| `ScrollConfig` | `widget`, `buffsize`, `uppercase`, `width`, ... | `buffsize > 0` |
781+
| `FillConfig` | `widget`, `width`, `height`, `outlined` | `height > 0` |
782+
| `BitrateConfig` | `widget`, `dimension` | `dimension > 0` |
783+
784+
`_fullbitrate` and `_bitrate` are alternatives: an empty `.fullbitrateConf` falls back to `.bitrateConf`, and `_reinitWidgets` must tear down whichever one is no longer wanted even when the replacement config is itself empty.
785+
757786
## Screen Rendering Fixes (Session: SH1106 YO_MONO)
758787

759788
### ClockWidget colon blink (widgets.cpp)
@@ -775,6 +804,13 @@ Important rendering invariants:
775804

776805
---
777806

807+
## Screen Rendering Fixes (Session: VU Rotated Layout)
808+
809+
- **New layout flag** `LayoutData::rotateVU` (exposed via `rotateVU_ptr`), treated exactly like `boomboxStyle` — absent means false. `VuWidget::_rotate` is read from `rotateVU_ptr` in `init()`.
810+
- **Layout ordering** in `displayTFT480x320conf.h`: `_layoutNames` is now `Default`, `Default (VU Rotated)`, `VaraiTamas (BoomBox)`. The rotated layout is layout #2 (`bandsConf = { 32, 130, 4, 2, 10, 3 }`, `.rotateVU = true`); BoomBox moved to #3.
811+
- **Blit choice**: `VuWidget::_draw()` uses the manual `startWrite()` / `setAddrWindow()` / `writePixels()` / `endWrite()` sequence for all three modes. `drawRGBBitmap()` was deliberately removed from the widget layer — the manual path depends only on `setAddrWindow` and `writePixels`, which every TFT driver is guaranteed to implement, and it issues a single bulk transfer rather than one `writePixels` call per scanline. Do not switch this back.
812+
- **Direction**: the rotated VU fills left-to-right with `_vumaxcolor` at the right end.
813+
778814
## CPU Core Assignments & Stack Sizes
779815

780816
The ESP32 has two hardware cores: **Core 0** (PRO_CPU) and **Core 1** (APP_CPU). The ESP32 Arduino framework runs `setup()` and `loop()` on Core 1. Audio decoding is isolated on Core 0; all other application tasks run on Core 1.

src/core/display.cpp

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -248,7 +248,7 @@ void Display::_buildPager() {
248248
if (fullbitrateConf_ptr->dimension > 0) {
249249
_fullbitrate = new BitrateWidget(*fullbitrateConf_ptr, config.theme.bitrate, config.theme.background);
250250
pages[PG_PLAYER]->addWidget(_fullbitrate);
251-
} else {
251+
} else if (bitrateConf_ptr->textsize > 0) {
252252
_bitrate = new TextWidget(*bitrateConf_ptr, 30, false, config.theme.bitrate, config.theme.background);
253253
pages[PG_PLAYER]->addWidget(_bitrate);
254254
}
@@ -310,7 +310,11 @@ void Display::_buildPager() {
310310
}
311311

312312
void Display::_apScreen() {
313-
if (_boot) _pager->removePage(_boot);
313+
if (_boot) {
314+
_pager->removePage(_boot);
315+
_boot = nullptr;
316+
_bootstring = nullptr;
317+
}
314318
#ifndef DSP_LCD
315319
_boot = new Page();
316320
#if DSP_MODEL!=DSP_NOKIA5110
@@ -349,7 +353,11 @@ void Display::_apScreen() {
349353
}
350354

351355
void Display::_start() {
352-
if (_boot) _pager->removePage(_boot);
356+
if (_boot) {
357+
_pager->removePage(_boot);
358+
_boot = nullptr;
359+
_bootstring = nullptr;
360+
}
353361
if (network.status != CONNECTED && network.status != SDOFFLINE) {
354362
_apScreen();
355363
_bootStep = 2;
@@ -983,11 +991,13 @@ void Display::_reinitWidgets() {
983991
pages[PG_PLAYER]->addWidget(_fullbitrate);
984992
} else _fullbitrate->init(*fullbitrateConf_ptr, config.theme.bitrate, config.theme.background);
985993
} else {
986-
if (!_bitrate) {
987-
if (_fullbitrate) { pages[PG_PLAYER]->removeWidget(_fullbitrate); delete _fullbitrate; _fullbitrate = nullptr; }
988-
_bitrate = new TextWidget(*bitrateConf_ptr, 30, false, config.theme.bitrate, config.theme.background);
989-
pages[PG_PLAYER]->addWidget(_bitrate);
990-
} else _bitrate->init(*bitrateConf_ptr, 30, false, config.theme.bitrate, config.theme.background);
994+
if (_fullbitrate) { pages[PG_PLAYER]->removeWidget(_fullbitrate); delete _fullbitrate; _fullbitrate = nullptr; }
995+
if (bitrateConf_ptr->textsize > 0) {
996+
if (!_bitrate) {
997+
_bitrate = new TextWidget(*bitrateConf_ptr, 30, false, config.theme.bitrate, config.theme.background);
998+
pages[PG_PLAYER]->addWidget(_bitrate);
999+
} else _bitrate->init(*bitrateConf_ptr, 30, false, config.theme.bitrate, config.theme.background);
1000+
}
9911001
}
9921002

9931003
// --- Footer widgets (lazy-create if newly enabled) ---
@@ -1037,6 +1047,16 @@ void Display::_reinitWidgets() {
10371047
#if DSP_MODEL==DSP_NOKIA5110
10381048
if (_plbackground) _plbackground->init(*playlBGConf_ptr, 1);
10391049
#endif
1050+
/* _plbackground->init() above resets _height and _config.top back to playlBGConf.
1051+
Its geometry is meant to follow the live playlist rows, so re-apply the same
1052+
values _buildPager() uses — otherwise the highlight band keeps the conf
1053+
height/position instead of matching itemHeight(). */
1054+
#if !defined(DSP_LCD) && !PLAYLIST_MODE_PAGED
1055+
if (_plbackground) {
1056+
_plbackground->setHeight(_plwidget->itemHeight());
1057+
_plbackground->moveTo({0,(uint16_t)(_plwidget->currentTop()-playlistConf_ptr->widget.textsize*2), (int16_t)playlBGConf_ptr->width});
1058+
}
1059+
#endif
10401060
}
10411061

10421062
void Display::_setLayoutPointers() {

src/displays/conf/displayTFT480x320conf.h

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -87,13 +87,11 @@ const LayoutData _layouts[] PROGMEM = {
8787
.rssiConf = { TFT_FRAMEWDT, DSP_HEIGHT-38-6, 3, WA_RIGHT },
8888
.numConf = { 0, 200, 0, WA_CENTER },
8989
.clockConf = { TFT_FRAMEWDT*2, 230, 0, WA_RIGHT },
90-
.vuConf = { TFT_FRAMEWDT, 161, 1, WA_LEFT }, //136 touches title2 almost, 210 touches IP, 162 (161?) top of clock
91-
//.vuConf = { TFT_FRAMEWDT, 159, 1, WA_LEFT }, //136 touches title2 almost, 210 touches IP, 162 (161?) top of clock
90+
.vuConf = { TFT_FRAMEWDT, 161, 1, WA_LEFT },
9291
/* CODEC BADGE {{ left, top, fontsize, align }, dimension} - if empty, bitrateConf will be used instead */
9392
.fullbitrateConf = {{ DSP_WIDTH-TFT_FRAMEWDT-38, 59, 2, WA_LEFT }, 42 },
9493
/* VU BANDS { onebandwidth, onebandheight, bandsHspace, bandsVspace, numofbands, fadespeed } */
95-
//.bandsConf = { 46, 130, 7, 2, 10, 3 }, //1st: 32, 130, 4, 2
96-
.bandsConf = { 25, 130, 17, 3, 10, 3 }, //1st: 32, 130, 4, 2
94+
.bandsConf = { 25, 130, 17, 3, 10, 3 },
9795
/* MOVES { left, top, width (-1 keeps Conf position) */
9896
.clockMove = { 0, 0, -1 },
9997
.weatherMove = { TFT_FRAMEWDT, 120, MAX_WIDTH },

src/displays/tools/psframebuffer.h

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,10 @@ class psFrameBuffer : public Adafruit_GFX {
3535
_ready = false;
3636
if(buffer) {
3737
_dspl->fillRect(_ll, _tt, _ww, _hh, _bgcolor);
38-
psramFrameBufferBytes -= _hh * _ww * sizeof(uint16_t);
38+
if (_psram) psramFrameBufferBytes -= _hh * _ww * sizeof(uint16_t);
3939
free(buffer);
4040
}
41+
_psram = false;
4142
buffer = nullptr;
4243
}
4344
bool begin(yoDisplay *dspl, int16_t l, int16_t t, int16_t w, int16_t h, uint16_t bgcolor = 0){
@@ -99,17 +100,20 @@ class psFrameBuffer : public Adafruit_GFX {
99100
int16_t height(){ return _hh; }
100101
private:
101102
int16_t _ll, _tt, _ww, _hh;
102-
yoDisplay *_dspl;
103+
yoDisplay *_dspl = nullptr;
103104
uint16_t *buffer=nullptr;
104105
bool _ready = false;
106+
bool _psram = false;
105107
uint16_t _bgcolor;
106108
void _createBuffer(){
107109
#if (defined(USE_FBUFFER) && USE_FBUFFER)
108110
if(psramInit()) {
109111
buffer = (uint16_t*) ps_calloc(_hh * _ww, sizeof(uint16_t));
112+
_psram = true;
110113
psramFrameBufferBytes += _hh * _ww * sizeof(uint16_t);
111114
} else {
112115
buffer = (uint16_t*) calloc(_hh * _ww, sizeof(uint16_t));
116+
_psram = false;
113117
}
114118
#endif
115119
if(buffer){

src/displays/widgets/pages.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,9 @@ void Pager::setPage(Page* page, bool black){
4747
//}
4848

4949
Page::~Page() {
50-
for (const auto& w : _widgets) removeWidget(w);
50+
std::list<Widget*> draining;
51+
draining.swap(_widgets);
52+
for (const auto& w : draining) delete w;
5153
// what about deleting _pages ???
5254
}
5355

0 commit comments

Comments
 (0)