#include "win/resize_ui.hpp" #include "win/ui_singleton.hpp" #include #include #include #include #include #include #include #include #include #include #include #pragma comment(lib, "Comdlg32.lib") #pragma comment(lib, "User32.lib") #pragma comment(lib, "Gdi32.lib") #pragma comment(lib, "Comctl32.lib") #pragma comment(lib, "Shell32.lib") #pragma comment(lib, "Ole32.lib") namespace media::win { static std::string wide_to_utf8(const std::wstring &w) { if (w.empty()) return {}; int n = WideCharToMultiByte(CP_UTF8, 0, w.c_str(), static_cast(w.size()), nullptr, 0, nullptr, nullptr); if (n <= 0) return {}; std::string s(static_cast(n), '\0'); WideCharToMultiByte(CP_UTF8, 0, w.c_str(), static_cast(w.size()), s.data(), n, nullptr, nullptr); return s; } static std::wstring utf8_to_wide(const std::string &s) { if (s.empty()) return L""; int n = MultiByteToWideChar(CP_UTF8, 0, s.c_str(), static_cast(s.size()), nullptr, 0); if (n <= 0) return L""; std::wstring w(static_cast(n), L'\0'); MultiByteToWideChar(CP_UTF8, 0, s.c_str(), static_cast(s.size()), w.data(), n); return w; } enum : int { IDC_STATIC_IN = 100, IDC_EDIT_IN = 101, IDC_BTN_IN = 102, IDC_STATIC_OUT = 103, IDC_EDIT_OUT = 104, IDC_BTN_OUT = 105, IDC_STATIC_MW = 106, IDC_EDIT_MW = 107, IDC_STATIC_MH = 108, IDC_EDIT_MH = 109, IDC_STATIC_FIT = 110, IDC_COMBO_FIT = 111, IDC_STATIC_Q = 112, IDC_EDIT_Q = 113, IDC_CHK_ENLARGE = 114, IDC_CHK_AUTOROT = 115, IDC_CHK_STRIP = 116, IDC_STATIC_PRESET = 117, IDC_COMBO_OUT_PRESET = 118, IDC_BTN_OK = IDOK, IDC_BTN_CANCEL = IDCANCEL, }; static HFONT g_msg_font{}; static void ensure_message_font() { if (g_msg_font) return; NONCLIENTMETRICSW ncm{}; ncm.cbSize = sizeof(ncm); if (SystemParametersInfoW(SPI_GETNONCLIENTMETRICS, sizeof(ncm), &ncm, 0)) g_msg_font = CreateFontIndirectW(&ncm.lfMessageFont); if (!g_msg_font) g_msg_font = static_cast(GetStockObject(DEFAULT_GUI_FONT)); } static BOOL CALLBACK set_child_font(HWND child, LPARAM font) { SendMessageW(child, WM_SETFONT, static_cast(font), TRUE); return TRUE; } static void apply_message_font(HWND root) { ensure_message_font(); SendMessageW(root, WM_SETFONT, reinterpret_cast(g_msg_font), TRUE); EnumChildWindows(root, set_child_font, reinterpret_cast(g_msg_font)); } struct UiState { HWND root{}; HWND h_in{}; HWND h_out{}; HWND h_btn_in{}; HWND h_btn_out{}; HWND h_mw{}; HWND h_mh{}; HWND h_fit{}; HWND h_q{}; HWND h_enlarge{}; HWND h_autorot{}; HWND h_strip{}; HWND h_out_preset{}; int y_preset{}; int x0{16}; int lw{108}; int gap{8}; int btn_w{90}; int margin_r{14}; int y_in{}; int y_out{}; ResizeOptions *opt{}; std::string *in_path{}; std::string *out_path{}; bool accepted{false}; }; static void layout_stretch_path_rows(UiState *st, int client_w) { if (!st || !st->h_in || !st->h_out || client_w < 320) return; const int edit_x = st->x0 + st->lw + st->gap; const int browse_x = client_w - st->margin_r - st->btn_w; if (browse_x < edit_x + 80) return; const int ew = browse_x - st->gap - edit_x; const int eh = 26; MoveWindow(st->h_in, edit_x, st->y_in, ew, eh, TRUE); MoveWindow(st->h_btn_in, browse_x, st->y_in, st->btn_w, 28, TRUE); if (st->h_out_preset) MoveWindow(st->h_out_preset, edit_x, st->y_preset, ew, 180, TRUE); MoveWindow(st->h_out, edit_x, st->y_out, ew, eh, TRUE); MoveWindow(st->h_btn_out, browse_x, st->y_out, st->btn_w, 28, TRUE); } static void set_utf8_edit(HWND h, const std::string &utf8) { SetWindowTextW(h, utf8_to_wide(utf8).c_str()); } static std::string get_utf8_edit(HWND h) { const int n = GetWindowTextLengthW(h); if (n <= 0) return {}; std::wstring w(static_cast(n) + 1, L'\0'); GetWindowTextW(h, w.data(), n + 1); w.resize(static_cast(n)); return wide_to_utf8(w); } /** Append semicolon-separated paths (same convention as --src / singleton bridge). */ static void merge_utf8_paths_into_edit(HWND h_edit, const std::vector &add_utf8) { if (add_utf8.empty()) return; std::wstring cur; const int n = GetWindowTextLengthW(h_edit); if (n > 0) { cur.assign(static_cast(n) + 1, L'\0'); GetWindowTextW(h_edit, cur.data(), n + 1); cur.resize(static_cast(n)); } for (const auto &p : add_utf8) { if (p.empty()) continue; std::wstring w = utf8_to_wide(p); if (w.empty()) continue; if (!cur.empty()) cur += L';'; cur += w; } SetWindowTextW(h_edit, cur.c_str()); } static void append_hdrop_to_input_edit(HWND h_edit, HDROP hdrop) { std::vector paths; const UINT nfiles = DragQueryFileW(hdrop, 0xFFFFFFFF, nullptr, 0); paths.reserve(nfiles); for (UINT i = 0; i < nfiles; ++i) { const UINT len = DragQueryFileW(hdrop, i, nullptr, 0); if (len == 0) continue; std::wstring w(static_cast(len) + 1, L'\0'); if (DragQueryFileW(hdrop, i, w.data(), len + 1) == 0) continue; w.resize(len); paths.push_back(wide_to_utf8(w)); } DragFinish(hdrop); merge_utf8_paths_into_edit(h_edit, paths); } static LRESULT CALLBACK in_edit_drop_subclass(HWND h, UINT msg, WPARAM wp, LPARAM lp, UINT_PTR subclass_id, DWORD_PTR parent_hwnd) { if (msg == WM_DROPFILES) { SendMessageW(reinterpret_cast(parent_hwnd), WM_DROPFILES, wp, 0); return 0; } if (msg == WM_NCDESTROY) RemoveWindowSubclass(h, in_edit_drop_subclass, subclass_id); return DefSubclassProc(h, msg, wp, lp); } static void browse_open(HWND owner, HWND h_edit) { wchar_t buf[MAX_PATH * 4]{}; OPENFILENAMEW of{}; of.lStructSize = sizeof(of); of.hwndOwner = owner; of.lpstrFilter = L"Images\0*.jpg;*.jpeg;*.png;*.webp;*.tif;*.tiff;*.bmp;*.gif;*.avif;*.heic;*.jpe;*.jfif;*.jf\0" L"All\0*.*\0\0"; of.nFilterIndex = 1; of.lpstrFile = buf; of.nMaxFile = MAX_PATH * 4; of.Flags = OFN_FILEMUSTEXIST | OFN_PATHMUSTEXIST | OFN_EXPLORER; if (GetOpenFileNameW(&of)) SetWindowTextW(h_edit, buf); } /** Longest existing directory prefix of `raw` (or parent if `raw` is a file path). Used to seed folder dialog. */ static std::wstring folder_browse_seed_from_text(const wchar_t *raw) { if (!raw || !raw[0]) return {}; std::wstring s(raw); while (!s.empty() && (s.back() == L' ' || s.back() == L'\t')) s.pop_back(); if (s.empty()) return {}; DWORD attr = GetFileAttributesW(s.c_str()); if (attr != INVALID_FILE_ATTRIBUTES) { if (attr & FILE_ATTRIBUTE_DIRECTORY) return s; const size_t pos = s.find_last_of(L"\\/"); if (pos != std::wstring::npos && pos > 0) return s.substr(0, pos); return {}; } for (int depth = 0; depth < 128; ++depth) { size_t pos = s.find_last_of(L"\\/"); if (pos == std::wstring::npos) break; if (pos == 0) { s = s.substr(0, 1); break; } s.resize(pos); if (s.size() == 2 && s[1] == L':') s += L'\\'; attr = GetFileAttributesW(s.c_str()); if (attr != INVALID_FILE_ATTRIBUTES && (attr & FILE_ATTRIBUTE_DIRECTORY)) return s; } return {}; } static int CALLBACK browse_output_folder_proc(HWND hwnd, UINT msg, LPARAM, LPARAM data) { if (msg == BFFM_INITIALIZED && data) SendMessageW(hwnd, BFFM_SETSELECTIONW, FALSE, data); return 0; } static void browse_output_folder(HWND owner, HWND h_edit) { wchar_t raw[MAX_PATH * 4]{}; GetWindowTextW(h_edit, raw, static_cast(sizeof(raw) / sizeof(raw[0]))); std::wstring seed = folder_browse_seed_from_text(raw); std::vector seed_z(seed.begin(), seed.end()); seed_z.push_back(L'\0'); BROWSEINFOW bi{}; bi.hwndOwner = owner; bi.lpszTitle = L"Choose output folder (files keep their names)"; bi.ulFlags = BIF_RETURNONLYFSDIRS | BIF_NEWDIALOGSTYLE; wchar_t display_name[MAX_PATH]{}; bi.pszDisplayName = display_name; if (!seed.empty()) { bi.lpfn = browse_output_folder_proc; bi.lParam = reinterpret_cast(seed_z.data()); } PIDLIST_ABSOLUTE pidl = SHBrowseForFolderW(&bi); if (!pidl) return; wchar_t path[MAX_PATH * 4]{}; if (SHGetPathFromIDListW(pidl, path)) SetWindowTextW(h_edit, path); CoTaskMemFree(pidl); } static LRESULT CALLBACK UiWndProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) { UiState *st = reinterpret_cast(GetWindowLongPtrW(hwnd, GWLP_USERDATA)); switch (msg) { case WM_CREATE: { auto *cs = reinterpret_cast(lp); st = reinterpret_cast(cs->lpCreateParams); SetWindowLongPtrW(hwnd, GWLP_USERDATA, reinterpret_cast(st)); st->root = hwnd; st->x0 = 14; st->lw = 108; st->gap = 8; st->btn_w = 90; st->margin_r = 14; const int x0 = st->x0; const int lw = st->lw; const int gap = st->gap; const int btn_w = st->btn_w; const int margin_r = st->margin_r; int y = 10; const int ew = 300; const int btn_h = 26; const int eh = 22; const int edit_x = x0 + lw + gap; const int browse_x = edit_x + ew + gap; CreateWindowExW(0, L"STATIC", L"Input image:", WS_CHILD | WS_VISIBLE, x0, y + 1, lw, 18, hwnd, (HMENU)IDC_STATIC_IN, nullptr, nullptr); st->y_in = y; st->h_in = CreateWindowExW( WS_EX_CLIENTEDGE, L"EDIT", L"", WS_CHILD | WS_VISIBLE | ES_AUTOHSCROLL, edit_x, y, ew, eh + 2, hwnd, (HMENU)IDC_EDIT_IN, nullptr, nullptr); st->h_btn_in = CreateWindowExW(0, L"BUTTON", L"Browse…", WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON, browse_x, y, btn_w, btn_h, hwnd, (HMENU)IDC_BTN_IN, nullptr, nullptr); y += 34; st->y_preset = y; CreateWindowExW(0, L"STATIC", L"Output preset:", WS_CHILD | WS_VISIBLE, x0, y + 1, lw, 18, hwnd, (HMENU)IDC_STATIC_PRESET, nullptr, nullptr); st->h_out_preset = CreateWindowExW( WS_EX_CLIENTEDGE, L"COMBOBOX", L"", WS_CHILD | WS_VISIBLE | CBS_DROPDOWNLIST | CBS_HASSTRINGS, edit_x, y - 1, ew, 180, hwnd, (HMENU)IDC_COMBO_OUT_PRESET, nullptr, nullptr); { const wchar_t *presets[] = {L"Next to source (default name)", L"Next to source (stem_resized)", L"Use path below (custom)"}; for (const wchar_t *t : presets) SendMessageW(st->h_out_preset, CB_ADDSTRING, 0, reinterpret_cast(t)); int pr = 0; if (!st->out_path->empty()) pr = 2; else if (st->opt->output_stem_suffix == "_resized") pr = 1; SendMessageW(st->h_out_preset, CB_SETCURSEL, static_cast(pr), 0); } y += 34; CreateWindowExW(0, L"STATIC", L"Custom output folder:", WS_CHILD | WS_VISIBLE, x0, y + 1, lw + 20, 18, hwnd, (HMENU)IDC_STATIC_OUT, nullptr, nullptr); st->y_out = y; st->h_out = CreateWindowExW( WS_EX_CLIENTEDGE, L"EDIT", L"", WS_CHILD | WS_VISIBLE | ES_AUTOHSCROLL, edit_x, y, ew, eh + 2, hwnd, (HMENU)IDC_EDIT_OUT, nullptr, nullptr); st->h_btn_out = CreateWindowExW(0, L"BUTTON", L"Browse…", WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON, browse_x, y, btn_w, btn_h, hwnd, (HMENU)IDC_BTN_OUT, nullptr, nullptr); y += 34; CreateWindowExW(0, L"STATIC", L"Max width:", WS_CHILD | WS_VISIBLE, x0, y + 1, lw, 18, hwnd, (HMENU)IDC_STATIC_MW, nullptr, nullptr); wchar_t mw[32]{}; swprintf_s(mw, L"%d", st->opt->max_width); st->h_mw = CreateWindowExW(WS_EX_CLIENTEDGE, L"EDIT", mw, WS_CHILD | WS_VISIBLE | ES_AUTOHSCROLL, edit_x, y, 96, eh + 2, hwnd, (HMENU)IDC_EDIT_MW, nullptr, nullptr); const int mh_label_x = edit_x + 96 + gap + 16; CreateWindowExW(0, L"STATIC", L"Max height:", WS_CHILD | WS_VISIBLE, mh_label_x, y + 1, 88, 18, hwnd, (HMENU)IDC_STATIC_MH, nullptr, nullptr); wchar_t mh[32]{}; swprintf_s(mh, L"%d", st->opt->max_height); st->h_mh = CreateWindowExW(WS_EX_CLIENTEDGE, L"EDIT", mh, WS_CHILD | WS_VISIBLE | ES_AUTOHSCROLL, mh_label_x + 88 + gap, y, 96, eh + 2, hwnd, (HMENU)IDC_EDIT_MH, nullptr, nullptr); y += 34; CreateWindowExW(0, L"STATIC", L"Fit:", WS_CHILD | WS_VISIBLE, x0, y + 1, lw, 18, hwnd, (HMENU)IDC_STATIC_FIT, nullptr, nullptr); st->h_fit = CreateWindowExW(WS_EX_CLIENTEDGE, L"COMBOBOX", L"", WS_CHILD | WS_VISIBLE | CBS_DROPDOWNLIST | CBS_HASSTRINGS, edit_x, y - 1, 220, 180, hwnd, (HMENU)IDC_COMBO_FIT, nullptr, nullptr); const wchar_t *fits[] = {L"inside", L"cover", L"contain", L"fill", L"outside"}; for (const wchar_t *f : fits) SendMessageW(st->h_fit, CB_ADDSTRING, 0, reinterpret_cast(f)); { std::wstring cur = utf8_to_wide(st->opt->fit); for (auto &ch : cur) { if (ch >= L'A' && ch <= L'Z') ch = static_cast(ch - L'A' + L'a'); } int sel = 0; for (int i = 0; i < 5; ++i) { if (cur == fits[i]) { sel = i; break; } } SendMessageW(st->h_fit, CB_SETCURSEL, static_cast(sel), 0); } y += 32; CreateWindowExW(0, L"STATIC", L"Quality (1–100):", WS_CHILD | WS_VISIBLE, x0, y + 1, lw + 40, 18, hwnd, (HMENU)IDC_STATIC_Q, nullptr, nullptr); wchar_t q[16]{}; swprintf_s(q, L"%d", st->opt->quality); st->h_q = CreateWindowExW(WS_EX_CLIENTEDGE, L"EDIT", q, WS_CHILD | WS_VISIBLE | ES_AUTOHSCROLL, edit_x, y, 64, eh + 2, hwnd, (HMENU)IDC_EDIT_Q, nullptr, nullptr); y += 32; st->h_enlarge = CreateWindowExW(0, L"BUTTON", L"Allow enlargement", WS_CHILD | WS_VISIBLE | BS_AUTOCHECKBOX, x0, y, 210, 22, hwnd, (HMENU)IDC_CHK_ENLARGE, nullptr, nullptr); SendMessageW(st->h_enlarge, BM_SETCHECK, st->opt->without_enlargement ? BST_UNCHECKED : BST_CHECKED, 0); st->h_autorot = CreateWindowExW(0, L"BUTTON", L"EXIF autorotate", WS_CHILD | WS_VISIBLE | BS_AUTOCHECKBOX, x0 + 228, y, 188, 22, hwnd, (HMENU)IDC_CHK_AUTOROT, nullptr, nullptr); SendMessageW(st->h_autorot, BM_SETCHECK, st->opt->autorotate ? BST_CHECKED : BST_UNCHECKED, 0); y += 26; st->h_strip = CreateWindowExW(0, L"BUTTON", L"Strip metadata on save", WS_CHILD | WS_VISIBLE | BS_AUTOCHECKBOX, x0, y, 268, 22, hwnd, (HMENU)IDC_CHK_STRIP, nullptr, nullptr); SendMessageW(st->h_strip, BM_SETCHECK, st->opt->strip_metadata ? BST_CHECKED : BST_UNCHECKED, 0); y += 34; const int btn_row_w = 92; CreateWindowExW(0, L"BUTTON", L"OK", WS_CHILD | WS_VISIBLE | BS_DEFPUSHBUTTON, browse_x - btn_row_w - gap - btn_row_w, y, btn_row_w, 28, hwnd, (HMENU)IDC_BTN_OK, nullptr, nullptr); CreateWindowExW(0, L"BUTTON", L"Cancel", WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON, browse_x - btn_row_w, y, btn_row_w, 28, hwnd, (HMENU)IDC_BTN_CANCEL, nullptr, nullptr); set_utf8_edit(st->h_in, *st->in_path); set_utf8_edit(st->h_out, *st->out_path); media::win::set_ui_merge_input_edit(st->h_in); DragAcceptFiles(hwnd, TRUE); SetWindowSubclass(st->h_in, in_edit_drop_subclass, 1, reinterpret_cast(hwnd)); if (HWND h_lab_in = GetDlgItem(hwnd, IDC_STATIC_IN)) SetWindowSubclass(h_lab_in, in_edit_drop_subclass, 2, reinterpret_cast(hwnd)); apply_message_font(hwnd); { RECT cr{}; GetClientRect(hwnd, &cr); layout_stretch_path_rows(st, cr.right); } return 0; } case WM_COMMAND: { if (!st) return 0; const int id = LOWORD(wp); if (id == IDC_BTN_IN) { browse_open(hwnd, st->h_in); return 0; } if (id == IDC_BTN_OUT) { browse_output_folder(hwnd, st->h_out); return 0; } if (id == IDC_BTN_CANCEL) { st->accepted = false; DestroyWindow(hwnd); return 0; } if (id == IDC_BTN_OK) { *st->in_path = get_utf8_edit(st->h_in); if (st->in_path->empty()) { MessageBoxW(hwnd, L"Choose an input image.", L"media-img", MB_OK | MB_ICONWARNING); return 0; } { const int ps = static_cast(SendMessageW(st->h_out_preset, CB_GETCURSEL, 0, 0)); st->opt->output_stem_suffix.clear(); const std::string custom_out = get_utf8_edit(st->h_out); // Non-empty custom folder always applies (presets 0/1 used to clear out_path and ignored this field). if (!custom_out.empty()) { *st->out_path = custom_out; if (ps == 1) st->opt->output_stem_suffix = "_resized"; } else if (ps == 0) { *st->out_path = {}; } else if (ps == 1) { *st->out_path = {}; st->opt->output_stem_suffix = "_resized"; } else { *st->out_path = {}; } } wchar_t bmw[32]{}; GetWindowTextW(st->h_mw, bmw, 32); wchar_t bmh[32]{}; GetWindowTextW(st->h_mh, bmh, 32); st->opt->max_width = _wtoi(bmw); st->opt->max_height = _wtoi(bmh); if (st->opt->max_width < 0) st->opt->max_width = 0; if (st->opt->max_height < 0) st->opt->max_height = 0; const int fi = static_cast(SendMessageW(st->h_fit, CB_GETCURSEL, 0, 0)); const wchar_t *fits[] = {L"inside", L"cover", L"contain", L"fill", L"outside"}; if (fi >= 0 && fi < 5) st->opt->fit = wide_to_utf8(fits[fi]); wchar_t bq[32]{}; GetWindowTextW(st->h_q, bq, 32); st->opt->quality = _wtoi(bq); if (st->opt->quality < 1) st->opt->quality = 1; if (st->opt->quality > 100) st->opt->quality = 100; st->opt->without_enlargement = SendMessageW(st->h_enlarge, BM_GETCHECK, 0, 0) != BST_CHECKED; st->opt->autorotate = SendMessageW(st->h_autorot, BM_GETCHECK, 0, 0) == BST_CHECKED; st->opt->strip_metadata = SendMessageW(st->h_strip, BM_GETCHECK, 0, 0) == BST_CHECKED; st->accepted = true; DestroyWindow(hwnd); return 0; } return 0; } case WM_DROPFILES: { if (!st) return 0; append_hdrop_to_input_edit(st->h_in, reinterpret_cast(wp)); return 0; } case WM_CTLCOLORSTATIC: { HDC hdc = reinterpret_cast(wp); SetTextColor(hdc, GetSysColor(COLOR_WINDOWTEXT)); SetBkColor(hdc, GetSysColor(COLOR_3DFACE)); return reinterpret_cast(GetSysColorBrush(COLOR_3DFACE)); } case WM_CTLCOLOREDIT: case WM_CTLCOLORLISTBOX: { HDC hdc = reinterpret_cast(wp); SetTextColor(hdc, GetSysColor(COLOR_WINDOWTEXT)); SetBkColor(hdc, GetSysColor(COLOR_WINDOW)); return reinterpret_cast(GetSysColorBrush(COLOR_WINDOW)); } case WM_SIZE: { if (st && (wp == SIZE_RESTORED || wp == SIZE_MAXIMIZED)) { RECT cr{}; GetClientRect(hwnd, &cr); layout_stretch_path_rows(st, cr.right); } return DefWindowProcW(hwnd, msg, wp, lp); } case WM_GETMINMAXINFO: { auto *mmi = reinterpret_cast(lp); mmi->ptMinTrackSize.x = 460; mmi->ptMinTrackSize.y = 300; return 0; } case WM_CLOSE: if (st) st->accepted = false; DestroyWindow(hwnd); return 0; case WM_DESTROY: media::win::clear_ui_merge_input_edit(); PostQuitMessage(0); return 0; default: return DefWindowProcW(hwnd, msg, wp, lp); } } static const wchar_t kClassName[] = L"MediaImgResizeUi"; bool show_resize_ui(ResizeOptions &opt, std::string &input_path, std::string &output_path, const ResizeOptions &initial) { opt = initial; INITCOMMONCONTROLSEX icc{}; icc.dwSize = sizeof(icc); icc.dwICC = ICC_STANDARD_CLASSES | ICC_WIN95_CLASSES; InitCommonControlsEx(&icc); static bool reg = false; if (!reg) { WNDCLASSEXW wc{}; wc.cbSize = sizeof(wc); wc.lpfnWndProc = UiWndProc; wc.hInstance = GetModuleHandleW(nullptr); wc.hCursor = LoadCursor(nullptr, IDC_ARROW); wc.hbrBackground = reinterpret_cast(COLOR_3DFACE + 1); wc.lpszClassName = kClassName; if (!RegisterClassExW(&wc)) { if (GetLastError() != ERROR_CLASS_ALREADY_EXISTS) return false; } reg = true; } UiState state{}; state.opt = &opt; state.in_path = &input_path; state.out_path = &output_path; const int W = 520; const int H = 430; RECT r{}; SystemParametersInfoW(SPI_GETWORKAREA, 0, &r, 0); const int sx = r.left + ((r.right - r.left) - W) / 2; const int sy = r.top + ((r.bottom - r.top) - H) / 2; const DWORD style = (WS_OVERLAPPEDWINDOW & ~WS_MAXIMIZEBOX) | WS_CLIPCHILDREN; HWND hwnd = CreateWindowExW(WS_EX_WINDOWEDGE, kClassName, L"media-img — resize", style, sx, sy, W, H, nullptr, nullptr, GetModuleHandleW(nullptr), &state); if (!hwnd) return false; ShowWindow(hwnd, SW_SHOW); UpdateWindow(hwnd); MSG msg; while (GetMessageW(&msg, nullptr, 0, 0) > 0) { if (!IsDialogMessageW(hwnd, &msg)) { TranslateMessage(&msg); DispatchMessageW(&msg); } } return state.accepted; } } // namespace media::win