Use standard 1px window borders on NC Island Window (#3394)

We take the standard window frame except that we remove the top part
(see `NonClientIslandWindow::_OnNcCalcSize`), then we put little 1 pixel
wide top border back in the client area using
`DwmExtendFrameIntoClientArea` and then we put the XAML island and the
drag bar on top.

Most of this PR is comments to explain how the code works and also
removing complex code that was needed to handle the weird cases when the
borders were custom.

I've also refactored a little bit the `NonClientIslandWindow` class.

* Fix DwmExtendFrameIntoClientArea values
* Fix WM_NCHITTEST handling
* Position the XAML island window correctly
* Fix weird colors in drag bar and hide old title bar buttons
* Fix the window's position when maximized
* Add support for dark theme on the frame
* DRY shared code between conhost and new terminal
* Fix drag bar and remove dead code
* Remove dead code and use cached DPI
* Refactor code
* Remove impossible TODO
* Use system metrics instead of hardcoding resize border height
* Use theme from app settings instead of system theme. Improve comments. Remove unused DWM frame on maximize.
* Fix initial position DPI handling bug and apply review changes
* Fix thick borders with DPI > 96

Closes #3064.
Closes #1307.
Closes #3136.
Closes #1897.
Closes #3222.
Closes #1859.
This commit is contained in:
greg904 2019-11-05 00:45:40 +01:00 committed by Dustin L. Howett (MSFT)
parent 6f36f8b23f
commit 5dfc021d8e
15 changed files with 462 additions and 674 deletions

View file

@ -335,7 +335,8 @@ namespace winrt::TerminalApp::implementation
TerminalSettings settings = _settings->MakeSettings(std::nullopt);
// TODO MSFT:21150597 - If the global setting "Always show tab bar" is
// set, then we'll need to add the height of the tab bar here.
// set or if "Show tabs in title bar" is set, then we'll need to add
// the height of the tab bar here.
return TermControl::GetProposedDimensions(settings, dpi);
}
@ -394,6 +395,17 @@ namespace winrt::TerminalApp::implementation
return point;
}
winrt::Windows::UI::Xaml::ElementTheme App::GetRequestedTheme()
{
if (!_loadedInitialSettings)
{
// Load settings if we haven't already
LoadSettings();
}
return _settings->GlobalSettings().GetRequestedTheme();
}
bool App::GetShowTabsInTitlebar()
{
if (!_loadedInitialSettings)

View file

@ -32,6 +32,7 @@ namespace winrt::TerminalApp::implementation
Windows::Foundation::Point GetLaunchDimensions(uint32_t dpi);
winrt::Windows::Foundation::Point GetLaunchInitialPositions(int32_t defaultInitialX, int32_t defaultInitialY);
winrt::Windows::UI::Xaml::ElementTheme GetRequestedTheme();
LaunchMode GetLaunchMode();
bool GetShowTabsInTitlebar();

View file

@ -31,6 +31,7 @@ namespace TerminalApp
Windows.Foundation.Point GetLaunchDimensions(UInt32 dpi);
Windows.Foundation.Point GetLaunchInitialPositions(Int32 defaultInitialX, Int32 defaultInitialY);
Windows.UI.Xaml.ElementTheme GetRequestedTheme();
LaunchMode GetLaunchMode();
Boolean GetShowTabsInTitlebar();
void TitlebarClicked();

View file

@ -22,7 +22,7 @@ AppHost::AppHost() noexcept :
if (_useNonClientArea)
{
_window = std::make_unique<NonClientIslandWindow>();
_window = std::make_unique<NonClientIslandWindow>(_app.GetRequestedTheme());
}
else
{
@ -188,54 +188,25 @@ void AppHost::_HandleCreateWindow(const HWND hwnd, RECT proposedRect, winrt::Ter
auto initialSize = _app.GetLaunchDimensions(dpix);
const short _currentWidth = Utils::ClampToShortMax(
const short islandWidth = Utils::ClampToShortMax(
static_cast<long>(ceil(initialSize.X)), 1);
const short _currentHeight = Utils::ClampToShortMax(
const short islandHeight = Utils::ClampToShortMax(
static_cast<long>(ceil(initialSize.Y)), 1);
// Create a RECT from our requested client size
auto nonClient = Viewport::FromDimensions({ _currentWidth,
_currentHeight })
.ToRect();
RECT islandFrame = {};
bool succeeded = AdjustWindowRectExForDpi(&islandFrame, WS_OVERLAPPEDWINDOW, false, 0, dpix);
// If we failed to get the correct window size for whatever reason, log
// the error and go on. We'll use whatever the control proposed as the
// size of our window, which will be at least close.
LOG_LAST_ERROR_IF(!succeeded);
// Get the size of a window we'd need to host that client rect. This will
// add the titlebar space.
if (_useNonClientArea)
{
// If we're in NC tabs mode, do the math ourselves. Get the margins
// we're using for the window - this will include the size of the
// titlebar content.
const auto pNcWindow = static_cast<NonClientIslandWindow*>(_window.get());
const MARGINS margins = pNcWindow->GetFrameMargins();
nonClient.left = 0;
nonClient.top = 0;
nonClient.right = margins.cxLeftWidth + nonClient.right + margins.cxRightWidth;
nonClient.bottom = margins.cyTopHeight + nonClient.bottom + margins.cyBottomHeight;
}
else
{
bool succeeded = AdjustWindowRectExForDpi(&nonClient, WS_OVERLAPPEDWINDOW, false, 0, dpix);
if (!succeeded)
{
// If we failed to get the correct window size for whatever reason, log
// the error and go on. We'll use whatever the control proposed as the
// size of our window, which will be at least close.
LOG_LAST_ERROR();
nonClient = Viewport::FromDimensions({ _currentWidth,
_currentHeight })
.ToRect();
}
// For client island scenario, there is an invisible border of 8 pixels.
// We need to remove this border to guarantee the left edge of the window
// coincides with the screen
const auto pCWindow = static_cast<IslandWindow*>(_window.get());
const RECT frame = pCWindow->GetFrameBorderMargins(dpix);
proposedRect.left += frame.left;
islandFrame.top = -NonClientIslandWindow::topBorderVisibleHeight;
}
adjustedHeight = nonClient.bottom - nonClient.top;
adjustedWidth = nonClient.right - nonClient.left;
adjustedWidth = -islandFrame.left + islandWidth + islandFrame.right;
adjustedHeight = -islandFrame.top + islandHeight + islandFrame.bottom;
}
const COORD origin{ gsl::narrow<short>(proposedRect.left),
@ -294,5 +265,5 @@ void AppHost::_UpdateTitleBarContent(const winrt::Windows::Foundation::IInspecta
// - <none>
void AppHost::_UpdateTheme(const winrt::TerminalApp::App&, const winrt::Windows::UI::Xaml::ElementTheme& arg)
{
_window->UpdateTheme(arg);
_window->OnApplicationThemeChanged(arg);
}

View file

@ -34,10 +34,8 @@ public:
WINRT_ASSERT(that);
WINRT_ASSERT(!that->_window);
that->_window = wil::unique_hwnd(window);
SetWindowLongPtr(window, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(that));
EnableNonClientDpiScaling(window);
that->_currentDpi = GetDpiForWindow(window);
return that->_OnNcCreate(wparam, lparam);
}
else if (T* that = GetThisFromHandle(window))
{
@ -245,6 +243,20 @@ protected:
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>

View file

@ -243,15 +243,6 @@ IRawElementProviderSimple* IslandWindow::_GetUiaProvider()
return _pUiaProvider;
}
RECT IslandWindow::GetFrameBorderMargins(unsigned int currentDpi)
{
const auto windowStyle = GetWindowStyle(_window.get());
const auto targetStyle = windowStyle & ~WS_DLGFRAME;
RECT frame{};
AdjustWindowRectExForDpi(&frame, targetStyle, false, GetWindowExStyle(_window.get()), currentDpi);
return frame;
}
// Method Description:
// - Called when the window has been resized (or maximized)
// Arguments:
@ -300,7 +291,7 @@ void IslandWindow::OnAppInitialized()
// - arg: the ElementTheme to use as the new theme for the UI
// Return Value:
// - <none>
void IslandWindow::UpdateTheme(const winrt::Windows::UI::Xaml::ElementTheme& requestedTheme)
void IslandWindow::OnApplicationThemeChanged(const winrt::Windows::UI::Xaml::ElementTheme& requestedTheme)
{
_rootGrid.RequestedTheme(requestedTheme);
// Invalidate the window rect, so that we'll repaint any elements we're

View file

@ -23,19 +23,17 @@ public:
[[nodiscard]] virtual LRESULT MessageHandler(UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept override;
IRawElementProviderSimple* _GetUiaProvider();
RECT GetFrameBorderMargins(unsigned int currentDpi);
void OnResize(const UINT width, const UINT height) override;
void OnMinimize() override;
void OnRestore() override;
virtual void OnAppInitialized();
virtual void SetContent(winrt::Windows::UI::Xaml::UIElement content);
virtual void OnApplicationThemeChanged(const winrt::Windows::UI::Xaml::ElementTheme& requestedTheme);
virtual void Initialize();
void SetCreateCallback(std::function<void(const HWND, const RECT, winrt::TerminalApp::LaunchMode& launchMode)> pfn) noexcept;
void UpdateTheme(const winrt::Windows::UI::Xaml::ElementTheme& requestedTheme);
#pragma region IUiaWindow
void ChangeViewport(const SMALL_RECT /*NewWindow*/)
{

File diff suppressed because it is too large Load diff

View file

@ -21,48 +21,58 @@ Author(s):
#include "IslandWindow.h"
#include "../../types/inc/Viewport.hpp"
#include <dwmapi.h>
#include <wil\resource.h>
#include <wil/resource.h>
class NonClientIslandWindow : public IslandWindow
{
public:
NonClientIslandWindow() noexcept;
// this is the same for all DPIs
static constexpr const int topBorderVisibleHeight = 1;
NonClientIslandWindow(const winrt::Windows::UI::Xaml::ElementTheme& requestedTheme) noexcept;
virtual ~NonClientIslandWindow() override;
virtual void OnSize(const UINT width, const UINT height) override;
[[nodiscard]] virtual LRESULT MessageHandler(UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept override;
MARGINS GetFrameMargins() const noexcept;
void Initialize() override;
void OnAppInitialized() override;
void SetContent(winrt::Windows::UI::Xaml::UIElement content) override;
void SetTitlebarContent(winrt::Windows::UI::Xaml::UIElement content);
void OnApplicationThemeChanged(const winrt::Windows::UI::Xaml::ElementTheme& requestedTheme) override;
private:
std::optional<COORD> _oldIslandPos;
winrt::TerminalApp::TitlebarControl _titlebar{ nullptr };
winrt::Windows::UI::Xaml::UIElement _clientContent{ nullptr };
wil::unique_hbrush _backgroundBrush;
COLORREF _backgroundBrushColor;
winrt::Windows::UI::Xaml::Controls::Border _dragBar{ nullptr };
wil::unique_hrgn _dragBarRegion;
MARGINS _maximizedMargins = { 0 };
winrt::Windows::UI::Xaml::ElementTheme _theme;
bool _isMaximized;
winrt::Windows::UI::Xaml::Controls::Border _dragBar{ nullptr };
RECT GetDragAreaRect() const noexcept;
int _GetResizeHandleHeight() const noexcept;
RECT _GetDragAreaRect() const noexcept;
int _GetTopBorderHeight() const noexcept;
[[nodiscard]] LRESULT HitTestNCA(POINT ptMouse) const noexcept;
[[nodiscard]] LRESULT _OnNcCreate(WPARAM wParam, LPARAM lParam) noexcept override;
[[nodiscard]] LRESULT _OnNcCalcSize(const WPARAM wParam, const LPARAM lParam) noexcept;
[[nodiscard]] LRESULT _OnNcHitTest(POINT ptMouse) const noexcept;
[[nodiscard]] LRESULT _OnPaint() noexcept;
void _OnMaximizeChange() noexcept;
void _OnDragBarSizeChanged(winrt::Windows::Foundation::IInspectable sender, winrt::Windows::UI::Xaml::SizeChangedEventArgs eventArgs) const;
[[nodiscard]] HRESULT _UpdateFrameMargins() const noexcept;
void _HandleActivateWindow();
bool _HandleWindowPosChanging(WINDOWPOS* const windowPos);
void _UpdateDragRegion();
void OnDragBarSizeChanged(winrt::Windows::Foundation::IInspectable sender, winrt::Windows::UI::Xaml::SizeChangedEventArgs eventArgs);
RECT GetMaxWindowRectInPixels(const RECT* const prcSuggested, _Out_opt_ UINT* pDpiSuggested);
void _UpdateMaximizedState();
void _UpdateIslandPosition(const UINT windowWidth, const UINT windowHeight);
void _UpdateIslandRegion() const;
void _UpdateFrameTheme() const;
};

View file

@ -38,7 +38,7 @@
<AdditionalIncludeDirectories>"$(OpenConsoleDir)src\cascadia\TerminalCore\lib\Generated Files";%(AdditionalIncludeDirectories);</AdditionalIncludeDirectories>
</ClCompile>
<Link>
<AdditionalDependencies>gdi32.lib;dwmapi.lib;Shcore.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalDependencies>gdi32.lib;dwmapi.lib;Shcore.lib;UxTheme.lib;%(AdditionalDependencies)</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<PropertyGroup>

19
src/types/ThemeUtils.cpp Normal file
View file

@ -0,0 +1,19 @@
#include "precomp.h"
#include "inc/ThemeUtils.h"
namespace Microsoft::Console::ThemeUtils
{
// Routine Description:
// - Attempts to enable/disable the dark mode on the frame of a window.
// Arguments:
// - hwnd: handle to the window to change
// - enabled: whether to enable or not the dark mode on the window's frame
// Return Value:
// - S_OK or suitable HRESULT from DWM engines.
[[nodiscard]] HRESULT SetWindowFrameDarkMode(HWND /* hwnd */, bool /* enabled */) noexcept
{
// TODO:GH #3425 implement the new DWM API and change
// src/interactivity/win32/windowtheme.cpp to use it.
return S_OK;
}
}

View file

@ -0,0 +1,8 @@
#pragma once
#include <Windows.h>
namespace Microsoft::Console::ThemeUtils
{
[[nodiscard]] HRESULT SetWindowFrameDarkMode(HWND hwnd, bool enabled) noexcept;
}

View file

@ -12,6 +12,7 @@
<ClCompile Include="..\MenuEvent.cpp" />
<ClCompile Include="..\ModifierKeyState.cpp" />
<ClCompile Include="..\ScreenInfoUiaProviderBase.cpp" />
<ClCompile Include="..\ThemeUtils.cpp" />
<ClCompile Include="..\UiaTextRangeBase.cpp" />
<ClCompile Include="..\Utf16Parser.cpp" />
<ClCompile Include="..\UTF8OutPipeReader.cpp" />
@ -30,7 +31,9 @@
<ClInclude Include="..\inc\convert.hpp" />
<ClInclude Include="..\inc\GlyphWidth.hpp" />
<ClInclude Include="..\inc\IInputEvent.hpp" />
<ClInclude Include="..\inc\ThemeUtils.h" />
<ClInclude Include="..\inc\UTF8OutPipeReader.hpp" />
<ClInclude Include="..\inc\utils.hpp" />
<ClInclude Include="..\inc\Viewport.hpp" />
<ClInclude Include="..\inc\Utf16Parser.hpp" />
<ClInclude Include="..\IUiaData.h" />
@ -38,7 +41,6 @@
<ClInclude Include="..\precomp.h" />
<ClInclude Include="..\ScreenInfoUiaProviderBase.h" />
<ClInclude Include="..\UiaTextRangeBase.hpp" />
<ClInclude Include="..\utils.hpp" />
<ClInclude Include="..\WindowUiaProviderBase.hpp" />
</ItemGroup>
<PropertyGroup>
@ -51,4 +53,4 @@
<!-- Careful reordering these. Some default props (contained in these files) are order sensitive. -->
<Import Project="$(SolutionDir)src\common.build.lib.props" />
<Import Project="$(SolutionDir)src\common.build.post.props" />
</Project>
</Project>

View file

@ -60,9 +60,6 @@
<ClCompile Include="..\UTF8OutPipeReader.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\WindowUiaProvider.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\ScreenInfoUiaProviderBase.cpp">
<Filter>Source Files</Filter>
</ClCompile>
@ -72,6 +69,9 @@
<ClCompile Include="..\WindowUiaProviderBase.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\ThemeUtils.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\inc\IInputEvent.hpp">
@ -95,21 +95,12 @@
<ClInclude Include="..\inc\GlyphWidth.hpp">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\utils.hpp">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\inc\UTF8OutPipeReader.hpp">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\WindowUiaProvider.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\IConsoleWindow.hpp">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\ScreenInfoUiaProvider.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\UiaTextRangeBase.hpp">
<Filter>Header Files</Filter>
</ClInclude>
@ -161,8 +152,14 @@
<ClInclude Include="..\IBaseData.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\inc\utils.hpp">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\inc\ThemeUtils.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<Natvis Include="$(SolutionDir)tools\ConsoleTypes.natvis" />
</ItemGroup>
</Project>
</Project>

View file

@ -41,6 +41,7 @@ SOURCES= \
..\convert.cpp \
..\Utf16Parser.cpp \
..\utils.cpp \
..\ThemeUtils.cpp \
INCLUDES= \
$(INCLUDES); \