diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt
index cf26df141..88310ed1f 100644
--- a/src/common/CMakeLists.txt
+++ b/src/common/CMakeLists.txt
@@ -113,6 +113,8 @@ if(WIN32)
d3d11/texture.cpp
d3d11/texture.h
windows_headers.h
+ win32_progress_callback.cpp
+ win32_progress_callback.h
)
target_link_libraries(common PRIVATE d3dcompiler.lib)
endif()
diff --git a/src/common/common.vcxproj b/src/common/common.vcxproj
index f1bec7b94..f2d965eb1 100644
--- a/src/common/common.vcxproj
+++ b/src/common/common.vcxproj
@@ -91,6 +91,7 @@
+
@@ -143,6 +144,7 @@
+
diff --git a/src/common/common.vcxproj.filters b/src/common/common.vcxproj.filters
index d59115d98..5c40671c8 100644
--- a/src/common/common.vcxproj.filters
+++ b/src/common/common.vcxproj.filters
@@ -100,6 +100,7 @@
+
@@ -193,6 +194,7 @@
+
@@ -208,4 +210,4 @@
{642ff5eb-af39-4aab-a42f-6eb8188a11d7}
-
\ No newline at end of file
+
diff --git a/src/common/win32_progress_callback.cpp b/src/common/win32_progress_callback.cpp
new file mode 100644
index 000000000..0d580f247
--- /dev/null
+++ b/src/common/win32_progress_callback.cpp
@@ -0,0 +1,233 @@
+#include "win32_progress_callback.h"
+#include "common/log.h"
+#include
+#pragma comment(lib, "Comctl32.lib")
+Log_SetChannel(Win32ProgressCallback);
+
+Win32ProgressCallback::Win32ProgressCallback() : BaseProgressCallback()
+{
+ Create();
+}
+
+void Win32ProgressCallback::PushState()
+{
+ BaseProgressCallback::PushState();
+}
+
+void Win32ProgressCallback::PopState()
+{
+ BaseProgressCallback::PopState();
+ Redraw(true);
+}
+
+void Win32ProgressCallback::SetCancellable(bool cancellable)
+{
+ BaseProgressCallback::SetCancellable(cancellable);
+ Redraw(true);
+}
+
+void Win32ProgressCallback::SetTitle(const char* title)
+{
+ SetWindowTextA(m_window_hwnd, title);
+}
+
+void Win32ProgressCallback::SetStatusText(const char* text)
+{
+ BaseProgressCallback::SetStatusText(text);
+ Redraw(true);
+}
+
+void Win32ProgressCallback::SetProgressRange(u32 range)
+{
+ BaseProgressCallback::SetProgressRange(range);
+ Redraw(false);
+}
+
+void Win32ProgressCallback::SetProgressValue(u32 value)
+{
+ BaseProgressCallback::SetProgressValue(value);
+ Redraw(false);
+}
+
+bool Win32ProgressCallback::Create()
+{
+ static const char* CLASS_NAME = "DSWin32ProgressCallbackWindow";
+ static bool class_registered = false;
+
+ if (!class_registered)
+ {
+ InitCommonControls();
+
+ WNDCLASSEX wc = {};
+ wc.cbSize = sizeof(WNDCLASSEX);
+ wc.lpfnWndProc = WndProcThunk;
+ wc.hInstance = GetModuleHandle(nullptr);
+ // wc.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_ICON));
+ // wc.hIconSm = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_ICON));
+ wc.hCursor = LoadCursor(NULL, IDC_WAIT);
+ wc.hbrBackground = (HBRUSH)COLOR_WINDOW;
+ wc.lpszClassName = CLASS_NAME;
+ if (!RegisterClassExA(&wc))
+ {
+ Log_ErrorPrint("Failed to register window class");
+ return false;
+ }
+
+ class_registered = true;
+ }
+
+ m_window_hwnd =
+ CreateWindowExA(WS_EX_CLIENTEDGE, CLASS_NAME, "Win32ProgressCallback", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT,
+ CW_USEDEFAULT, WINDOW_WIDTH, WINDOW_HEIGHT, nullptr, nullptr, GetModuleHandle(nullptr), this);
+ if (!m_window_hwnd)
+ {
+ Log_ErrorPrint("Failed to create window");
+ return false;
+ }
+
+ SetWindowLongPtr(m_window_hwnd, GWLP_USERDATA, reinterpret_cast(this));
+ ShowWindow(m_window_hwnd, SW_SHOW);
+ PumpMessages();
+ return true;
+}
+
+void Win32ProgressCallback::Destroy()
+{
+ if (!m_window_hwnd)
+ return;
+
+ DestroyWindow(m_window_hwnd);
+ m_window_hwnd = {};
+ m_text_hwnd = {};
+ m_progress_hwnd = {};
+}
+
+void Win32ProgressCallback::PumpMessages()
+{
+ MSG msg;
+ while (PeekMessageA(&msg, m_window_hwnd, 0, 0, PM_REMOVE))
+ {
+ TranslateMessage(&msg);
+ DispatchMessageA(&msg);
+ }
+}
+
+void Win32ProgressCallback::Redraw(bool force)
+{
+ const int percent =
+ static_cast((static_cast(m_progress_value) / static_cast(m_progress_range)) * 100.0f);
+ if (percent == m_last_progress_percent && !force)
+ {
+ PumpMessages();
+ return;
+ }
+
+ m_last_progress_percent = percent;
+
+ SendMessageA(m_progress_hwnd, PBM_SETRANGE, 0, MAKELPARAM(0, m_progress_range));
+ SendMessageA(m_progress_hwnd, PBM_SETPOS, static_cast(m_progress_value), 0);
+ SetWindowTextA(m_text_hwnd, m_status_text);
+ RedrawWindow(m_text_hwnd, nullptr, nullptr, RDW_INVALIDATE);
+ PumpMessages();
+}
+
+LRESULT CALLBACK Win32ProgressCallback::WndProcThunk(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
+{
+ Win32ProgressCallback* cb;
+ if (msg == WM_CREATE)
+ {
+ const CREATESTRUCTA* cs = reinterpret_cast(lparam);
+ cb = static_cast(cs->lpCreateParams);
+ }
+ else
+ {
+ cb = reinterpret_cast(GetWindowLongPtrA(hwnd, GWLP_USERDATA));
+ }
+
+ return cb->WndProc(hwnd, msg, wparam, lparam);
+}
+
+LRESULT CALLBACK Win32ProgressCallback::WndProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
+{
+ switch (msg)
+ {
+ case WM_CREATE:
+ {
+ const CREATESTRUCTA* cs = reinterpret_cast(lparam);
+ HFONT default_font = reinterpret_cast(GetStockObject(ANSI_VAR_FONT));
+ SendMessageA(hwnd, WM_SETFONT, WPARAM(default_font), TRUE);
+
+ int y = WINDOW_MARGIN;
+
+ m_text_hwnd = CreateWindowExA(0, "Static", nullptr, WS_VISIBLE | WS_CHILD, WINDOW_MARGIN, y, SUBWINDOW_WIDTH, 16,
+ hwnd, nullptr, cs->hInstance, nullptr);
+ SendMessageA(m_text_hwnd, WM_SETFONT, WPARAM(default_font), TRUE);
+ y += 16 + WINDOW_MARGIN;
+
+ m_progress_hwnd = CreateWindowExA(0, PROGRESS_CLASSA, nullptr, WS_VISIBLE | WS_CHILD, WINDOW_MARGIN, y,
+ SUBWINDOW_WIDTH, 32, hwnd, nullptr, cs->hInstance, nullptr);
+ y += 32 + WINDOW_MARGIN;
+
+ m_list_box_hwnd =
+ CreateWindowExA(0, "LISTBOX", nullptr, WS_VISIBLE | WS_CHILD | WS_VSCROLL | WS_HSCROLL | WS_BORDER | LBS_NOSEL,
+ WINDOW_MARGIN, y, SUBWINDOW_WIDTH, 170, hwnd, nullptr, cs->hInstance, nullptr);
+ SendMessageA(m_list_box_hwnd, WM_SETFONT, WPARAM(default_font), TRUE);
+ y += 170;
+ }
+ break;
+
+ default:
+ return DefWindowProcA(hwnd, msg, wparam, lparam);
+ }
+
+ return 0;
+}
+
+void Win32ProgressCallback::DisplayError(const char* message)
+{
+ Log_ErrorPrint(message);
+ SendMessageA(m_list_box_hwnd, LB_ADDSTRING, 0, reinterpret_cast(message));
+ SendMessageA(m_list_box_hwnd, WM_VSCROLL, SB_BOTTOM, 0);
+ PumpMessages();
+}
+
+void Win32ProgressCallback::DisplayWarning(const char* message)
+{
+ Log_WarningPrint(message);
+ SendMessageA(m_list_box_hwnd, LB_ADDSTRING, 0, reinterpret_cast(message));
+ SendMessageA(m_list_box_hwnd, WM_VSCROLL, SB_BOTTOM, 0);
+ PumpMessages();
+}
+
+void Win32ProgressCallback::DisplayInformation(const char* message)
+{
+ Log_InfoPrint(message);
+ SendMessageA(m_list_box_hwnd, LB_ADDSTRING, 0, reinterpret_cast(message));
+ SendMessageA(m_list_box_hwnd, WM_VSCROLL, SB_BOTTOM, 0);
+ PumpMessages();
+}
+
+void Win32ProgressCallback::DisplayDebugMessage(const char* message)
+{
+ Log_DevPrint(message);
+}
+
+void Win32ProgressCallback::ModalError(const char* message)
+{
+ PumpMessages();
+ MessageBoxA(m_window_hwnd, message, "Error", MB_ICONERROR | MB_OK);
+ PumpMessages();
+}
+
+bool Win32ProgressCallback::ModalConfirmation(const char* message)
+{
+ PumpMessages();
+ bool result = MessageBoxA(m_window_hwnd, message, "Confirmation", MB_ICONQUESTION | MB_YESNO) == IDYES;
+ PumpMessages();
+ return result;
+}
+
+void Win32ProgressCallback::ModalInformation(const char* message)
+{
+ MessageBoxA(m_window_hwnd, message, "Information", MB_ICONINFORMATION | MB_OK);
+}
diff --git a/src/common/win32_progress_callback.h b/src/common/win32_progress_callback.h
new file mode 100644
index 000000000..ffb046ff5
--- /dev/null
+++ b/src/common/win32_progress_callback.h
@@ -0,0 +1,51 @@
+#pragma once
+#include "common/progress_callback.h"
+#include "windows_headers.h"
+
+class Win32ProgressCallback final : public BaseProgressCallback
+{
+public:
+ Win32ProgressCallback();
+
+ void PushState() override;
+ void PopState() override;
+
+ void SetCancellable(bool cancellable) override;
+ void SetTitle(const char* title) override;
+ void SetStatusText(const char* text) override;
+ void SetProgressRange(u32 range) override;
+ void SetProgressValue(u32 value) override;
+
+ void DisplayError(const char* message) override;
+ void DisplayWarning(const char* message) override;
+ void DisplayInformation(const char* message) override;
+ void DisplayDebugMessage(const char* message) override;
+
+ void ModalError(const char* message) override;
+ bool ModalConfirmation(const char* message) override;
+ void ModalInformation(const char* message) override;
+
+private:
+ enum : int
+ {
+ WINDOW_WIDTH = 600,
+ WINDOW_HEIGHT = 300,
+ WINDOW_MARGIN = 10,
+ SUBWINDOW_WIDTH = WINDOW_WIDTH - 20 - WINDOW_MARGIN - WINDOW_MARGIN,
+ };
+
+ bool Create();
+ void Destroy();
+ void Redraw(bool force);
+ void PumpMessages();
+
+ static LRESULT CALLBACK WndProcThunk(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam);
+ LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam);
+
+ HWND m_window_hwnd{};
+ HWND m_text_hwnd{};
+ HWND m_progress_hwnd{};
+ HWND m_list_box_hwnd{};
+
+ int m_last_progress_percent = -1;
+};