mono/packages/media/cpp/src/win/resize_ui.cpp

632 lines
23 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include "win/resize_ui.hpp"
#include "win/ui_singleton.hpp"
#include <windows.h>
#include <winerror.h>
#include <shellapi.h>
#include <commctrl.h>
#include <commdlg.h>
#include <shlobj.h>
#include <objbase.h>
#include <cstdio>
#include <cstring>
#include <string>
#include <vector>
#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<int>(w.size()), nullptr, 0, nullptr, nullptr);
if (n <= 0)
return {};
std::string s(static_cast<size_t>(n), '\0');
WideCharToMultiByte(CP_UTF8, 0, w.c_str(), static_cast<int>(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<int>(s.size()), nullptr, 0);
if (n <= 0)
return L"";
std::wstring w(static_cast<size_t>(n), L'\0');
MultiByteToWideChar(CP_UTF8, 0, s.c_str(), static_cast<int>(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<HFONT>(GetStockObject(DEFAULT_GUI_FONT));
}
static BOOL CALLBACK set_child_font(HWND child, LPARAM font) {
SendMessageW(child, WM_SETFONT, static_cast<WPARAM>(font), TRUE);
return TRUE;
}
static void apply_message_font(HWND root) {
ensure_message_font();
SendMessageW(root, WM_SETFONT, reinterpret_cast<WPARAM>(g_msg_font), TRUE);
EnumChildWindows(root, set_child_font, reinterpret_cast<LPARAM>(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<size_t>(n) + 1, L'\0');
GetWindowTextW(h, w.data(), n + 1);
w.resize(static_cast<size_t>(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<std::string> &add_utf8) {
if (add_utf8.empty())
return;
std::wstring cur;
const int n = GetWindowTextLengthW(h_edit);
if (n > 0) {
cur.assign(static_cast<size_t>(n) + 1, L'\0');
GetWindowTextW(h_edit, cur.data(), n + 1);
cur.resize(static_cast<size_t>(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<std::string> 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<size_t>(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<HWND>(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<int>(sizeof(raw) / sizeof(raw[0])));
std::wstring seed = folder_browse_seed_from_text(raw);
std::vector<wchar_t> 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<LPARAM>(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<UiState *>(GetWindowLongPtrW(hwnd, GWLP_USERDATA));
switch (msg) {
case WM_CREATE: {
auto *cs = reinterpret_cast<CREATESTRUCTW *>(lp);
st = reinterpret_cast<UiState *>(cs->lpCreateParams);
SetWindowLongPtrW(hwnd, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(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<LPARAM>(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<WPARAM>(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<LPARAM>(f));
{
std::wstring cur = utf8_to_wide(st->opt->fit);
for (auto &ch : cur) {
if (ch >= L'A' && ch <= L'Z')
ch = static_cast<wchar_t>(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<WPARAM>(sel), 0);
}
y += 32;
CreateWindowExW(0, L"STATIC", L"Quality (1100):", 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<DWORD_PTR>(hwnd));
if (HWND h_lab_in = GetDlgItem(hwnd, IDC_STATIC_IN))
SetWindowSubclass(h_lab_in, in_edit_drop_subclass, 2, reinterpret_cast<DWORD_PTR>(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<int>(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<int>(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<HDROP>(wp));
return 0;
}
case WM_CTLCOLORSTATIC: {
HDC hdc = reinterpret_cast<HDC>(wp);
SetTextColor(hdc, GetSysColor(COLOR_WINDOWTEXT));
SetBkColor(hdc, GetSysColor(COLOR_3DFACE));
return reinterpret_cast<LRESULT>(GetSysColorBrush(COLOR_3DFACE));
}
case WM_CTLCOLOREDIT:
case WM_CTLCOLORLISTBOX: {
HDC hdc = reinterpret_cast<HDC>(wp);
SetTextColor(hdc, GetSysColor(COLOR_WINDOWTEXT));
SetBkColor(hdc, GetSysColor(COLOR_WINDOW));
return reinterpret_cast<LRESULT>(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<MINMAXINFO *>(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<HBRUSH>(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