terminal/src/interactivity/win32/WindowMetrics.cpp
N d09fdd61cb
Change backslashes in include statements to forward slashes (#8205)
Many include statements use forward slashes, while others use backwards
slashes. This is inconsistent formatting. For this reason, I changed the
backward slashes to forward slashes since that is the standard.
2020-11-25 21:02:10 +00:00

293 lines
12 KiB
C++

// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "precomp.h"
#include "WindowMetrics.hpp"
#include "windowdpiapi.hpp"
#include "../inc/ServiceLocator.hpp"
#pragma hdrstop
// The following default masks are used in creating windows
#define CONSOLE_WINDOW_FLAGS (WS_OVERLAPPEDWINDOW | WS_HSCROLL | WS_VSCROLL)
#define CONSOLE_WINDOW_EX_FLAGS (WS_EX_WINDOWEDGE | WS_EX_ACCEPTFILES | WS_EX_APPWINDOW)
using namespace Microsoft::Console::Interactivity::Win32;
// Routine Description:
// - Gets the minimum possible client rectangle in pixels.
// - Purely based on system metrics. Doesn't compensate for potential scroll bars.
// Arguments:
// - <none>
// Return Value:
// - RECT of client area positions in pixels.
RECT WindowMetrics::GetMinClientRectInPixels()
{
// prepare rectangle
RECT rc;
SetRectEmpty(&rc);
// set bottom/right dimensions to represent minimum window width/height
rc.right = GetSystemMetrics(SM_CXMIN);
rc.bottom = GetSystemMetrics(SM_CYMIN);
// convert to client rect
ConvertWindowRectToClientRect(&rc);
// there is no scroll bar subtraction here as the minimum window dimensions can be expanded wider to hold a scroll bar if necessary
return rc;
}
// Routine Description:
// - Gets the maximum possible client rectangle in pixels.
// - This leaves space for potential scroll bars to be visible within the window (which are non-client area pixels when rendered)
// - This is a measurement of the inner area of the window, not including the non-client frame area and not including scroll bars.
// Arguments:
// - <none>
// Return Value:
// - RECT of client area positions in pixels.
RECT WindowMetrics::GetMaxClientRectInPixels()
{
// This will retrieve the outer window rect. We need the client area to calculate characters.
RECT rc = GetMaxWindowRectInPixels();
// convert to client rect
ConvertWindowRectToClientRect(&rc);
return rc;
}
// Routine Description:
// - Gets the maximum possible window rectangle in pixels. Based on the monitor the window is on or the primary monitor if no window exists yet.
// Arguments:
// - <none>
// Return Value:
// - RECT containing the left, right, top, and bottom positions from the desktop origin in pixels. Measures the outer edges of the potential window.
RECT WindowMetrics::GetMaxWindowRectInPixels()
{
RECT rc;
SetRectEmpty(&rc);
return GetMaxWindowRectInPixels(&rc, nullptr);
}
// Routine Description:
// - Gets the maximum possible window rectangle in pixels. Based on the monitor the window is on or the primary monitor if no window exists yet.
// Arguments:
// - prcSuggested - If we were given a suggested rectangle for where the window is going, we can pass it in here to find out the max size on that monitor.
// - If this value is zero and we had a valid window handle, we'll use that instead. Otherwise the value of 0 will make us use the primary monitor.
// - pDpiSuggested - The dpi that matches the suggested rect. We will attempt to compute this during the function, but if we fail for some reason,
// - the original value passed in will be left untouched.
// Return Value:
// - RECT containing the left, right, top, and bottom positions from the desktop origin in pixels. Measures the outer edges of the potential window.
RECT WindowMetrics::GetMaxWindowRectInPixels(const RECT* const prcSuggested, _Out_opt_ UINT* pDpiSuggested)
{
// prepare rectangle
RECT rc = *prcSuggested;
// use zero rect to compare.
RECT rcZero;
SetRectEmpty(&rcZero);
// First get the monitor pointer from either the active window or the default location (0,0,0,0)
HMONITOR hMonitor = nullptr;
// NOTE: We must use the nearest monitor because sometimes the system moves the window around into strange spots while performing snap and Win+D operations.
// Those operations won't work correctly if we use MONITOR_DEFAULTTOPRIMARY.
auto pWindow = ServiceLocator::LocateConsoleWindow();
if (pWindow == nullptr || (TRUE != EqualRect(&rc, &rcZero)))
{
// For invalid window handles or when we were passed a non-zero suggestion rectangle, get the monitor from the rect.
hMonitor = MonitorFromRect(&rc, MONITOR_DEFAULTTONEAREST);
}
else
{
// Otherwise, get the monitor from the window handle.
hMonitor = MonitorFromWindow(pWindow->GetWindowHandle(), MONITOR_DEFAULTTONEAREST);
}
// If for whatever reason there is no monitor, we're going to give back whatever we got since we can't figure anything out.
// We won't adjust the DPI either. That's OK. DPI doesn't make much sense with no display.
if (nullptr == hMonitor)
{
return rc;
}
// Now obtain the monitor pixel dimensions
MONITORINFO MonitorInfo = { 0 };
MonitorInfo.cbSize = sizeof(MONITORINFO);
GetMonitorInfoW(hMonitor, &MonitorInfo);
// We have to make a correction to the work area. If we actually consume the entire work area (by maximizing the window)
// The window manager will render the borders off-screen.
// We need to pad the work rectangle with the border dimensions to represent the actual max outer edges of the window rect.
WINDOWINFO wi = { 0 };
wi.cbSize = sizeof(WINDOWINFO);
GetWindowInfo(pWindow ? pWindow->GetWindowHandle() : nullptr, &wi);
if (pWindow != nullptr && pWindow->IsInFullscreen())
{
// In full screen mode, we will consume the whole monitor with no chrome.
rc = MonitorInfo.rcMonitor;
}
else
{
// In non-full screen, we want to only use the work area (avoiding the task bar space)
rc = MonitorInfo.rcWork;
rc.top -= wi.cyWindowBorders;
rc.bottom += wi.cyWindowBorders;
rc.left -= wi.cxWindowBorders;
rc.right += wi.cxWindowBorders;
}
if (pDpiSuggested != nullptr)
{
UINT monitorDpiX;
UINT monitorDpiY;
if (SUCCEEDED(GetDpiForMonitor(hMonitor, MDT_EFFECTIVE_DPI, &monitorDpiX, &monitorDpiY)))
{
*pDpiSuggested = monitorDpiX;
}
else
{
*pDpiSuggested = ServiceLocator::LocateGlobals().dpi;
}
}
return rc;
}
// Routine Description:
// - Converts a client rect (inside not including non-client area) into a window rect (the outside edge dimensions)
// - NOTE: This one uses the current global DPI for calculations.
// Arguments:
// - prc - Rectangle to be adjusted from window to client
// - dwStyle - Style flags
// - fMenu - Whether or not a menu is present for this window
// - dwExStyle - Extended Style flags
// Return Value:
// - BOOL if adjustment was successful. (See AdjustWindowRectEx definition for details).
BOOL WindowMetrics::AdjustWindowRectEx(_Inout_ LPRECT prc, const DWORD dwStyle, const BOOL fMenu, const DWORD dwExStyle)
{
return ServiceLocator::LocateHighDpiApi<WindowDpiApi>()->AdjustWindowRectExForDpi(prc, dwStyle, fMenu, dwExStyle, ServiceLocator::LocateGlobals().dpi);
}
// Routine Description:
// - Converts a client rect (inside not including non-client area) into a window rect (the outside edge dimensions)
// Arguments:
// - prc - Rectangle to be adjusted from window to client
// - dwStyle - Style flags
// - fMenu - Whether or not a menu is present for this window
// - dwExStyle - Extended Style flags
// - iDpi - The DPI to use for scaling.
// Return Value:
// - BOOL if adjustment was successful. (See AdjustWindowRectEx definition for details).
BOOL WindowMetrics::AdjustWindowRectEx(_Inout_ LPRECT prc, const DWORD dwStyle, const BOOL fMenu, const DWORD dwExStyle, const int iDpi)
{
return ServiceLocator::LocateHighDpiApi<WindowDpiApi>()->AdjustWindowRectExForDpi(prc, dwStyle, fMenu, dwExStyle, iDpi);
}
// Routine Description:
// - Converts a client rect (inside not including non-client area) into a window rect (the outside edge dimensions)
// - This is a helper to call AdjustWindowRectEx.
// - It finds the appropriate window styles for the active window or uses the defaults from our class registration.
// - It does NOT compensate for scrollbars or menus.
// Arguments:
// - prc - Pointer to rectangle to be adjusted from client to window positions.
// Return Value:
// - <none>
void WindowMetrics::ConvertClientRectToWindowRect(_Inout_ RECT* const prc)
{
ConvertRect(prc, ConvertRectangle::CLIENT_TO_WINDOW);
}
// Routine Description:
// - Converts a window rect (the outside edge dimensions) into a client rect (inside not including non-client area)
// - This is a helper to call UnadjustWindowRectEx.
// - It finds the appropriate window styles for the active window or uses the defaults from our class registration.
// - It does NOT compensate for scrollbars or menus.
// Arguments:
// - prc - Pointer to rectangle to be adjusted from window to client positions.
// Return Value:
// - <none>
void WindowMetrics::ConvertWindowRectToClientRect(_Inout_ RECT* const prc)
{
ConvertRect(prc, ConvertRectangle::WINDOW_TO_CLIENT);
}
// Routine Description:
// - Converts a window rect (the outside edge dimensions) into a client rect (inside not including non-client area)
// - Effectively the opposite math of "AdjustWindowRectEx"
// - See: http://blogs.msdn.com/b/oldnewthing/archive/2013/10/17/10457292.aspx
// Arguments:
// - prc - Rectangle to be adjusted from window to client
// - dwStyle - Style flags
// - fMenu - Whether or not a menu is present for this window
// - dwExStyle - Extended Style flags
// Return Value:
// - BOOL if adjustment was successful. (See AdjustWindowRectEx definition for details).
BOOL WindowMetrics::UnadjustWindowRectEx(_Inout_ LPRECT prc, const DWORD dwStyle, const BOOL fMenu, const DWORD dwExStyle)
{
RECT rc;
SetRectEmpty(&rc);
BOOL fRc = AdjustWindowRectEx(&rc, dwStyle, fMenu, dwExStyle);
if (fRc)
{
prc->left -= rc.left;
prc->top -= rc.top;
prc->right -= rc.right;
prc->bottom -= rc.bottom;
}
return fRc;
}
// Routine Description:
// - To reduce code duplication, this will do the style lookup and forwards/backwards calls for client/window rectangle translation.
// - Only really intended for use by the related static methods.
// Arguments:
// - prc - Pointer to rectangle to be adjusted from client to window positions.
// - crDirection - specifies which conversion to perform
// Return Value:
// - <none>
void WindowMetrics::ConvertRect(_Inout_ RECT* const prc, const ConvertRectangle crDirection)
{
// collect up current window style (if available) for adjustment
DWORD dwStyle = 0;
DWORD dwExStyle = 0;
Microsoft::Console::Types::IConsoleWindow* pWindow = ServiceLocator::LocateConsoleWindow();
if (pWindow != nullptr)
{
dwStyle = GetWindowStyle(pWindow->GetWindowHandle());
dwExStyle = GetWindowExStyle(pWindow->GetWindowHandle());
}
else
{
dwStyle = CONSOLE_WINDOW_FLAGS;
dwExStyle = CONSOLE_WINDOW_EX_FLAGS;
}
switch (crDirection)
{
case CLIENT_TO_WINDOW:
{
// ask system to adjust our client rectangle into a window rectangle using the given style
AdjustWindowRectEx(prc, dwStyle, false, dwExStyle);
break;
}
case WINDOW_TO_CLIENT:
{
// ask system to adjust our window rectangle into a client rectangle using the given style
UnadjustWindowRectEx(prc, dwStyle, false, dwExStyle);
break;
}
default:
{
FAIL_FAST_HR(E_NOTIMPL); // not implemented
}
}
}