v2 rebuild: GDI+ single-surface UI, self-calibrating progress, compact push-to-talk, GGML+Whisper integration
This commit is contained in:
@@ -0,0 +1,78 @@
|
||||
# Win Dictation — Fix Note 01
|
||||
|
||||
**Every control renders as the blue Record pill**
|
||||
|
||||
**Applies to:** the single-surface UI rewrite described in `UI-and-Progress-Rebuild.md` (Part 1), after the first build.
|
||||
**Status:** root cause confirmed, one-line fix below.
|
||||
|
||||
---
|
||||
|
||||
## Symptom
|
||||
|
||||
After wiring up the single-surface paint, every control — **Record**, **Pin**, the **mic** and **model** selects, and **Copy / Paste / Clear** — renders as the *same* blue accent pill with a white dot. No labels, no distinct styles. (The window chrome, card, and scrollbar are correct; only the widgets are wrong.)
|
||||
|
||||
## The mistake
|
||||
|
||||
`Widget::kind` is declared without an initializer, and the widget array has static storage duration, so the whole array is zero-initialized:
|
||||
|
||||
```cpp
|
||||
struct Widget {
|
||||
WK kind; // <-- no initializer
|
||||
RectF r;
|
||||
bool hover = false;
|
||||
bool pressed = false;
|
||||
float anim = 0.0f;
|
||||
};
|
||||
static Widget g_w[ (int)WK::Transcript + 1 ]; // zero-filled → every kind == 0
|
||||
```
|
||||
|
||||
`WK::RecordHero` is the first enumerator, i.e. value `0`. So **every** slot's `kind` reads as `RecordHero`. `LayoutWidgets()` assigns each slot's *rectangle* (`g_w[(int)WK::Pin].r = …`, etc.) but never its `kind`. The paint loop dispatches on `kind`:
|
||||
|
||||
```cpp
|
||||
for (auto& w : g_w) {
|
||||
switch (w.kind) { // 0 for every widget
|
||||
case WK::RecordHero: DrawHero(g, w); break; // <-- all eight land here
|
||||
case WK::Copy: DrawGhost(g, w, L"Copy"); break; // unreachable
|
||||
case WK::SelAudio: DrawSelectSurface(...); break; // unreachable
|
||||
...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Result: `DrawHero` paints all eight widgets → eight identical blue pills.
|
||||
|
||||
## The required change
|
||||
|
||||
The array is **indexed by enum value** (`g_w[(int)WK::Pin]`, `g_w[(int)WK::Copy]`, …), so slot `i` must carry `kind == (WK)i`. Stamp the kinds once, at the top of `LayoutWidgets()` — it runs before the first paint and again on every resize / DPI change, so the invariant is always re-asserted:
|
||||
|
||||
```cpp
|
||||
void LayoutWidgets(int W, int H) {
|
||||
for (int i = 0; i < (int)std::size(g_w); ++i) // <-- ADD THESE TWO LINES
|
||||
g_w[i].kind = (WK)i; // slot index == kind
|
||||
|
||||
for (auto& w : g_w) w.r = RectF(0, 0, 0, 0);
|
||||
float s = g_dpiScale;
|
||||
// ... unchanged ...
|
||||
}
|
||||
```
|
||||
|
||||
Equivalent alternative: an `InitWidgets()` helper called once in `wWinMain` before the first `LayoutWidgets`/paint. Doing it *inside* `LayoutWidgets` is the most robust, because nothing can paint before layout has run.
|
||||
|
||||
**Invariant to preserve:** `g_w[i].kind == (WK)i`. As long as rectangles keep being assigned via `g_w[(int)WK::X].r`, the slot index always equals the enum value and this holds.
|
||||
|
||||
After this change, `DrawGhost` (Copy/Paste/Clear), `DrawPinSurface`, and `DrawSelectSurface` (mic/model) take over their slots, and you get the intended distinct, borderless, label-bearing controls.
|
||||
|
||||
## Secondary issue noticed (cosmetic — not fixed here)
|
||||
|
||||
The transcript **placeholder** ("Your transcription will appear here…") won't appear. `PaintSurface` draws it into the transcript rect, but the real multiline `EDIT` child window covers that rect and is opaque, so it hides the parent-painted text. `EM_SETCUEBANNER` only works on *single-line* edits. Clean fix: subclass the multiline `EDIT` and draw the cue in its own `WM_PAINT` when the control is empty and unfocused. Tracked as a separate follow-up.
|
||||
|
||||
## Verify after rebuilding
|
||||
|
||||
1. **Top row:** a wide blue **Record** pill + a quiet **Pin** toggle (accent only when pinned).
|
||||
2. **Bottom:** **mic** and **model** selects (label + chevron, faint at rest) and quiet **Copy / Paste / Clear** ghost buttons (text only at rest).
|
||||
3. **Hover** a ghost button → a soft fill fades in; **press** → slightly darker. No resting borders, no hairlines.
|
||||
4. Independent of this fix, run a transcription and confirm the progress **%** rises smoothly and the **"… s left"** value counts **down** (not up).
|
||||
|
||||
---
|
||||
|
||||
*Companion to `UI-and-Progress-Rebuild.md`. This note documents a correction to that guide's Part 1 scaffold; the guide itself is left unchanged.*
|
||||
Reference in New Issue
Block a user