From ee76328daf0361d834cd8a01d53ee5b3e50fc1ed Mon Sep 17 00:00:00 2001 From: Win Dictation Dev Date: Thu, 11 Jun 2026 17:59:48 +1200 Subject: [PATCH] Replace history popup with inline dropdown painted on main surface --- src/main.cpp | 101 +++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 93 insertions(+), 8 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 3d77720..186337d 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -153,6 +153,9 @@ RectF g_sBack, g_sSave, g_sCancel; RECT g_sContent = {0,0,0,0}; struct CatRowRect { RectF row, btn; }; int g_setHot = -1; +int g_histOpen = -1; +int g_histScroll = 0; +int g_histHot = -1; Downloader g_dl; enum class DlState { NotInstalled, Downloading, Installed }; struct CatState { DlState state = DlState::NotInstalled; int pct = 0; }; @@ -651,7 +654,9 @@ LRESULT CALLBACK PopupProc(HWND h, UINT m, WPARAM w, LPARAM l) { PostMessageW(g_pop.owner, WM_APP_SELECT, (WPARAM)g_pop.ctrlId, (LPARAM)row); DestroyWindow(h); return 0; } - case WM_KILLFOCUS: DestroyWindow(h); return 0; + case WM_ACTIVATE: + if (LOWORD(w) == WA_INACTIVE) DestroyWindow(h); + return 0; case WM_ERASEBKGND: return 1; case WM_PAINT: { PAINTSTRUCT ps; HDC hdc = BeginPaint(h, &ps); @@ -968,6 +973,40 @@ void PaintSurface(HWND hwnd) { } } + if (g_histOpen >= 0 && g_view == View::Main) { + RectF& anchor = g_w[(int)WK::History].r; + float s = g_dpiScale; + REAL dropW = (REAL)(int)(anchor.Width); + REAL dropX = anchor.X; + REAL dropY = anchor.Y + anchor.Height + 2 * s; + int maxVis = std::min(g_histOpen, 6); + REAL rowH = 30 * s; + REAL dropH = maxVis * rowH + 6 * s; + + Rect dropR((int)dropX, (int)dropY, (int)dropW, (int)dropH); + FillRound(g, T_CARD, dropR, (int)(10 * s)); + StrokeRound(g, T_FAINT, dropR, (int)(10 * s), 1.0f); + + int startIdx = g_histScroll; + for (int i = 0; i < maxVis && (startIdx + i) < (int)g_history.size(); ++i) { + int gi = startIdx + i; + Rect rowR((int)(dropX + 3 * s), (int)(dropY + 3 * s + i * rowH), + (int)(dropW - 6 * s), (int)(rowH)); + if (gi == g_histHot) + FillRound(g, T_CARD_HI, rowR, (int)(7 * s)); + RectF tb((REAL)rowR.X + 6 * s, (REAL)rowR.Y, (REAL)rowR.Width - 12 * s, (REAL)rowR.Height); + DrawTextC(g, g_history[gi].label.c_str(), *g_gpUI, T_TEXT, + tb, StringAlignmentNear, StringAlignmentCenter); + } + if (g_histOpen > maxVis) { + REAL scrollH = dropH - 2 * s; + REAL thumbH = scrollH * maxVis / g_histOpen; + REAL thumbY = dropY + 1 * s + (scrollH - thumbH) * g_histScroll / (g_histOpen - maxVis); + Rect thumb((int)(dropX + dropW - 6 * s), (int)thumbY, (int)(4 * s), (int)thumbH); + FillRound(g, T_DIM, thumb, 3); + } + } + RECT vr = g_vuRect; RectF stripRect((REAL)vr.left, (REAL)vr.top, (REAL)(vr.right - vr.left), (REAL)(vr.bottom - vr.top)); DrawStatusStrip(g, stripRect); @@ -1018,6 +1057,21 @@ void PaintSurface(HWND hwnd) { EndPaint(hwnd, &ps); } +static int HistDropHitTest(POINT p) { + if (g_histOpen <= 0) return -1; + RectF& anchor = g_w[(int)WK::History].r; + float s = g_dpiScale; + REAL dropX = anchor.X, dropY = anchor.Y + anchor.Height + 2*s; + REAL dropW = anchor.Width, rowH = 30*s; + REAL dropH = std::min(g_histOpen, 6) * rowH + 6*s; + if (p.x < dropX || p.x > dropX + dropW || p.y < dropY || p.y > dropY + dropH) + return -1; + int idx = (int)((p.y - dropY - 3*s) / rowH); + int realIdx = g_histScroll + idx; + if (realIdx < 0 || realIdx >= (int)g_history.size()) return -1; + return realIdx; +} + int HitTest(POINT p) { for (int i = 0; i < (int)std::size(g_w); ++i) if (g_w[i].kind != WK::Transcript && g_w[i].r.Contains((REAL)p.x, (REAL)p.y)) return i; @@ -1077,13 +1131,11 @@ void OnClick(HWND hWnd, WK kind) { ShowSelectPopup(hWnd, ID_SEL_AUDIO, g_audioItems, g_audioSel, g_w[(int)WK::SelAudio].r); break; case WK::History: { - wchar_t dbg[64]; - swprintf_s(dbg, L"History: %d entries", (int)g_history.size()); - SetStatus(hWnd, dbg); - std::vector items; - for (auto& e : g_history) items.push_back(e.label); - if (items.empty()) items.push_back(L"No history yet"); - ShowSelectPopup(hWnd, ID_SEL_HISTORY, items, -1, g_w[(int)WK::History].r); + if (g_histOpen >= 0) { g_histOpen = -1; InvalidateRect(hWnd, nullptr, FALSE); break; } + if (g_history.empty()) { SetStatus(hWnd, L"No history yet"); break; } + g_histOpen = (int)g_history.size(); + g_histScroll = 0; g_histHot = -1; + InvalidateRect(hWnd, nullptr, FALSE); break; } case WK::SettingsCog: SwitchView(hWnd, View::Settings); break; @@ -1361,6 +1413,10 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) return 0; } POINT p{ GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) }; + if (g_histOpen > 0) { + int dh = HistDropHitTest(p); + if (dh != g_histHot) { g_histHot = dh; InvalidateRect(hWnd, nullptr, FALSE); } + } int hot = HitTest(p); if (hot != g_hot) { if (g_hot >= 0) g_w[g_hot].hover = false; @@ -1373,9 +1429,14 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) } case WM_MOUSELEAVE: if (g_hot >= 0) { g_w[g_hot].hover = false; g_hot = -1; EnsureAnimating(hWnd); } + if (g_histHot >= 0) { g_histHot = -1; InvalidateRect(hWnd, nullptr, FALSE); } return 0; case WM_LBUTTONDOWN: if (g_view == View::Settings) return 0; + if (g_histOpen > 0) { + POINT p{ GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) }; + if (HistDropHitTest(p) < 0) { g_histOpen = -1; g_histHot = -1; g_active = -1; InvalidateRect(hWnd, nullptr, FALSE); return 0; } + } g_active = g_hot; if (g_active >= 0) { g_w[g_active].pressed = true; SetCapture(hWnd); EnsureAnimating(hWnd); } return 0; @@ -1385,6 +1446,25 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) OnSettingsClick(hWnd, p); return 0; } + if (g_histOpen > 0) { + POINT p{ GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) }; + int dh = HistDropHitTest(p); + if (dh >= 0 && dh < (int)g_history.size()) { + std::wstring cur = GetEditText(hWnd); + if (!cur.empty() && cur != g_lastLoadedText) + ArchiveSession(cur); + std::wstring text = ReadFileUtf8(g_history[dh].path); + SetWindowTextW(GetDlgItem(hWnd, ID_EDIT_TEXT), text.c_str()); + g_lastLoadedText = text; + g_editDirty = false; + g_history = LoadHistoryIndex(); + UpdatePlaceholder(hWnd); + SetStatus(hWnd, L"Loaded from history"); + } + g_histOpen = -1; g_histHot = -1; + InvalidateRect(hWnd, nullptr, FALSE); + return 0; + } ReleaseCapture(); POINT p{ GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) }; if (g_active >= 0 && HitTest(p) == g_active) OnClick(hWnd, g_w[g_active].kind); @@ -1401,6 +1481,11 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) LayoutSettings(rc.right, rc.bottom); InvalidateRect(hWnd, nullptr, FALSE); } + if (g_histOpen > 6) { + g_histScroll -= GET_WHEEL_DELTA_WPARAM(wParam) / 60; + g_histScroll = std::max(0, std::min(g_histScroll, g_histOpen - 6)); + InvalidateRect(hWnd, nullptr, FALSE); + } return 0; case WM_DPICHANGED: {