Fix 03: Replace inline history dropdown with proper PopupProc popup — scrollable, DPI-scaled, screen-edge aware, renders above EDIT control
This commit is contained in:
+127
-117
@@ -153,9 +153,6 @@ 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; };
|
||||
@@ -209,9 +206,22 @@ std::vector<std::wstring> g_audioItems; int g_audioSel = 0;
|
||||
std::vector<std::wstring> g_modelItems; int g_modelSel = 0;
|
||||
bool g_initializing = true;
|
||||
|
||||
struct PopupState { std::vector<std::wstring> items; int sel; int hot; HWND owner; int ctrlId; };
|
||||
struct PopupState {
|
||||
std::vector<std::wstring> items;
|
||||
int sel = -1;
|
||||
int hot = -1;
|
||||
HWND owner = nullptr;
|
||||
int ctrlId = 0;
|
||||
int scroll = 0;
|
||||
int visRows = 0;
|
||||
int rowH = 30;
|
||||
int pad = 3;
|
||||
int wheelAccum = 0;
|
||||
};
|
||||
static PopupState g_pop;
|
||||
|
||||
static const int kPopupMaxVisible = 8;
|
||||
|
||||
enum class WK { RecordHero, Pin, SettingsCog, Copy, Paste, Clear, SelAudio, History, Transcript };
|
||||
struct Widget {
|
||||
WK kind;
|
||||
@@ -639,20 +649,49 @@ void DrawProgress(Graphics& g, const RECT& r, float frac) {
|
||||
}
|
||||
}
|
||||
|
||||
static int PopupRowFromY(int y) {
|
||||
int row = (y - g_pop.pad) / g_pop.rowH;
|
||||
if (row < 0 || row >= g_pop.visRows) return -1;
|
||||
int abs = g_pop.scroll + row;
|
||||
if (abs >= (int)g_pop.items.size()) return -1;
|
||||
return abs;
|
||||
}
|
||||
|
||||
LRESULT CALLBACK PopupProc(HWND h, UINT m, WPARAM w, LPARAM l) {
|
||||
switch (m) {
|
||||
case WM_MOUSEMOVE: {
|
||||
int row = GET_Y_LPARAM(l) / 30;
|
||||
int row = PopupRowFromY(GET_Y_LPARAM(l));
|
||||
if (row != g_pop.hot) { g_pop.hot = row; InvalidateRect(h, nullptr, FALSE); }
|
||||
TRACKMOUSEEVENT t{ sizeof(t), TME_LEAVE, h, 0 }; TrackMouseEvent(&t);
|
||||
return 0;
|
||||
}
|
||||
case WM_MOUSELEAVE: g_pop.hot = -1; InvalidateRect(h, nullptr, FALSE); return 0;
|
||||
case WM_MOUSELEAVE:
|
||||
g_pop.hot = -1; InvalidateRect(h, nullptr, FALSE);
|
||||
return 0;
|
||||
case WM_MOUSEWHEEL: {
|
||||
int maxScroll = (int)g_pop.items.size() - g_pop.visRows;
|
||||
if (maxScroll <= 0) return 0;
|
||||
g_pop.wheelAccum += GET_WHEEL_DELTA_WPARAM(w);
|
||||
int steps = g_pop.wheelAccum / WHEEL_DELTA;
|
||||
if (steps != 0) {
|
||||
g_pop.wheelAccum -= steps * WHEEL_DELTA;
|
||||
g_pop.scroll -= steps * 3;
|
||||
g_pop.scroll = std::max(0, std::min(g_pop.scroll, maxScroll));
|
||||
POINT pt; GetCursorPos(&pt); ScreenToClient(h, &pt);
|
||||
g_pop.hot = PopupRowFromY(pt.y);
|
||||
InvalidateRect(h, nullptr, FALSE);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
case WM_KEYDOWN:
|
||||
if (w == VK_ESCAPE) DestroyWindow(h);
|
||||
return 0;
|
||||
case WM_LBUTTONUP: {
|
||||
int row = GET_Y_LPARAM(l) / 30;
|
||||
if (row >= 0 && row < (int)g_pop.items.size())
|
||||
int row = PopupRowFromY(GET_Y_LPARAM(l));
|
||||
if (row >= 0)
|
||||
PostMessageW(g_pop.owner, WM_APP_SELECT, (WPARAM)g_pop.ctrlId, (LPARAM)row);
|
||||
DestroyWindow(h); return 0;
|
||||
DestroyWindow(h);
|
||||
return 0;
|
||||
}
|
||||
case WM_ACTIVATE:
|
||||
if (LOWORD(w) == WA_INACTIVE) DestroyWindow(h);
|
||||
@@ -665,21 +704,41 @@ LRESULT CALLBACK PopupProc(HWND h, UINT m, WPARAM w, LPARAM l) {
|
||||
HBITMAP bmp = CreateCompatibleBitmap(hdc, rc.right, rc.bottom);
|
||||
HBITMAP old = (HBITMAP)SelectObject(mem, bmp);
|
||||
{
|
||||
Graphics g(mem); g.SetSmoothingMode(SmoothingModeAntiAlias);
|
||||
Graphics g(mem);
|
||||
g.SetSmoothingMode(SmoothingModeAntiAlias);
|
||||
g.SetTextRenderingHint(TextRenderingHintClearTypeGridFit);
|
||||
Rect all(0, 0, rc.right, rc.bottom);
|
||||
FillRound(g, T_CARD, all, 10); StrokeRound(g, T_FAINT, all, 10, 1.0f);
|
||||
Font f(mem, g_fUI);
|
||||
for (int i = 0; i < (int)g_pop.items.size(); ++i) {
|
||||
Rect row(3, i * 30 + 3, rc.right - 6, 28);
|
||||
FillRound(g, T_CARD, all, 10);
|
||||
StrokeRound(g, T_FAINT, all, 10, 1.0f);
|
||||
|
||||
int n = (int)g_pop.items.size();
|
||||
bool hasBar = n > g_pop.visRows;
|
||||
int barW = std::max(3, (int)(4 * g_dpiScale));
|
||||
int rowW = rc.right - 2 * g_pop.pad - (hasBar ? barW + g_pop.pad : 0);
|
||||
int last = std::min(n, g_pop.scroll + g_pop.visRows);
|
||||
|
||||
for (int i = g_pop.scroll; i < last; ++i) {
|
||||
int vis = i - g_pop.scroll;
|
||||
Rect row(g_pop.pad, g_pop.pad + vis * g_pop.rowH, rowW, g_pop.rowH - 2);
|
||||
if (i == g_pop.hot) FillRound(g, T_CARD_HI, row, 7);
|
||||
RectF tb((REAL)row.X + 9, (REAL)row.Y, (REAL)row.Width - 12, (REAL)row.Height);
|
||||
DrawTextC(g, g_pop.items[i].c_str(), f, (i == g_pop.sel) ? T_ACCENT : T_TEXT,
|
||||
DrawTextC(g, g_pop.items[i].c_str(), *g_gpUI,
|
||||
(i == g_pop.sel) ? T_ACCENT : T_TEXT,
|
||||
tb, StringAlignmentNear, StringAlignmentCenter);
|
||||
}
|
||||
if (hasBar) {
|
||||
int trackH = rc.bottom - 2 * g_pop.pad;
|
||||
int thumbH = std::max((int)(20 * g_dpiScale), trackH * g_pop.visRows / n);
|
||||
int maxScroll = n - g_pop.visRows;
|
||||
int thumbY = g_pop.pad + (trackH - thumbH) * g_pop.scroll / maxScroll;
|
||||
Rect thumb(rc.right - g_pop.pad - barW, thumbY, barW, thumbH);
|
||||
FillRound(g, T_DIM, thumb, barW / 2);
|
||||
}
|
||||
}
|
||||
BitBlt(hdc, 0, 0, rc.right, rc.bottom, mem, 0, 0, SRCCOPY);
|
||||
SelectObject(mem, old); DeleteObject(bmp); DeleteDC(mem);
|
||||
EndPaint(h, &ps); return 0;
|
||||
EndPaint(h, &ps);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return DefWindowProc(h, m, w, l);
|
||||
@@ -689,28 +748,60 @@ void ShowSelectPopup(HWND owner, int ctrlId, const std::vector<std::wstring>& it
|
||||
static bool reg = false;
|
||||
if (!reg) {
|
||||
WNDCLASSEXW wc{ sizeof(wc) };
|
||||
wc.lpfnWndProc = PopupProc;
|
||||
wc.hInstance = hInst;
|
||||
wc.hCursor = LoadCursor(nullptr, IDC_ARROW);
|
||||
wc.lpfnWndProc = PopupProc;
|
||||
wc.hInstance = hInst;
|
||||
wc.hCursor = LoadCursor(nullptr, IDC_ARROW);
|
||||
wc.hbrBackground = CreateSolidBrush(CR_SURFACE);
|
||||
wc.lpszClassName = L"DictPopup";
|
||||
RegisterClassExW(&wc);
|
||||
reg = true;
|
||||
}
|
||||
g_pop = { items, sel, -1, owner, ctrlId };
|
||||
POINT tl{ (LONG)anchor.X, (LONG)(anchor.Y + anchor.Height) };
|
||||
if (items.empty()) return;
|
||||
|
||||
float s = g_dpiScale;
|
||||
int rowH = (int)(30 * s);
|
||||
int pad = (int)(3 * s); if (pad < 3) pad = 3;
|
||||
int n = (int)items.size();
|
||||
int visRows = std::min(n, kPopupMaxVisible);
|
||||
|
||||
g_pop = PopupState{};
|
||||
g_pop.items = items;
|
||||
g_pop.sel = sel;
|
||||
g_pop.owner = owner;
|
||||
g_pop.ctrlId = ctrlId;
|
||||
g_pop.visRows = visRows;
|
||||
g_pop.rowH = rowH;
|
||||
g_pop.pad = pad;
|
||||
if (sel >= 0 && n > visRows)
|
||||
g_pop.scroll = std::max(0, std::min(sel - visRows / 2, n - visRows));
|
||||
|
||||
int h = visRows * rowH + 2 * pad;
|
||||
int wdt = std::max((int)anchor.Width, (int)(260 * s));
|
||||
|
||||
POINT tl{ (LONG)anchor.X, (LONG)anchor.Y };
|
||||
ClientToScreen(owner, &tl);
|
||||
int h = (int)items.size() * 30 + 6, wdt = (int)anchor.Width;
|
||||
int anchorTop = tl.y;
|
||||
int anchorBottom = tl.y + (int)anchor.Height;
|
||||
int x = tl.x;
|
||||
|
||||
HMONITOR mon = MonitorFromWindow(owner, MONITOR_DEFAULTTONEAREST);
|
||||
MONITORINFO mi{ sizeof(mi) };
|
||||
GetMonitorInfo(mon, &mi);
|
||||
int y;
|
||||
if (anchorBottom + 2 + h <= mi.rcWork.bottom) y = anchorBottom + 2;
|
||||
else y = anchorTop - h - 2;
|
||||
if (y < mi.rcWork.top) y = mi.rcWork.top;
|
||||
if (x + wdt > mi.rcWork.right) x = mi.rcWork.right - wdt;
|
||||
if (x < mi.rcWork.left) x = mi.rcWork.left;
|
||||
|
||||
HWND p = CreateWindowExW(WS_EX_TOOLWINDOW | WS_EX_TOPMOST, L"DictPopup", L"",
|
||||
WS_POPUP, tl.x, tl.y + 2, wdt, h, owner, nullptr, hInst, nullptr);
|
||||
if (!p) {
|
||||
wchar_t dbg[128];
|
||||
swprintf_s(dbg, L"Popup failed: w=%d h=%d x=%d y=%d err=%d", wdt, h, tl.x, tl.y, GetLastError());
|
||||
MessageBoxW(owner, dbg, L"Popup Error", MB_OK);
|
||||
return;
|
||||
}
|
||||
int corner = DWMWCP_ROUND; DwmSetWindowAttribute(p, DWMWA_WINDOW_CORNER_PREFERENCE, &corner, sizeof(corner));
|
||||
ShowWindow(p, SW_SHOWNA); SetFocus(p);
|
||||
WS_POPUP, x, y, wdt, h, owner, nullptr, hInst, nullptr);
|
||||
if (!p) return;
|
||||
|
||||
int corner = DWMWCP_ROUND;
|
||||
DwmSetWindowAttribute(p, DWMWA_WINDOW_CORNER_PREFERENCE, &corner, sizeof(corner));
|
||||
ShowWindow(p, SW_SHOWNA);
|
||||
SetFocus(p);
|
||||
}
|
||||
|
||||
void LayoutControls(HWND h, int W, int H) {
|
||||
@@ -973,40 +1064,6 @@ 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);
|
||||
@@ -1057,21 +1114,6 @@ 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;
|
||||
@@ -1131,11 +1173,12 @@ 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: {
|
||||
if (g_histOpen >= 0) { g_histOpen = -1; InvalidateRect(hWnd, nullptr, FALSE); break; }
|
||||
g_history = LoadHistoryIndex();
|
||||
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);
|
||||
std::vector<std::wstring> labels;
|
||||
labels.reserve(g_history.size());
|
||||
for (const auto& e : g_history) labels.push_back(e.label);
|
||||
ShowSelectPopup(hWnd, ID_SEL_HISTORY, labels, -1, g_w[(int)WK::History].r);
|
||||
break;
|
||||
}
|
||||
case WK::SettingsCog: SwitchView(hWnd, View::Settings); break;
|
||||
@@ -1413,10 +1456,6 @@ 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;
|
||||
@@ -1429,14 +1468,9 @@ 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;
|
||||
@@ -1446,25 +1480,6 @@ 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);
|
||||
@@ -1481,11 +1496,6 @@ 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: {
|
||||
|
||||
Reference in New Issue
Block a user