[AlwaysOnTop] Round corners on Windows 11 (#19109)

* check window corners

* draw rounded rectangle

* draw rounded corners

* switch between rounded and not rounded rects

* added enabled corners setting

* update corner
This commit is contained in:
Seraphima Zykova
2022-07-01 17:56:45 +02:00
committed by GitHub
parent 35bb4280d0
commit d201ae4335
14 changed files with 175 additions and 28 deletions

View File

@@ -136,6 +136,7 @@
<ClCompile Include="trace.cpp" /> <ClCompile Include="trace.cpp" />
<ClCompile Include="VirtualDesktopUtils.cpp" /> <ClCompile Include="VirtualDesktopUtils.cpp" />
<ClCompile Include="WindowBorder.cpp" /> <ClCompile Include="WindowBorder.cpp" />
<ClCompile Include="WindowCornersUtil.cpp" />
<ClCompile Include="WinHookEventIDs.cpp" /> <ClCompile Include="WinHookEventIDs.cpp" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
@@ -150,6 +151,7 @@
<ClInclude Include="trace.h" /> <ClInclude Include="trace.h" />
<ClInclude Include="VirtualDesktopUtils.h" /> <ClInclude Include="VirtualDesktopUtils.h" />
<ClInclude Include="WindowBorder.h" /> <ClInclude Include="WindowBorder.h" />
<ClInclude Include="WindowCornersUtil.h" />
<ClInclude Include="WinHookEventIDs.h" /> <ClInclude Include="WinHookEventIDs.h" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@@ -42,6 +42,9 @@
<ClCompile Include="VirtualDesktopUtils.cpp"> <ClCompile Include="VirtualDesktopUtils.cpp">
<Filter>Source Files</Filter> <Filter>Source Files</Filter>
</ClCompile> </ClCompile>
<ClCompile Include="WindowCornersUtil.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<None Include="packages.config" /> <None Include="packages.config" />
@@ -83,5 +86,8 @@
<ClInclude Include="VirtualDesktopUtils.h"> <ClInclude Include="VirtualDesktopUtils.h">
<Filter>Header Files</Filter> <Filter>Header Files</Filter>
</ClInclude> </ClInclude>
<ClInclude Include="WindowCornersUtil.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@@ -86,17 +86,26 @@ void FrameDrawer::Show()
Render(); Render();
} }
void FrameDrawer::SetBorderRect(RECT windowRect, COLORREF color, int thickness) void FrameDrawer::SetBorderRect(RECT windowRect, COLORREF color, int thickness, int radius)
{ {
const auto newSceneRect = DrawableRect{ auto newSceneRect = DrawableRect{
.rect = ConvertRect(windowRect),
.borderColor = ConvertColor(color), .borderColor = ConvertColor(color),
.thickness = thickness .thickness = thickness,
}; };
if (radius != 0)
{
newSceneRect.roundedRect = ConvertRect(windowRect, thickness, radius);
}
else
{
newSceneRect.rect = ConvertRect(windowRect, thickness);
}
const bool colorUpdated = std::memcmp(&m_sceneRect.borderColor, &newSceneRect.borderColor, sizeof(newSceneRect.borderColor)); const bool colorUpdated = std::memcmp(&m_sceneRect.borderColor, &newSceneRect.borderColor, sizeof(newSceneRect.borderColor));
const bool thicknessUpdated = m_sceneRect.thickness != newSceneRect.thickness; const bool thicknessUpdated = m_sceneRect.thickness != newSceneRect.thickness;
const bool needsRedraw = colorUpdated || thicknessUpdated; const bool cornersUpdated = m_sceneRect.rect.has_value() != newSceneRect.rect.has_value() || m_sceneRect.roundedRect.has_value() != newSceneRect.roundedRect.has_value();
const bool needsRedraw = colorUpdated || thicknessUpdated || cornersUpdated;
RECT clientRect; RECT clientRect;
if (!SUCCEEDED(DwmGetWindowAttribute(m_window, DWMWA_EXTENDED_FRAME_BOUNDS, &clientRect, sizeof(clientRect)))) if (!SUCCEEDED(DwmGetWindowAttribute(m_window, DWMWA_EXTENDED_FRAME_BOUNDS, &clientRect, sizeof(clientRect))))
@@ -104,7 +113,7 @@ void FrameDrawer::SetBorderRect(RECT windowRect, COLORREF color, int thickness)
return; return;
} }
m_sceneRect = newSceneRect; m_sceneRect = std::move(newSceneRect);
const auto renderTargetSize = D2D1::SizeU(clientRect.right - clientRect.left, clientRect.bottom - clientRect.top); const auto renderTargetSize = D2D1::SizeU(clientRect.right - clientRect.left, clientRect.bottom - clientRect.top);
@@ -170,24 +179,38 @@ D2D1_COLOR_F FrameDrawer::ConvertColor(COLORREF color)
1.f); 1.f);
} }
D2D1_RECT_F FrameDrawer::ConvertRect(RECT rect) D2D1_ROUNDED_RECT FrameDrawer::ConvertRect(RECT rect, int thickness, int radius)
{ {
return D2D1::RectF((float)rect.left, (float)rect.top, (float)rect.right, (float)rect.bottom); auto d2d1Rect = D2D1::RectF((float)rect.left + thickness, (float)rect.top + thickness, (float)rect.right - thickness, (float)rect.bottom - thickness);
return D2D1::RoundedRect(d2d1Rect, (float)radius, (float)radius);
}
D2D1_RECT_F FrameDrawer::ConvertRect(RECT rect, int thickness)
{
return D2D1::RectF((float)rect.left + thickness, (float)rect.top + thickness, (float)rect.right - thickness, (float)rect.bottom - thickness);
} }
void FrameDrawer::Render() void FrameDrawer::Render()
{ {
if (!m_renderTarget) if (!m_renderTarget || !m_borderBrush)
{
return; return;
}
m_renderTarget->BeginDraw(); m_renderTarget->BeginDraw();
m_renderTarget->Clear(D2D1::ColorF(0.f, 0.f, 0.f, 0.f)); m_renderTarget->Clear(D2D1::ColorF(0.f, 0.f, 0.f, 0.f));
if (m_borderBrush) // The border stroke is centered on the line.
{
// The border stroke is centered on the line.
m_renderTarget->DrawRectangle(m_sceneRect.rect, m_borderBrush.get(), static_cast<float>(m_sceneRect.thickness * 2));
}
if (m_sceneRect.roundedRect)
{
m_renderTarget->DrawRoundedRectangle(m_sceneRect.roundedRect.value(), m_borderBrush.get(), static_cast<float>(m_sceneRect.thickness * 2));
}
else if (m_sceneRect.rect)
{
m_renderTarget->DrawRectangle(m_sceneRect.rect.value(), m_borderBrush.get(), static_cast<float>(m_sceneRect.thickness * 2));
}
m_renderTarget->EndDraw(); m_renderTarget->EndDraw();
} }

View File

@@ -18,14 +18,15 @@ public:
void Show(); void Show();
void Hide(); void Hide();
void SetBorderRect(RECT windowRect, COLORREF color, int thickness); void SetBorderRect(RECT windowRect, COLORREF color, int thickness, int radius);
private: private:
bool CreateRenderTargets(const RECT& clientRect); bool CreateRenderTargets(const RECT& clientRect);
struct DrawableRect struct DrawableRect
{ {
D2D1_RECT_F rect; std::optional<D2D1_RECT_F> rect;
std::optional<D2D1_ROUNDED_RECT> roundedRect;
D2D1_COLOR_F borderColor; D2D1_COLOR_F borderColor;
int thickness; int thickness;
}; };
@@ -33,7 +34,8 @@ private:
static ID2D1Factory* GetD2DFactory(); static ID2D1Factory* GetD2DFactory();
static IDWriteFactory* GetWriteFactory(); static IDWriteFactory* GetWriteFactory();
static D2D1_COLOR_F ConvertColor(COLORREF color); static D2D1_COLOR_F ConvertColor(COLORREF color);
static D2D1_RECT_F ConvertRect(RECT rect); static D2D1_ROUNDED_RECT ConvertRect(RECT rect, int thickness, int radius);
static D2D1_RECT_F ConvertRect(RECT rect, int thickness);
void Render(); void Render();
HWND m_window = nullptr; HWND m_window = nullptr;

View File

@@ -20,6 +20,7 @@ namespace NonLocalizable
const static wchar_t* BlockInGameModeID = L"do-not-activate-on-game-mode"; const static wchar_t* BlockInGameModeID = L"do-not-activate-on-game-mode";
const static wchar_t* ExcludedAppsID = L"excluded-apps"; const static wchar_t* ExcludedAppsID = L"excluded-apps";
const static wchar_t* FrameAccentColor = L"frame-accent-color"; const static wchar_t* FrameAccentColor = L"frame-accent-color";
const static wchar_t* RoundCornersEnabledID = L"round-corners-enabled";
} }
// TODO: move to common utils // TODO: move to common utils
@@ -153,6 +154,16 @@ void AlwaysOnTopSettings::LoadSettings()
} }
} }
if (const auto jsonVal = values.get_bool_value(NonLocalizable::RoundCornersEnabledID))
{
auto val = *jsonVal;
if (m_settings.roundCornersEnabled != val)
{
m_settings.roundCornersEnabled = val;
NotifyObservers(SettingId::RoundCornersEnabled);
}
}
if (auto jsonVal = values.get_string_value(NonLocalizable::ExcludedAppsID)) if (auto jsonVal = values.get_string_value(NonLocalizable::ExcludedAppsID))
{ {
std::wstring apps = std::move(*jsonVal); std::wstring apps = std::move(*jsonVal);

View File

@@ -17,6 +17,7 @@ struct Settings
PowerToysSettings::HotkeyObject hotkey = PowerToysSettings::HotkeyObject::from_settings(true, true, false, false, 84); // win + ctrl + T PowerToysSettings::HotkeyObject hotkey = PowerToysSettings::HotkeyObject::from_settings(true, true, false, false, 84); // win + ctrl + T
bool enableFrame = true; bool enableFrame = true;
bool enableSound = true; bool enableSound = true;
bool roundCornersEnabled = true;
bool blockInGameMode = true; bool blockInGameMode = true;
bool frameAccentColor = true; bool frameAccentColor = true;
int frameThickness = 15; int frameThickness = 15;

View File

@@ -9,5 +9,6 @@ enum class SettingId
FrameColor, FrameColor,
BlockInGameMode, BlockInGameMode,
ExcludeApps, ExcludeApps,
FrameAccentColor FrameAccentColor,
RoundCornersEnabled
}; };

View File

@@ -6,6 +6,7 @@
#include <FrameDrawer.h> #include <FrameDrawer.h>
#include <Settings.h> #include <Settings.h>
#include <WindowCornersUtil.h>
// Non-Localizable strings // Non-Localizable strings
namespace NonLocalizable namespace NonLocalizable
@@ -31,7 +32,7 @@ std::optional<RECT> GetFrameRect(HWND window)
} }
WindowBorder::WindowBorder(HWND window) : WindowBorder::WindowBorder(HWND window) :
SettingsObserver({ SettingId::FrameColor, SettingId::FrameThickness, SettingId::FrameAccentColor }), SettingsObserver({ SettingId::FrameColor, SettingId::FrameThickness, SettingId::FrameAccentColor, SettingId::RoundCornersEnabled }),
m_window(nullptr), m_window(nullptr),
m_trackingWindow(window), m_trackingWindow(window),
m_frameDrawer(nullptr) m_frameDrawer(nullptr)
@@ -187,7 +188,13 @@ void WindowBorder::UpdateBorderProperties() const
color = AlwaysOnTopSettings::settings().frameColor; color = AlwaysOnTopSettings::settings().frameColor;
} }
m_frameDrawer->SetBorderRect(frameRect, color, AlwaysOnTopSettings::settings().frameThickness); int cornerRadius = 0;
if (AlwaysOnTopSettings::settings().roundCornersEnabled)
{
cornerRadius = WindowBordersUtils::AreCornersRounded(m_trackingWindow) ? 8 : 0;
}
m_frameDrawer->SetBorderRect(frameRect, color, AlwaysOnTopSettings::settings().frameThickness, cornerRadius);
} }
LRESULT WindowBorder::WndProc(UINT message, WPARAM wparam, LPARAM lparam) noexcept LRESULT WindowBorder::WndProc(UINT message, WPARAM wparam, LPARAM lparam) noexcept
@@ -263,6 +270,12 @@ void WindowBorder::SettingsUpdate(SettingId id)
UpdateBorderProperties(); UpdateBorderProperties();
} }
break; break;
case SettingId::RoundCornersEnabled:
{
UpdateBorderProperties();
}
break;
default: default:
break; break;
} }

View File

@@ -0,0 +1,39 @@
#include "pch.h"
#include "WindowCornersUtil.h"
#include <common/utils/winapi_error.h>
#include <dwmapi.h>
// Placeholder enums since dwmapi.h doesn't have these until SDK 22000.
// TODO: Remove once SDK targets 22000 or above.
enum DWMWINDOWATTRIBUTE_CUSTOM
{
DWMWA_WINDOW_CORNER_PREFERENCE = 33
};
enum DWM_WINDOW_CORNER_PREFERENCE
{
DWMWCP_DEFAULT = 0,
DWMWCP_DONOTROUND = 1,
DWMWCP_ROUND = 2,
DWMWCP_ROUNDSMALL = 3
};
bool WindowBordersUtils::AreCornersRounded(HWND window) noexcept
{
int cornerPreference = DWMWCP_DEFAULT;
auto res = DwmGetWindowAttribute(window, DWMWA_WINDOW_CORNER_PREFERENCE, &cornerPreference, sizeof(cornerPreference));
if (res != S_OK)
{
// no need to spam with error log if arg is invalid (on Windows 10)
if (res != E_INVALIDARG)
{
Logger::error(L"Get corner preference error: {}", get_last_error_or_default(res));
}
return false;
}
return cornerPreference != DWM_WINDOW_CORNER_PREFERENCE::DWMWCP_DONOTROUND;
}

View File

@@ -0,0 +1,4 @@
namespace WindowBordersUtils
{
bool AreCornersRounded(HWND window) noexcept;
}

View File

@@ -14,9 +14,10 @@ namespace Microsoft.PowerToys.Settings.UI.Library
public const bool DefaultFrameEnabled = true; public const bool DefaultFrameEnabled = true;
public const int DefaultFrameThickness = 15; public const int DefaultFrameThickness = 15;
public const string DefaultFrameColor = "#0099cc"; public const string DefaultFrameColor = "#0099cc";
public const bool DefaultFrameAccentColor = true;
public const bool DefaultSoundEnabled = true; public const bool DefaultSoundEnabled = true;
public const bool DefaultDoNotActivateOnGameMode = true; public const bool DefaultDoNotActivateOnGameMode = true;
public const bool DefaultFrameAccentColor = true; public const bool DefaultRoundCornersEnabled = true;
public AlwaysOnTopProperties() public AlwaysOnTopProperties()
{ {
@@ -24,10 +25,11 @@ namespace Microsoft.PowerToys.Settings.UI.Library
FrameEnabled = new BoolProperty(DefaultFrameEnabled); FrameEnabled = new BoolProperty(DefaultFrameEnabled);
FrameThickness = new IntProperty(DefaultFrameThickness); FrameThickness = new IntProperty(DefaultFrameThickness);
FrameColor = new StringProperty(DefaultFrameColor); FrameColor = new StringProperty(DefaultFrameColor);
FrameAccentColor = new BoolProperty(DefaultFrameAccentColor);
SoundEnabled = new BoolProperty(DefaultSoundEnabled); SoundEnabled = new BoolProperty(DefaultSoundEnabled);
DoNotActivateOnGameMode = new BoolProperty(DefaultDoNotActivateOnGameMode); DoNotActivateOnGameMode = new BoolProperty(DefaultDoNotActivateOnGameMode);
RoundCornersEnabled = new BoolProperty(DefaultRoundCornersEnabled);
ExcludedApps = new StringProperty(); ExcludedApps = new StringProperty();
FrameAccentColor = new BoolProperty(DefaultFrameAccentColor);
} }
[JsonPropertyName("hotkey")] [JsonPropertyName("hotkey")]
@@ -42,6 +44,9 @@ namespace Microsoft.PowerToys.Settings.UI.Library
[JsonPropertyName("frame-color")] [JsonPropertyName("frame-color")]
public StringProperty FrameColor { get; set; } public StringProperty FrameColor { get; set; }
[JsonPropertyName("frame-accent-color")]
public BoolProperty FrameAccentColor { get; set; }
[JsonPropertyName("sound-enabled")] [JsonPropertyName("sound-enabled")]
public BoolProperty SoundEnabled { get; set; } public BoolProperty SoundEnabled { get; set; }
@@ -51,8 +56,8 @@ namespace Microsoft.PowerToys.Settings.UI.Library
[JsonPropertyName("excluded-apps")] [JsonPropertyName("excluded-apps")]
public StringProperty ExcludedApps { get; set; } public StringProperty ExcludedApps { get; set; }
[JsonPropertyName("frame-accent-color")] [JsonPropertyName("round-corners-enabled")]
public BoolProperty FrameAccentColor { get; set; } public BoolProperty RoundCornersEnabled { get; set; }
public string ToJsonString() public string ToJsonString()
{ {

View File

@@ -8,6 +8,7 @@ using System.Runtime.CompilerServices;
using System.Text.Json; using System.Text.Json;
using Microsoft.PowerToys.Settings.UI.Library.Helpers; using Microsoft.PowerToys.Settings.UI.Library.Helpers;
using Microsoft.PowerToys.Settings.UI.Library.Interfaces; using Microsoft.PowerToys.Settings.UI.Library.Interfaces;
using Microsoft.PowerToys.Settings.UI.Library.Utilities;
namespace Microsoft.PowerToys.Settings.UI.Library.ViewModels namespace Microsoft.PowerToys.Settings.UI.Library.ViewModels
{ {
@@ -51,10 +52,12 @@ namespace Microsoft.PowerToys.Settings.UI.Library.ViewModels
_frameEnabled = Settings.Properties.FrameEnabled.Value; _frameEnabled = Settings.Properties.FrameEnabled.Value;
_frameThickness = Settings.Properties.FrameThickness.Value; _frameThickness = Settings.Properties.FrameThickness.Value;
_frameColor = Settings.Properties.FrameColor.Value; _frameColor = Settings.Properties.FrameColor.Value;
_frameAccentColor = Settings.Properties.FrameAccentColor.Value;
_soundEnabled = Settings.Properties.SoundEnabled.Value; _soundEnabled = Settings.Properties.SoundEnabled.Value;
_doNotActivateOnGameMode = Settings.Properties.DoNotActivateOnGameMode.Value; _doNotActivateOnGameMode = Settings.Properties.DoNotActivateOnGameMode.Value;
_roundCornersEnabled = Settings.Properties.RoundCornersEnabled.Value;
_excludedApps = Settings.Properties.ExcludedApps.Value; _excludedApps = Settings.Properties.ExcludedApps.Value;
_frameAccentColor = Settings.Properties.FrameAccentColor.Value; _windows11 = Helper.Windows11();
// set the callback functions value to hangle outgoing IPC message. // set the callback functions value to hangle outgoing IPC message.
SendConfigMSG = ipcMSGCallBackFunc; SendConfigMSG = ipcMSGCallBackFunc;
@@ -186,6 +189,21 @@ namespace Microsoft.PowerToys.Settings.UI.Library.ViewModels
} }
} }
public bool RoundCornersEnabled
{
get => _roundCornersEnabled;
set
{
if (value != _roundCornersEnabled)
{
_roundCornersEnabled = value;
Settings.Properties.RoundCornersEnabled.Value = value;
NotifyPropertyChanged();
}
}
}
public string ExcludedApps public string ExcludedApps
{ {
get => _excludedApps; get => _excludedApps;
@@ -216,6 +234,16 @@ namespace Microsoft.PowerToys.Settings.UI.Library.ViewModels
} }
} }
public bool Windows11
{
get => _windows11;
set
{
_windows11 = value;
}
}
public void NotifyPropertyChanged([CallerMemberName] string propertyName = null) public void NotifyPropertyChanged([CallerMemberName] string propertyName = null)
{ {
OnPropertyChanged(propertyName); OnPropertyChanged(propertyName);
@@ -227,9 +255,11 @@ namespace Microsoft.PowerToys.Settings.UI.Library.ViewModels
private bool _frameEnabled; private bool _frameEnabled;
private int _frameThickness; private int _frameThickness;
private string _frameColor; private string _frameColor;
private bool _frameAccentColor;
private bool _soundEnabled; private bool _soundEnabled;
private bool _doNotActivateOnGameMode; private bool _doNotActivateOnGameMode;
private bool _roundCornersEnabled;
private string _excludedApps; private string _excludedApps;
private bool _frameAccentColor; private bool _windows11;
} }
} }

View File

@@ -1996,7 +1996,7 @@ From there, simply click on one of the supported files in the File Explorer and
<data name="FileExplorerPreview_ToggleSwitch_Preview_GCODE.Header" xml:space="preserve"> <data name="FileExplorerPreview_ToggleSwitch_Preview_GCODE.Header" xml:space="preserve">
<value>Geometric Code</value> <value>Geometric Code</value>
<comment>File type, do not translate</comment> <comment>File type, do not translate</comment>
</data> </data>
<data name="FileExplorerPreview_ToggleSwitch_Preview_GCODE.Description" xml:space="preserve"> <data name="FileExplorerPreview_ToggleSwitch_Preview_GCODE.Description" xml:space="preserve">
<value>Only .gcode files with embedded thumbnails are supported</value> <value>Only .gcode files with embedded thumbnails are supported</value>
</data> </data>
@@ -2125,4 +2125,7 @@ From there, simply click on one of the supported files in the File Explorer and
<data name="FancyZones_DisableRoundCornersOnWindowSnap.Content" xml:space="preserve"> <data name="FancyZones_DisableRoundCornersOnWindowSnap.Content" xml:space="preserve">
<value>Disable round corners when window is snapped</value> <value>Disable round corners when window is snapped</value>
</data> </data>
</root> <data name="AlwaysOnTop_RoundCorners.Content" xml:space="preserve">
<value>Enable round corners</value>
</data>
</root>

View File

@@ -12,6 +12,7 @@
<Page.Resources> <Page.Resources>
<converters:BoolToObjectConverter x:Key="BoolToComboBoxIndexConverter" TrueValue="1" FalseValue="0"/> <converters:BoolToObjectConverter x:Key="BoolToComboBoxIndexConverter" TrueValue="1" FalseValue="0"/>
<converters:BoolToVisibilityConverter x:Key="FalseToVisibleConverter" TrueValue="Collapsed" FalseValue="Visible"/> <converters:BoolToVisibilityConverter x:Key="FalseToVisibleConverter" TrueValue="Collapsed" FalseValue="Visible"/>
<converters:BoolToVisibilityConverter x:Key="TrueToVisibleConverter" TrueValue="Visible" FalseValue="Collapsed"/>
</Page.Resources> </Page.Resources>
<controls:SettingsPageControl x:Uid="AlwaysOnTop" IsTabStop="False" <controls:SettingsPageControl x:Uid="AlwaysOnTop" IsTabStop="False"
@@ -84,6 +85,12 @@
LargeChange="5"/> LargeChange="5"/>
</controls:Setting.ActionContent> </controls:Setting.ActionContent>
</controls:Setting> </controls:Setting>
<CheckBox x:Uid="AlwaysOnTop_RoundCorners"
IsChecked="{x:Bind Mode=TwoWay, Path=ViewModel.RoundCornersEnabled}"
Margin="{StaticResource ExpanderSettingMargin}"
Visibility="{x:Bind Mode=OneWay, Path=ViewModel.Windows11, Converter={StaticResource TrueToVisibleConverter}}"/>
</StackPanel> </StackPanel>
</controls:SettingExpander.Content> </controls:SettingExpander.Content>
</controls:SettingExpander> </controls:SettingExpander>