#pragma region Public Domain Code /* This code is in the public domain. You can do whatever you want with it, change it, appropriate it, relicense it, redistribute it, include it in your own libraries and applications, sell it and never mention where you've got it. THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #pragma endregion // Public Domain Code // If the library is compiled as a DLL, this ensures symbols are exported #define NUCLEX_GRAPHICS_DEMO_SOURCE 1 #include "Window.h" #include #include namespace { // ------------------------------------------------------------------------------------------- // /// Appends pointers to strings template class PointerAppender { /// Appends a pointer to a string /// Size of a pointer /// String the pointer will be appended to /// Pointer that will be appended to the string public: void operator()(std::wstring &string, const void *pointer); }; /// Appends 32 bit pointers to strings template<> class PointerAppender<4> { /// Appends a 32 bit pointer to a string /// Size of a pointer /// String the pointer will be appended to /// Pointer that will be appended to the string public: void operator ()(std::wstring &string, const void *pointer) { wchar_t instanceAddress[9]; // No hexadecimally printed 32 bit integer can be longer ::_ultow(reinterpret_cast(pointer), instanceAddress, 16); string.append(instanceAddress); } }; /// Appends 64 bit pointers to strings template<> class PointerAppender<8> { /// Appends a 64 bit pointer to a string /// Size of a pointer /// String the pointer will be appended to /// Pointer that will be appended to the string public: void operator()(std::wstring &string, const void *pointer) { wchar_t instanceAddress[17]; // No hexadecimally printed 64 bit integer can be longer ::_ui64tow(reinterpret_cast(pointer), instanceAddress, 16); string.append(instanceAddress); } }; // ------------------------------------------------------------------------------------------- // /// Unregisters a window class on scope exit unless dismissed class WindowClassUnregisterer { /// Initializes a new window class unregisterer /// Handle of the application owning the window class /// Name of the window class being unregistered public: WindowClassUnregisterer( HINSTANCE instanceHandle, const std::wstring &windowClassName ) : instanceHandle(instanceHandle), windowClassName(windowClassName) {} /// Unregisters the window class unless dismissed public: ~WindowClassUnregisterer() { if(this->instanceHandle != nullptr) { ::UnregisterClassW(this->windowClassName.c_str(), this->instanceHandle); } } /// Disarms the unregisterer, leaving the window class alive public: void Dismiss() { this->instanceHandle = nullptr; } /// Application instance that owns the window class private: HINSTANCE instanceHandle; /// Name of the window class that will be unregistered private: std::wstring windowClassName; }; } // anonymous namespace namespace Nuclex { namespace Graphics { namespace Demo { // ------------------------------------------------------------------------------------------- // Window::Window(HINSTANCE instanceHandle, const std::wstring &title) : instanceHandle(instanceHandle), windowHandle(nullptr), title(title), isVisible(false), wasCloseRequested(false) { // Each window gets its own window class. Theoretically we could share it, but then // we would have to add thread synchronization so that the window class won't be // destroyed when another thread needs it. std::wstring windowClassName = getWindowClassName(); registerWindowClass(instanceHandle, windowClassName); { WindowClassUnregisterer windowClassScope(instanceHandle, windowClassName); createWindow(); windowClassScope.Dismiss(); } } // ------------------------------------------------------------------------------------------- // Window::~Window() { destroyWindow(); unregisterWindowClass(this->instanceHandle, getWindowClassName()); } // ------------------------------------------------------------------------------------------- // void Window::SetTitle(const std::wstring &title) { BOOL result = ::SetWindowTextW(this->windowHandle, title.c_str()); if(result == FALSE) { throw std::runtime_error("Could not change window title"); } this->title = title; } // ------------------------------------------------------------------------------------------- // void Window::Show(bool visible /* = true */) { if(visible) { BOOL result = ::ShowWindow(this->windowHandle, SW_SHOW); if(!this->isVisible && (result != FALSE)) { throw std::runtime_error("Could not show window"); } } else { BOOL result = ::ShowWindow(this->windowHandle, SW_HIDE); if(this->isVisible && (result == FALSE)) { throw std::runtime_error("Could not hide window"); } } this->isVisible = visible; } // ------------------------------------------------------------------------------------------- // RECT Window::GetViewRectangle() const { RECT viewRectangle; // Obtain the client rectangle of the window in window coordinates BOOL result = ::GetClientRect(this->windowHandle, &viewRectangle); if(result == FALSE) { throw std::runtime_error("Unable to obtain client rectangle of the window"); } // Now find out where the window is on the screen. We're assuming the window message // loop is called synchronously, that is, it is not possible for the window to be moved // between the call to GetClientRect() and ClientToScreen(). POINT windowPosition = {0}; result = ::ClientToScreen(windowHandle, &windowPosition); if(result == FALSE) { throw std::runtime_error("Could not convert window position to screen coordinates"); } // Add the window position to the rectangle so it is translated into screen coordinates viewRectangle.left += windowPosition.x; viewRectangle.top += windowPosition.y; viewRectangle.right += windowPosition.x; viewRectangle.bottom += windowPosition.y; return viewRectangle; } // ------------------------------------------------------------------------------------------- // void Window::ResizeViewRectangle(std::size_t width, std::size_t height) { RECT windowRectangle = GetViewRectangle(); // Prepare a client rectangle with the new width and height windowRectangle.right = static_cast(windowRectangle.left + width); windowRectangle.bottom = static_cast(windowRectangle.top + height); // Let Windows calculate how big the window needs to be in order for // the client area to have the requested size LONG style = ::GetWindowLong(this->windowHandle, GWL_STYLE); BOOL result = ::AdjustWindowRect(&windowRectangle, style, FALSE); if(result == FALSE) { throw std::runtime_error("Could not calculate window rectangle from client rectangle"); } // Update the window's position result = ::MoveWindow( this->windowHandle, static_cast(windowRectangle.left), static_cast(windowRectangle.top), static_cast(windowRectangle.right - windowRectangle.left), static_cast(windowRectangle.bottom - windowRectangle.top), TRUE // Repaint? ); if(result == FALSE) { throw std::runtime_error("Could not resize window"); } } // ------------------------------------------------------------------------------------------- // void Window::CenterOnPrimaryDisplay() { // Find the center of the desktop window, which covers the primary display, // but not any other monitors the user may have extended his desktop to. POINT center; { RECT desktopWindowRectangle; BOOL result = ::GetWindowRect(::GetDesktopWindow(), &desktopWindowRectangle); if(result == FALSE) { throw std::runtime_error("Could not determine desktop window rectangle"); } center.x = (desktopWindowRectangle.left + desktopWindowRectangle.right) / 2; center.y = (desktopWindowRectangle.top + desktopWindowRectangle.bottom) / 2; } // Now obtain the current size of the window so we can calculate the screen // position at which it will appear centered RECT windowRectangle; BOOL result = ::GetWindowRect(this->windowHandle, &windowRectangle); if(result == FALSE) { throw std::runtime_error("Could not determine window rectangle"); } // Calculate the new coordinates for the window { LONG width = windowRectangle.right - windowRectangle.left; LONG height = windowRectangle.bottom - windowRectangle.top; windowRectangle.left = center.x - (width / 2); windowRectangle.top = center.y - (height / 2); windowRectangle.right = windowRectangle.left + width; windowRectangle.bottom = windowRectangle.top + height; } // All done, move the window to the calculated position result = ::MoveWindow( this->windowHandle, static_cast(windowRectangle.left), static_cast(windowRectangle.top), static_cast(windowRectangle.right - windowRectangle.left), static_cast(windowRectangle.bottom - windowRectangle.top), TRUE // Repaint? ); if(result == FALSE) { throw std::runtime_error("Could not move window"); } } // ------------------------------------------------------------------------------------------- // LRESULT Window::ProcessMessage( HWND windowHandle, UINT message, WPARAM parameter1, LPARAM parameter2 ) { if(message == WM_CLOSE) { this->wasCloseRequested = true; return 0; } return ::DefWindowProcW(windowHandle, message, parameter1, parameter2); } // ------------------------------------------------------------------------------------------- // void Window::registerWindowClass( HINSTANCE instanceHandle, const std::wstring &windowClassName ) { WNDCLASSEXW windowClass; // Fill the window class with sensible values windowClass.cbSize = sizeof(windowClass); windowClass.style = CS_BYTEALIGNCLIENT | CS_HREDRAW | CS_VREDRAW; windowClass.lpfnWndProc = windowProcedure; windowClass.cbClsExtra = 0; windowClass.cbWndExtra = 0; windowClass.hInstance = instanceHandle; windowClass.hIcon = ::LoadIcon(nullptr, IDI_APPLICATION); windowClass.hCursor = ::LoadCursor(nullptr, IDC_ARROW); windowClass.hbrBackground = reinterpret_cast(::GetStockObject(BLACK_BRUSH)); windowClass.lpszMenuName = nullptr; windowClass.lpszClassName = windowClassName.c_str(); windowClass.hIconSm = nullptr; ATOM result = ::RegisterClassExW(&windowClass); if(result == 0) { throw std::runtime_error("Could not register window class"); } } // ------------------------------------------------------------------------------------------- // void Window::unregisterWindowClass( HINSTANCE instanceHandle, const std::wstring &windowClassName ) { using namespace std; // In case assert() is in std for your compiler BOOL result = ::UnregisterClassW(windowClassName.c_str(), instanceHandle); assert((result != FALSE) && "Window class should succeed unregistering"); UNREFERENCED_PARAMETER(result); // Avoid warning on /W4 } // ------------------------------------------------------------------------------------------- // void Window::createWindow() { std::wstring windowClassName = getWindowClassName(); // Put together the window styles the window should have DWORD style = WS_POPUPWINDOW | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME; style |= WS_MINIMIZEBOX | WS_MAXIMIZEBOX; if(this->isVisible) { style |= WS_VISIBLE; } this->windowHandle = ::CreateWindowExW( WS_EX_APPWINDOW, windowClassName.c_str(), this->title.c_str(), style, CW_USEDEFAULT, // Initial X position CW_USEDEFAULT, // Initial Y position CW_USEDEFAULT, // Initial width CW_USEDEFAULT, // Initial height ::GetDesktopWindow(), // Parent window, uses desktop window to show in task bar nullptr, // Menu handle this->instanceHandle, nullptr // User-defined parameter ); if(this->windowHandle == nullptr) { throw std::runtime_error("Could not create window"); } // Store the pointer to this instance in the window so the window procedure // can look it up and forward calls. This has less conflict potential than // using the SetWindowLongPtr() function with GWLP_USERDATA. BOOL result = ::SetPropW( this->windowHandle, L"Nuclex::Wrapper::Window", reinterpret_cast(this) ); if(result == FALSE) { throw std::runtime_error("Could not assign window property"); } } // ------------------------------------------------------------------------------------------- // void Window::destroyWindow() { using namespace std; // In case assert() is in std for your compiler BOOL result = ::DestroyWindow(this->windowHandle); assert((result != FALSE) && "Window should be successfully destroyed"); UNREFERENCED_PARAMETER(result); // Avoid warning on /W4 } // ------------------------------------------------------------------------------------------- // std::wstring Window::getWindowClassName() const { std::wstring name(L"Nuclex::Wrapper::Window#"); PointerAppender()(name, this); return name; } // ------------------------------------------------------------------------------------------- // LRESULT CALLBACK Window::windowProcedure( HWND windowHandle, UINT message, WPARAM parameter1, LPARAM parameter2 ) { Window *self = reinterpret_cast( ::GetPropW(windowHandle, L"Nuclex::Wrapper::Window") ); if(self != nullptr) { try { return self->ProcessMessage(windowHandle, message, parameter1, parameter2); } catch(const std::exception &) { using namespace std; assert(false && "Exceptions must not reach the window procedure"); } } return ::DefWindowProc(windowHandle, message, parameter1, parameter2); } // ------------------------------------------------------------------------------------------- // }}} // namespace Nuclex::Graphics::Demo