#ifdef _WIN32 #define NOMINMAX #include #include #include "config.hpp" #include "connector_service.hpp" #include "platform.hpp" #include 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(value.size()), nullptr, 0); std::wstring result(size, L'\0'); MultiByteToWideChar(CP_UTF8, 0, value.data(), static_cast(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(value.size()), nullptr, 0, nullptr, nullptr); std::string result(size, '\0'); WideCharToMultiByte(CP_UTF8, 0, value.data(), static_cast(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(length) + 1, L'\0'); GetWindowTextW(field, value.data(), length + 1); value.resize(static_cast(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; try { switch (message) { case WM_CREATE: { HFONT font = static_cast(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(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(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(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(font), TRUE); path_field = CreateWindowExW(WS_EX_CLIENTEDGE, L"EDIT", L"", WS_CHILD | WS_VISIBLE | ES_AUTOHSCROLL, 24, 153, 390, 30, window, reinterpret_cast(100), nullptr, nullptr); SendMessageW(path_field, WM_SETFONT, reinterpret_cast(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(101), nullptr, nullptr); SendMessageW(browse_button, WM_SETFONT, reinterpret_cast(font), TRUE); automatic_button = CreateWindowW(L"BUTTON", L"恢复自动检测", WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON, 24, 202, 135, 34, window, reinterpret_cast(102), nullptr, nullptr); SendMessageW(automatic_button, WM_SETFONT, reinterpret_cast(font), TRUE); save_button = CreateWindowW(L"BUTTON", L"保存路径", WS_CHILD | WS_VISIBLE | BS_DEFPUSHBUTTON, 435, 202, 135, 34, window, reinterpret_cast(103), nullptr, nullptr); SendMessageW(save_button, WM_SETFONT, reinterpret_cast(font), TRUE); connection_button = CreateWindowW(L"BUTTON", L"断开连接", WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON, 435, 250, 135, 34, window, reinterpret_cast(104), nullptr, nullptr); SendMessageW(connection_button, WM_SETFONT, reinterpret_cast(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); } catch (...) { return DefWindowProcW(window, message, wparam, lparam); } } } // namespace int WINAPI wWinMain(HINSTANCE instance, HINSTANCE previous, PWSTR command_line, int show) { (void)previous; (void)command_line; try { service.start(startup_error); } catch (...) { 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(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(message.wParam); } #endif