169 lines
7.5 KiB
C++
169 lines
7.5 KiB
C++
#ifdef _WIN32
|
|
|
|
#define NOMINMAX
|
|
#include <windows.h>
|
|
#include <commdlg.h>
|
|
|
|
#include "config.hpp"
|
|
#include "connector_service.hpp"
|
|
#include "platform.hpp"
|
|
|
|
#include <string>
|
|
|
|
namespace {
|
|
|
|
connector::ConnectorService service;
|
|
HWND path_field = nullptr;
|
|
HWND status_label = nullptr;
|
|
HWND path_label = nullptr;
|
|
HWND browse_button = nullptr;
|
|
HWND automatic_button = nullptr;
|
|
HWND save_button = nullptr;
|
|
HWND connection_button = nullptr;
|
|
std::string startup_error;
|
|
|
|
std::wstring wide(const std::string& value) {
|
|
if (value.empty()) return {};
|
|
const int size = MultiByteToWideChar(CP_UTF8, 0, value.data(), static_cast<int>(value.size()), nullptr, 0);
|
|
std::wstring result(size, L'\0');
|
|
MultiByteToWideChar(CP_UTF8, 0, value.data(), static_cast<int>(value.size()), result.data(), size);
|
|
return result;
|
|
}
|
|
|
|
std::string utf8(const std::wstring& value) {
|
|
if (value.empty()) return {};
|
|
const int size = WideCharToMultiByte(CP_UTF8, 0, value.data(), static_cast<int>(value.size()), nullptr, 0, nullptr, nullptr);
|
|
std::string result(size, '\0');
|
|
WideCharToMultiByte(CP_UTF8, 0, value.data(), static_cast<int>(value.size()), result.data(), size, nullptr, nullptr);
|
|
return result;
|
|
}
|
|
|
|
std::wstring field_text(HWND field) {
|
|
const int length = GetWindowTextLengthW(field);
|
|
std::wstring value(static_cast<std::size_t>(length) + 1, L'\0');
|
|
GetWindowTextW(field, value.data(), length + 1);
|
|
value.resize(static_cast<std::size_t>(length));
|
|
return value;
|
|
}
|
|
|
|
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 已就绪";
|
|
SetWindowTextW(status_label, status.c_str());
|
|
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}) {
|
|
if (control) ShowWindow(control, show_path ? SW_SHOW : SW_HIDE);
|
|
}
|
|
}
|
|
|
|
void save_path(HWND window) {
|
|
const std::string path = utf8(field_text(path_field));
|
|
if (!path.empty() && !connector::valid_spine_executable(path)) {
|
|
MessageBoxW(window, L"请选择实际存在的 Spine.com 文件。", L"Spine 路径无效", MB_OK | MB_ICONWARNING);
|
|
return;
|
|
}
|
|
try {
|
|
auto config = connector::load_config();
|
|
config.spine_executable = path;
|
|
connector::save_config(config);
|
|
refresh_status();
|
|
} catch (const std::exception& error) {
|
|
MessageBoxW(window, wide(error.what()).c_str(), L"保存失败", MB_OK | MB_ICONERROR);
|
|
}
|
|
}
|
|
|
|
LRESULT CALLBACK window_proc(HWND window, UINT message, WPARAM wparam, LPARAM lparam) {
|
|
(void)lparam;
|
|
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);
|
|
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);
|
|
refresh_status();
|
|
return 0;
|
|
}
|
|
case WM_COMMAND:
|
|
switch (LOWORD(wparam)) {
|
|
case 101: {
|
|
wchar_t filename[MAX_PATH] = L"";
|
|
OPENFILENAMEW dialog{};
|
|
dialog.lStructSize = sizeof(dialog);
|
|
dialog.hwndOwner = window;
|
|
dialog.lpstrFilter = L"Spine.com\0Spine.com\0所有文件\0*.*\0";
|
|
dialog.lpstrFile = filename;
|
|
dialog.nMaxFile = MAX_PATH;
|
|
dialog.Flags = OFN_FILEMUSTEXIST | OFN_PATHMUSTEXIST;
|
|
if (GetOpenFileNameW(&dialog)) SetWindowTextW(path_field, filename);
|
|
return 0;
|
|
}
|
|
case 102: SetWindowTextW(path_field, L""); save_path(window); return 0;
|
|
case 103: save_path(window); return 0;
|
|
case 104:
|
|
if (service.running()) {
|
|
service.stop();
|
|
startup_error.clear();
|
|
} else {
|
|
startup_error.clear();
|
|
service.start(startup_error);
|
|
}
|
|
refresh_status();
|
|
return 0;
|
|
}
|
|
break;
|
|
case WM_CLOSE: DestroyWindow(window); return 0;
|
|
case WM_DESTROY: service.stop(); PostQuitMessage(0); return 0;
|
|
}
|
|
return DefWindowProcW(window, message, wparam, lparam);
|
|
}
|
|
|
|
} // namespace
|
|
|
|
int WINAPI wWinMain(HINSTANCE instance, HINSTANCE previous, PWSTR command_line, int show) {
|
|
(void)previous;
|
|
(void)command_line;
|
|
service.start(startup_error);
|
|
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.lpszClassName = class_name;
|
|
RegisterClassW(&window_class);
|
|
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);
|
|
if (!window) return 1;
|
|
ShowWindow(window, show);
|
|
UpdateWindow(window);
|
|
MSG message{};
|
|
while (GetMessageW(&message, nullptr, 0, 0) > 0) {
|
|
TranslateMessage(&message);
|
|
DispatchMessageW(&message);
|
|
}
|
|
service.stop();
|
|
return static_cast<int>(message.wParam);
|
|
}
|
|
|
|
#endif
|