229 lines
7.6 KiB
C++
229 lines
7.6 KiB
C++
// Copyright (c) Microsoft Corporation.
|
|
// Licensed under the MIT license.
|
|
|
|
#pragma once
|
|
|
|
// Custom window messages
|
|
#define CM_UPDATE_TITLE (WM_USER)
|
|
|
|
#include <wil/resource.h>
|
|
|
|
template<typename T>
|
|
class BaseWindow
|
|
{
|
|
public:
|
|
virtual ~BaseWindow() = 0;
|
|
static T* GetThisFromHandle(HWND const window) noexcept
|
|
{
|
|
return reinterpret_cast<T*>(GetWindowLongPtr(window, GWLP_USERDATA));
|
|
}
|
|
|
|
[[nodiscard]] static LRESULT __stdcall WndProc(HWND const window, UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept
|
|
{
|
|
WINRT_ASSERT(window);
|
|
|
|
if (WM_NCCREATE == message)
|
|
{
|
|
auto cs = reinterpret_cast<CREATESTRUCT*>(lparam);
|
|
T* that = static_cast<T*>(cs->lpCreateParams);
|
|
WINRT_ASSERT(that);
|
|
WINRT_ASSERT(!that->_window);
|
|
that->_window = wil::unique_hwnd(window);
|
|
|
|
return that->_OnNcCreate(wparam, lparam);
|
|
}
|
|
else if (T* that = GetThisFromHandle(window))
|
|
{
|
|
return that->MessageHandler(message, wparam, lparam);
|
|
}
|
|
|
|
return DefWindowProc(window, message, wparam, lparam);
|
|
}
|
|
|
|
[[nodiscard]] virtual LRESULT MessageHandler(UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept
|
|
{
|
|
switch (message)
|
|
{
|
|
case WM_DPICHANGED:
|
|
{
|
|
return HandleDpiChange(_window.get(), wparam, lparam);
|
|
}
|
|
|
|
case WM_DESTROY:
|
|
{
|
|
PostQuitMessage(0);
|
|
return 0;
|
|
}
|
|
|
|
case WM_SIZE:
|
|
{
|
|
UINT width = LOWORD(lparam);
|
|
UINT height = HIWORD(lparam);
|
|
|
|
switch (wparam)
|
|
{
|
|
case SIZE_MAXIMIZED:
|
|
[[fallthrough]];
|
|
case SIZE_RESTORED:
|
|
if (_minimized)
|
|
{
|
|
_minimized = false;
|
|
OnRestore();
|
|
}
|
|
|
|
// We always need to fire the resize event, even when we're transitioning from minimized.
|
|
// We might be transitioning directly from minimized to maximized, and we'll need
|
|
// to trigger any size-related content changes.
|
|
OnResize(width, height);
|
|
break;
|
|
case SIZE_MINIMIZED:
|
|
if (!_minimized)
|
|
{
|
|
_minimized = true;
|
|
OnMinimize();
|
|
}
|
|
break;
|
|
default:
|
|
// do nothing.
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
case CM_UPDATE_TITLE:
|
|
{
|
|
SetWindowTextW(_window.get(), _title.c_str());
|
|
break;
|
|
}
|
|
}
|
|
|
|
return DefWindowProc(_window.get(), message, wparam, lparam);
|
|
}
|
|
|
|
// DPI Change handler. on WM_DPICHANGE resize the window
|
|
[[nodiscard]] LRESULT HandleDpiChange(const HWND hWnd, const WPARAM wParam, const LPARAM lParam)
|
|
{
|
|
_inDpiChange = true;
|
|
const HWND hWndStatic = GetWindow(hWnd, GW_CHILD);
|
|
if (hWndStatic != nullptr)
|
|
{
|
|
const UINT uDpi = HIWORD(wParam);
|
|
|
|
// Resize the window
|
|
auto lprcNewScale = reinterpret_cast<RECT*>(lParam);
|
|
|
|
SetWindowPos(hWnd, nullptr, lprcNewScale->left, lprcNewScale->top, lprcNewScale->right - lprcNewScale->left, lprcNewScale->bottom - lprcNewScale->top, SWP_NOZORDER | SWP_NOACTIVATE);
|
|
|
|
_currentDpi = uDpi;
|
|
}
|
|
_inDpiChange = false;
|
|
return 0;
|
|
}
|
|
|
|
virtual void OnResize(const UINT width, const UINT height) = 0;
|
|
virtual void OnMinimize() = 0;
|
|
virtual void OnRestore() = 0;
|
|
|
|
RECT GetWindowRect() const noexcept
|
|
{
|
|
RECT rc = { 0 };
|
|
::GetWindowRect(_window.get(), &rc);
|
|
return rc;
|
|
}
|
|
|
|
HWND GetHandle() const noexcept
|
|
{
|
|
return _window.get();
|
|
}
|
|
|
|
float GetCurrentDpiScale() const noexcept
|
|
{
|
|
const auto dpi = ::GetDpiForWindow(_window.get());
|
|
const auto scale = static_cast<float>(dpi) / static_cast<float>(USER_DEFAULT_SCREEN_DPI);
|
|
return scale;
|
|
}
|
|
|
|
// Gets the physical size of the client area of the HWND in _window
|
|
SIZE GetPhysicalSize() const noexcept
|
|
{
|
|
RECT rect = {};
|
|
GetClientRect(_window.get(), &rect);
|
|
const auto windowsWidth = rect.right - rect.left;
|
|
const auto windowsHeight = rect.bottom - rect.top;
|
|
return SIZE{ windowsWidth, windowsHeight };
|
|
}
|
|
|
|
// Gets the logical (in DIPs) size of a physical size specified by the parameter physicalSize
|
|
// Remarks:
|
|
// XAML coordinate system is always in Display Independent Pixels (a.k.a DIPs or Logical). However Win32 GDI (because of legacy reasons)
|
|
// in DPI mode "Per-Monitor and Per-Monitor (V2) DPI Awareness" is always in physical pixels.
|
|
// The formula to transform is:
|
|
// logical = (physical / dpi) + 0.5 // 0.5 is to ensure that we pixel snap correctly at the edges, this is necessary with odd DPIs like 1.25, 1.5, 1, .75
|
|
// See also:
|
|
// https://docs.microsoft.com/en-us/windows/desktop/LearnWin32/dpi-and-device-independent-pixels
|
|
// https://docs.microsoft.com/en-us/windows/desktop/hidpi/high-dpi-desktop-application-development-on-windows#per-monitor-and-per-monitor-v2-dpi-awareness
|
|
winrt::Windows::Foundation::Size GetLogicalSize(const SIZE physicalSize) const noexcept
|
|
{
|
|
const auto scale = GetCurrentDpiScale();
|
|
// 0.5 is to ensure that we pixel snap correctly at the edges, this is necessary with odd DPIs like 1.25, 1.5, 1, .75
|
|
const auto logicalWidth = (physicalSize.cx / scale) + 0.5f;
|
|
const auto logicalHeight = (physicalSize.cy / scale) + 0.5f;
|
|
return winrt::Windows::Foundation::Size(logicalWidth, logicalHeight);
|
|
}
|
|
|
|
winrt::Windows::Foundation::Size GetLogicalSize() const noexcept
|
|
{
|
|
return GetLogicalSize(GetPhysicalSize());
|
|
}
|
|
|
|
// Method Description:
|
|
// - Sends a message to our message loop to update the title of the window.
|
|
// Arguments:
|
|
// - newTitle: a string to use as the new title of the window.
|
|
// Return Value:
|
|
// - <none>
|
|
void UpdateTitle(std::wstring_view newTitle)
|
|
{
|
|
_title = newTitle;
|
|
PostMessageW(_window.get(), CM_UPDATE_TITLE, 0, reinterpret_cast<LPARAM>(nullptr));
|
|
}
|
|
|
|
// Method Description:
|
|
// Reset the current dpi of the window. This method is only called after we change the
|
|
// initial launch position. This makes sure the dpi is consistent with the monitor on which
|
|
// the window will launch
|
|
void RefreshCurrentDPI()
|
|
{
|
|
_currentDpi = GetDpiForWindow(_window.get());
|
|
}
|
|
|
|
protected:
|
|
using base_type = BaseWindow<T>;
|
|
wil::unique_hwnd _window;
|
|
|
|
unsigned int _currentDpi = 0;
|
|
bool _inDpiChange = false;
|
|
|
|
std::wstring _title = L"";
|
|
|
|
bool _minimized = false;
|
|
|
|
// Method Description:
|
|
// - This method is called when the window receives the WM_NCCREATE message.
|
|
// Return Value:
|
|
// - The value returned from the window proc.
|
|
virtual [[nodiscard]] LRESULT _OnNcCreate(WPARAM wParam, LPARAM lParam) noexcept
|
|
{
|
|
SetWindowLongPtr(_window.get(), GWLP_USERDATA, reinterpret_cast<LONG_PTR>(this));
|
|
|
|
EnableNonClientDpiScaling(_window.get());
|
|
_currentDpi = GetDpiForWindow(_window.get());
|
|
|
|
return DefWindowProc(_window.get(), WM_NCCREATE, wParam, lParam);
|
|
};
|
|
};
|
|
|
|
template<typename T>
|
|
inline BaseWindow<T>::~BaseWindow()
|
|
{
|
|
}
|