统一ui
This commit is contained in:
@@ -170,6 +170,7 @@ int main(int argc, char* argv[]) {
|
||||
service.start(startup_error);
|
||||
NSApplication* application = [NSApplication sharedApplication];
|
||||
application.activationPolicy = NSApplicationActivationPolicyRegular;
|
||||
application.appearance = [NSAppearance appearanceNamed:NSAppearanceNameDarkAqua];
|
||||
ConnectorAppDelegate* delegate = [[ConnectorAppDelegate alloc] init];
|
||||
delegate.service = &service;
|
||||
delegate.startupError = [NSString stringWithUTF8String:startup_error.c_str()];
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#define NOMINMAX
|
||||
#include <windows.h>
|
||||
#include <commdlg.h>
|
||||
#include <windowsx.h>
|
||||
|
||||
#include "config.hpp"
|
||||
#include "connector_service.hpp"
|
||||
@@ -12,6 +13,16 @@
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr COLORREF kBackground = RGB(38, 37, 36);
|
||||
constexpr COLORREF kControlBackground = RGB(56, 55, 54);
|
||||
constexpr COLORREF kControlPressed = RGB(70, 69, 68);
|
||||
constexpr COLORREF kBorder = RGB(76, 75, 74);
|
||||
constexpr COLORREF kPrimaryText = RGB(238, 238, 238);
|
||||
constexpr COLORREF kSecondaryText = RGB(166, 164, 162);
|
||||
constexpr COLORREF kSuccessText = RGB(48, 209, 88);
|
||||
constexpr COLORREF kWarningText = RGB(255, 159, 10);
|
||||
constexpr COLORREF kErrorText = RGB(255, 69, 58);
|
||||
|
||||
connector::ConnectorService service;
|
||||
HWND path_field = nullptr;
|
||||
HWND status_label = nullptr;
|
||||
@@ -21,6 +32,89 @@ HWND automatic_button = nullptr;
|
||||
HWND save_button = nullptr;
|
||||
HWND connection_button = nullptr;
|
||||
std::string startup_error;
|
||||
UINT ui_dpi = 96;
|
||||
HFONT title_font = nullptr;
|
||||
HFONT body_font = nullptr;
|
||||
HBRUSH background_brush = nullptr;
|
||||
HBRUSH control_brush = nullptr;
|
||||
COLORREF status_color = kSuccessText;
|
||||
|
||||
int scaled(int value) {
|
||||
return MulDiv(value, static_cast<int>(ui_dpi), 96);
|
||||
}
|
||||
|
||||
void enable_dpi_awareness() {
|
||||
HMODULE user32 = GetModuleHandleW(L"user32.dll");
|
||||
using SetAwareness = BOOL(WINAPI*)(HANDLE);
|
||||
const auto set_awareness = reinterpret_cast<SetAwareness>(GetProcAddress(user32, "SetProcessDpiAwarenessContext"));
|
||||
if (set_awareness) set_awareness(reinterpret_cast<HANDLE>(static_cast<INT_PTR>(-4))); // PER_MONITOR_AWARE_V2
|
||||
using GetDpi = UINT(WINAPI*)();
|
||||
const auto get_dpi = reinterpret_cast<GetDpi>(GetProcAddress(user32, "GetDpiForSystem"));
|
||||
if (get_dpi) ui_dpi = get_dpi();
|
||||
}
|
||||
|
||||
HFONT make_font(int point_size, int weight) {
|
||||
return CreateFontW(-MulDiv(point_size, static_cast<int>(ui_dpi), 72), 0, 0, 0, weight, FALSE, FALSE, FALSE,
|
||||
DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, CLEARTYPE_QUALITY,
|
||||
DEFAULT_PITCH | FF_DONTCARE, L"Segoe UI");
|
||||
}
|
||||
|
||||
void use_dark_title_bar(HWND window) {
|
||||
HMODULE dwm = LoadLibraryW(L"dwmapi.dll");
|
||||
if (!dwm) return;
|
||||
using SetAttribute = HRESULT(WINAPI*)(HWND, DWORD, LPCVOID, DWORD);
|
||||
const auto set_attribute = reinterpret_cast<SetAttribute>(GetProcAddress(dwm, "DwmSetWindowAttribute"));
|
||||
if (set_attribute) {
|
||||
const BOOL enabled = TRUE;
|
||||
// Windows 10 20H1+ uses 20; older Windows 10 builds use 19. Both calls are safe to probe.
|
||||
if (FAILED(set_attribute(window, 20, &enabled, sizeof(enabled)))) {
|
||||
set_attribute(window, 19, &enabled, sizeof(enabled));
|
||||
}
|
||||
}
|
||||
FreeLibrary(dwm);
|
||||
}
|
||||
|
||||
HWND make_label(HWND parent, const wchar_t* text, int id, int x, int y, int width, int height, HFONT font) {
|
||||
HWND label = CreateWindowW(L"STATIC", text, WS_CHILD | WS_VISIBLE | SS_LEFT,
|
||||
scaled(x), scaled(y), scaled(width), scaled(height), parent,
|
||||
reinterpret_cast<HMENU>(static_cast<INT_PTR>(id)), nullptr, nullptr);
|
||||
SendMessageW(label, WM_SETFONT, reinterpret_cast<WPARAM>(font), TRUE);
|
||||
return label;
|
||||
}
|
||||
|
||||
HWND make_button(HWND parent, const wchar_t* text, int id, int x, int y, int width, int height) {
|
||||
HWND button = CreateWindowW(L"BUTTON", text, WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_OWNERDRAW,
|
||||
scaled(x), scaled(y), scaled(width), scaled(height), parent,
|
||||
reinterpret_cast<HMENU>(static_cast<INT_PTR>(id)), nullptr, nullptr);
|
||||
SendMessageW(button, WM_SETFONT, reinterpret_cast<WPARAM>(body_font), TRUE);
|
||||
return button;
|
||||
}
|
||||
|
||||
void draw_dark_button(const DRAWITEMSTRUCT& item) {
|
||||
RECT bounds = item.rcItem;
|
||||
HBRUSH fill = CreateSolidBrush((item.itemState & ODS_SELECTED) ? kControlPressed : kControlBackground);
|
||||
HPEN border = CreatePen(PS_SOLID, scaled(1), kBorder);
|
||||
const HGDIOBJ old_brush = SelectObject(item.hDC, fill);
|
||||
const HGDIOBJ old_pen = SelectObject(item.hDC, border);
|
||||
RoundRect(item.hDC, bounds.left, bounds.top, bounds.right, bounds.bottom, scaled(7), scaled(7));
|
||||
SelectObject(item.hDC, old_pen);
|
||||
SelectObject(item.hDC, old_brush);
|
||||
DeleteObject(border);
|
||||
DeleteObject(fill);
|
||||
|
||||
const int length = GetWindowTextLengthW(item.hwndItem);
|
||||
std::wstring text(static_cast<std::size_t>(length) + 1, L'\0');
|
||||
GetWindowTextW(item.hwndItem, text.data(), length + 1);
|
||||
text.resize(static_cast<std::size_t>(length));
|
||||
SetBkMode(item.hDC, TRANSPARENT);
|
||||
SetTextColor(item.hDC, kPrimaryText);
|
||||
SelectObject(item.hDC, body_font);
|
||||
DrawTextW(item.hDC, text.c_str(), length, &bounds, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
|
||||
if (item.itemState & ODS_FOCUS) {
|
||||
InflateRect(&bounds, -scaled(4), -scaled(4));
|
||||
DrawFocusRect(item.hDC, &bounds);
|
||||
}
|
||||
}
|
||||
|
||||
std::wstring wide(const std::string& value) {
|
||||
if (value.empty()) return {};
|
||||
@@ -48,10 +142,21 @@ std::wstring field_text(HWND field) {
|
||||
|
||||
void refresh_status() {
|
||||
std::wstring status;
|
||||
if (!startup_error.empty()) status = L"连接器未启动 · " + wide(startup_error);
|
||||
else if (!service.running()) status = L"连接器已断开";
|
||||
else status = connector::find_spine_executable().empty() ? L"连接器运行中 · 未找到 Spine" : L"连接器运行中 · Spine 已就绪";
|
||||
if (!startup_error.empty()) {
|
||||
status = L"● 连接器未启动 · " + wide(startup_error);
|
||||
status_color = kErrorText;
|
||||
} else if (!service.running()) {
|
||||
status = L"● 连接器已断开";
|
||||
status_color = kSecondaryText;
|
||||
} else if (connector::find_spine_executable().empty()) {
|
||||
status = L"● 连接器运行中 · 未找到 Spine";
|
||||
status_color = kWarningText;
|
||||
} else {
|
||||
status = L"● 连接器运行中 · Spine 已就绪";
|
||||
status_color = kSuccessText;
|
||||
}
|
||||
SetWindowTextW(status_label, status.c_str());
|
||||
InvalidateRect(status_label, nullptr, TRUE);
|
||||
SetWindowTextW(connection_button, service.running() ? L"断开连接" : L"启动连接");
|
||||
const bool show_path = connector::find_spine_executable().empty();
|
||||
for (HWND control : {path_label, path_field, browse_button, automatic_button, save_button}) {
|
||||
@@ -80,30 +185,47 @@ LRESULT CALLBACK window_proc(HWND window, UINT message, WPARAM wparam, LPARAM lp
|
||||
try {
|
||||
switch (message) {
|
||||
case WM_CREATE: {
|
||||
HFONT font = static_cast<HFONT>(GetStockObject(DEFAULT_GUI_FONT));
|
||||
HWND title = CreateWindowW(L"STATIC", L"Spine粒子连接器", WS_CHILD | WS_VISIBLE, 24, 20, 540, 34, window, nullptr, nullptr, nullptr);
|
||||
SendMessageW(title, WM_SETFONT, reinterpret_cast<WPARAM>(font), TRUE);
|
||||
HWND help = CreateWindowW(L"STATIC", L"应用打开期间,网页可调用本机 Spine 生成工程文件。", WS_CHILD | WS_VISIBLE, 24, 58, 540, 24, window, nullptr, nullptr, nullptr);
|
||||
SendMessageW(help, WM_SETFONT, reinterpret_cast<WPARAM>(font), TRUE);
|
||||
status_label = CreateWindowW(L"STATIC", L"", WS_CHILD | WS_VISIBLE, 24, 91, 540, 24, window, nullptr, nullptr, nullptr);
|
||||
SendMessageW(status_label, WM_SETFONT, reinterpret_cast<WPARAM>(font), TRUE);
|
||||
path_label = CreateWindowW(L"STATIC", L"Spine.com 路径", WS_CHILD | WS_VISIBLE, 24, 128, 160, 24, window, nullptr, nullptr, nullptr);
|
||||
SendMessageW(path_label, WM_SETFONT, reinterpret_cast<WPARAM>(font), TRUE);
|
||||
path_field = CreateWindowExW(WS_EX_CLIENTEDGE, L"EDIT", L"", WS_CHILD | WS_VISIBLE | ES_AUTOHSCROLL, 24, 153, 390, 30, window, reinterpret_cast<HMENU>(100), nullptr, nullptr);
|
||||
SendMessageW(path_field, WM_SETFONT, reinterpret_cast<WPARAM>(font), TRUE);
|
||||
make_label(window, L"Spine粒子连接器", 200, 28, 24, 560, 34, title_font);
|
||||
make_label(window, L"应用打开且连接已启动时,网页可调用本机 Spine 生成工程文件。", 201, 29, 65, 560, 24, body_font);
|
||||
status_label = make_label(window, L"", 202, 29, 101, 560, 24, body_font);
|
||||
path_label = make_label(window, L"Spine.com 路径", 203, 29, 140, 160, 24, body_font);
|
||||
path_field = CreateWindowW(L"EDIT", L"", WS_CHILD | WS_VISIBLE | WS_TABSTOP | WS_BORDER | ES_AUTOHSCROLL,
|
||||
scaled(29), scaled(168), scaled(390), scaled(32), window,
|
||||
reinterpret_cast<HMENU>(100), nullptr, nullptr);
|
||||
SendMessageW(path_field, WM_SETFONT, reinterpret_cast<WPARAM>(body_font), TRUE);
|
||||
SendMessageW(path_field, EM_SETMARGINS, EC_LEFTMARGIN | EC_RIGHTMARGIN, MAKELPARAM(scaled(8), scaled(8)));
|
||||
const auto config = connector::load_config();
|
||||
SetWindowTextW(path_field, wide(config.spine_executable).c_str());
|
||||
browse_button = CreateWindowW(L"BUTTON", L"选择 Spine.com", WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON, 425, 151, 145, 34, window, reinterpret_cast<HMENU>(101), nullptr, nullptr);
|
||||
SendMessageW(browse_button, WM_SETFONT, reinterpret_cast<WPARAM>(font), TRUE);
|
||||
automatic_button = CreateWindowW(L"BUTTON", L"恢复自动检测", WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON, 24, 202, 135, 34, window, reinterpret_cast<HMENU>(102), nullptr, nullptr);
|
||||
SendMessageW(automatic_button, WM_SETFONT, reinterpret_cast<WPARAM>(font), TRUE);
|
||||
save_button = CreateWindowW(L"BUTTON", L"保存路径", WS_CHILD | WS_VISIBLE | BS_DEFPUSHBUTTON, 435, 202, 135, 34, window, reinterpret_cast<HMENU>(103), nullptr, nullptr);
|
||||
SendMessageW(save_button, WM_SETFONT, reinterpret_cast<WPARAM>(font), TRUE);
|
||||
connection_button = CreateWindowW(L"BUTTON", L"断开连接", WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON, 435, 250, 135, 34, window, reinterpret_cast<HMENU>(104), nullptr, nullptr);
|
||||
SendMessageW(connection_button, WM_SETFONT, reinterpret_cast<WPARAM>(font), TRUE);
|
||||
browse_button = make_button(window, L"选择 Spine.com", 101, 430, 166, 160, 36);
|
||||
automatic_button = make_button(window, L"恢复自动检测", 102, 29, 212, 130, 36);
|
||||
save_button = make_button(window, L"保存路径", 103, 460, 212, 130, 36);
|
||||
connection_button = make_button(window, L"断开连接", 104, 460, 265, 130, 36);
|
||||
refresh_status();
|
||||
return 0;
|
||||
}
|
||||
case WM_CTLCOLORSTATIC: {
|
||||
HDC context = reinterpret_cast<HDC>(wparam);
|
||||
const int id = GetDlgCtrlID(reinterpret_cast<HWND>(lparam));
|
||||
SetBkMode(context, TRANSPARENT);
|
||||
if (id == 202) SetTextColor(context, status_color);
|
||||
else if (id == 201) SetTextColor(context, kSecondaryText);
|
||||
else SetTextColor(context, kPrimaryText);
|
||||
return reinterpret_cast<LRESULT>(background_brush);
|
||||
}
|
||||
case WM_CTLCOLOREDIT: {
|
||||
HDC context = reinterpret_cast<HDC>(wparam);
|
||||
SetBkColor(context, kControlBackground);
|
||||
SetTextColor(context, kPrimaryText);
|
||||
return reinterpret_cast<LRESULT>(control_brush);
|
||||
}
|
||||
case WM_DRAWITEM: {
|
||||
const auto* item = reinterpret_cast<const DRAWITEMSTRUCT*>(lparam);
|
||||
if (item && item->CtlType == ODT_BUTTON) {
|
||||
draw_dark_button(*item);
|
||||
return TRUE;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case WM_COMMAND:
|
||||
switch (LOWORD(wparam)) {
|
||||
case 101: {
|
||||
@@ -132,8 +254,21 @@ LRESULT CALLBACK window_proc(HWND window, UINT message, WPARAM wparam, LPARAM lp
|
||||
return 0;
|
||||
}
|
||||
break;
|
||||
case WM_ERASEBKGND: {
|
||||
RECT area{};
|
||||
GetClientRect(window, &area);
|
||||
FillRect(reinterpret_cast<HDC>(wparam), &area, background_brush);
|
||||
return TRUE;
|
||||
}
|
||||
case WM_CLOSE: DestroyWindow(window); return 0;
|
||||
case WM_DESTROY: service.stop(); PostQuitMessage(0); return 0;
|
||||
case WM_DESTROY:
|
||||
service.stop();
|
||||
if (title_font) DeleteObject(title_font);
|
||||
if (body_font) DeleteObject(body_font);
|
||||
if (background_brush) DeleteObject(background_brush);
|
||||
if (control_brush) DeleteObject(control_brush);
|
||||
PostQuitMessage(0);
|
||||
return 0;
|
||||
}
|
||||
return DefWindowProcW(window, message, wparam, lparam);
|
||||
} catch (...) {
|
||||
@@ -151,17 +286,26 @@ int WINAPI wWinMain(HINSTANCE instance, HINSTANCE previous, PWSTR command_line,
|
||||
} catch (...) {
|
||||
startup_error = "连接器启动失败";
|
||||
}
|
||||
enable_dpi_awareness();
|
||||
title_font = make_font(20, FW_SEMIBOLD);
|
||||
body_font = make_font(13, FW_NORMAL);
|
||||
background_brush = CreateSolidBrush(kBackground);
|
||||
control_brush = CreateSolidBrush(kControlBackground);
|
||||
const wchar_t* class_name = L"SpineParticleConnectorWindow";
|
||||
WNDCLASSW window_class{};
|
||||
window_class.lpfnWndProc = window_proc;
|
||||
window_class.hInstance = instance;
|
||||
window_class.hCursor = LoadCursor(nullptr, IDC_ARROW);
|
||||
window_class.hbrBackground = reinterpret_cast<HBRUSH>(COLOR_WINDOW + 1);
|
||||
window_class.hbrBackground = background_brush;
|
||||
window_class.lpszClassName = class_name;
|
||||
RegisterClassW(&window_class);
|
||||
RECT window_bounds{0, 0, scaled(620), scaled(326)};
|
||||
AdjustWindowRect(&window_bounds, WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX, FALSE);
|
||||
HWND window = CreateWindowW(class_name, L"Spine粒子连接器", WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX,
|
||||
CW_USEDEFAULT, CW_USEDEFAULT, 620, 350, nullptr, nullptr, instance, nullptr);
|
||||
CW_USEDEFAULT, CW_USEDEFAULT, window_bounds.right - window_bounds.left,
|
||||
window_bounds.bottom - window_bounds.top, nullptr, nullptr, instance, nullptr);
|
||||
if (!window) return 1;
|
||||
use_dark_title_bar(window);
|
||||
ShowWindow(window, show);
|
||||
UpdateWindow(window);
|
||||
MSG message{};
|
||||
|
||||
Binary file not shown.
Reference in New Issue
Block a user