mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-04-04 02:06:36 +02:00
[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:
@@ -136,6 +136,7 @@
|
||||
<ClCompile Include="trace.cpp" />
|
||||
<ClCompile Include="VirtualDesktopUtils.cpp" />
|
||||
<ClCompile Include="WindowBorder.cpp" />
|
||||
<ClCompile Include="WindowCornersUtil.cpp" />
|
||||
<ClCompile Include="WinHookEventIDs.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
@@ -150,6 +151,7 @@
|
||||
<ClInclude Include="trace.h" />
|
||||
<ClInclude Include="VirtualDesktopUtils.h" />
|
||||
<ClInclude Include="WindowBorder.h" />
|
||||
<ClInclude Include="WindowCornersUtil.h" />
|
||||
<ClInclude Include="WinHookEventIDs.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
|
||||
@@ -42,6 +42,9 @@
|
||||
<ClCompile Include="VirtualDesktopUtils.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="WindowCornersUtil.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="packages.config" />
|
||||
@@ -83,5 +86,8 @@
|
||||
<ClInclude Include="VirtualDesktopUtils.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="WindowCornersUtil.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -86,17 +86,26 @@ void FrameDrawer::Show()
|
||||
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{
|
||||
.rect = ConvertRect(windowRect),
|
||||
auto newSceneRect = DrawableRect{
|
||||
.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 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;
|
||||
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;
|
||||
}
|
||||
|
||||
m_sceneRect = newSceneRect;
|
||||
m_sceneRect = std::move(newSceneRect);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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()
|
||||
{
|
||||
if (!m_renderTarget)
|
||||
if (!m_renderTarget || !m_borderBrush)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
m_renderTarget->BeginDraw();
|
||||
|
||||
m_renderTarget->Clear(D2D1::ColorF(0.f, 0.f, 0.f, 0.f));
|
||||
|
||||
if (m_borderBrush)
|
||||
{
|
||||
// The border stroke is centered on the line.
|
||||
m_renderTarget->DrawRectangle(m_sceneRect.rect, m_borderBrush.get(), static_cast<float>(m_sceneRect.thickness * 2));
|
||||
}
|
||||
// The border stroke is centered on the line.
|
||||
|
||||
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();
|
||||
}
|
||||
@@ -18,14 +18,15 @@ public:
|
||||
|
||||
void Show();
|
||||
void Hide();
|
||||
void SetBorderRect(RECT windowRect, COLORREF color, int thickness);
|
||||
void SetBorderRect(RECT windowRect, COLORREF color, int thickness, int radius);
|
||||
|
||||
private:
|
||||
bool CreateRenderTargets(const RECT& clientRect);
|
||||
|
||||
struct DrawableRect
|
||||
{
|
||||
D2D1_RECT_F rect;
|
||||
std::optional<D2D1_RECT_F> rect;
|
||||
std::optional<D2D1_ROUNDED_RECT> roundedRect;
|
||||
D2D1_COLOR_F borderColor;
|
||||
int thickness;
|
||||
};
|
||||
@@ -33,7 +34,8 @@ private:
|
||||
static ID2D1Factory* GetD2DFactory();
|
||||
static IDWriteFactory* GetWriteFactory();
|
||||
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();
|
||||
|
||||
HWND m_window = nullptr;
|
||||
|
||||
@@ -20,6 +20,7 @@ namespace NonLocalizable
|
||||
const static wchar_t* BlockInGameModeID = L"do-not-activate-on-game-mode";
|
||||
const static wchar_t* ExcludedAppsID = L"excluded-apps";
|
||||
const static wchar_t* FrameAccentColor = L"frame-accent-color";
|
||||
const static wchar_t* RoundCornersEnabledID = L"round-corners-enabled";
|
||||
}
|
||||
|
||||
// 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))
|
||||
{
|
||||
std::wstring apps = std::move(*jsonVal);
|
||||
|
||||
@@ -17,6 +17,7 @@ struct Settings
|
||||
PowerToysSettings::HotkeyObject hotkey = PowerToysSettings::HotkeyObject::from_settings(true, true, false, false, 84); // win + ctrl + T
|
||||
bool enableFrame = true;
|
||||
bool enableSound = true;
|
||||
bool roundCornersEnabled = true;
|
||||
bool blockInGameMode = true;
|
||||
bool frameAccentColor = true;
|
||||
int frameThickness = 15;
|
||||
|
||||
@@ -9,5 +9,6 @@ enum class SettingId
|
||||
FrameColor,
|
||||
BlockInGameMode,
|
||||
ExcludeApps,
|
||||
FrameAccentColor
|
||||
FrameAccentColor,
|
||||
RoundCornersEnabled
|
||||
};
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
#include <FrameDrawer.h>
|
||||
#include <Settings.h>
|
||||
#include <WindowCornersUtil.h>
|
||||
|
||||
// Non-Localizable strings
|
||||
namespace NonLocalizable
|
||||
@@ -31,7 +32,7 @@ std::optional<RECT> GetFrameRect(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_trackingWindow(window),
|
||||
m_frameDrawer(nullptr)
|
||||
@@ -187,7 +188,13 @@ void WindowBorder::UpdateBorderProperties() const
|
||||
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
|
||||
@@ -263,6 +270,12 @@ void WindowBorder::SettingsUpdate(SettingId id)
|
||||
UpdateBorderProperties();
|
||||
}
|
||||
break;
|
||||
|
||||
case SettingId::RoundCornersEnabled:
|
||||
{
|
||||
UpdateBorderProperties();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
39
src/modules/alwaysontop/AlwaysOnTop/WindowCornersUtil.cpp
Normal file
39
src/modules/alwaysontop/AlwaysOnTop/WindowCornersUtil.cpp
Normal 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;
|
||||
}
|
||||
4
src/modules/alwaysontop/AlwaysOnTop/WindowCornersUtil.h
Normal file
4
src/modules/alwaysontop/AlwaysOnTop/WindowCornersUtil.h
Normal file
@@ -0,0 +1,4 @@
|
||||
namespace WindowBordersUtils
|
||||
{
|
||||
bool AreCornersRounded(HWND window) noexcept;
|
||||
}
|
||||
@@ -14,9 +14,10 @@ namespace Microsoft.PowerToys.Settings.UI.Library
|
||||
public const bool DefaultFrameEnabled = true;
|
||||
public const int DefaultFrameThickness = 15;
|
||||
public const string DefaultFrameColor = "#0099cc";
|
||||
public const bool DefaultFrameAccentColor = true;
|
||||
public const bool DefaultSoundEnabled = true;
|
||||
public const bool DefaultDoNotActivateOnGameMode = true;
|
||||
public const bool DefaultFrameAccentColor = true;
|
||||
public const bool DefaultRoundCornersEnabled = true;
|
||||
|
||||
public AlwaysOnTopProperties()
|
||||
{
|
||||
@@ -24,10 +25,11 @@ namespace Microsoft.PowerToys.Settings.UI.Library
|
||||
FrameEnabled = new BoolProperty(DefaultFrameEnabled);
|
||||
FrameThickness = new IntProperty(DefaultFrameThickness);
|
||||
FrameColor = new StringProperty(DefaultFrameColor);
|
||||
FrameAccentColor = new BoolProperty(DefaultFrameAccentColor);
|
||||
SoundEnabled = new BoolProperty(DefaultSoundEnabled);
|
||||
DoNotActivateOnGameMode = new BoolProperty(DefaultDoNotActivateOnGameMode);
|
||||
RoundCornersEnabled = new BoolProperty(DefaultRoundCornersEnabled);
|
||||
ExcludedApps = new StringProperty();
|
||||
FrameAccentColor = new BoolProperty(DefaultFrameAccentColor);
|
||||
}
|
||||
|
||||
[JsonPropertyName("hotkey")]
|
||||
@@ -42,6 +44,9 @@ namespace Microsoft.PowerToys.Settings.UI.Library
|
||||
[JsonPropertyName("frame-color")]
|
||||
public StringProperty FrameColor { get; set; }
|
||||
|
||||
[JsonPropertyName("frame-accent-color")]
|
||||
public BoolProperty FrameAccentColor { get; set; }
|
||||
|
||||
[JsonPropertyName("sound-enabled")]
|
||||
public BoolProperty SoundEnabled { get; set; }
|
||||
|
||||
@@ -51,8 +56,8 @@ namespace Microsoft.PowerToys.Settings.UI.Library
|
||||
[JsonPropertyName("excluded-apps")]
|
||||
public StringProperty ExcludedApps { get; set; }
|
||||
|
||||
[JsonPropertyName("frame-accent-color")]
|
||||
public BoolProperty FrameAccentColor { get; set; }
|
||||
[JsonPropertyName("round-corners-enabled")]
|
||||
public BoolProperty RoundCornersEnabled { get; set; }
|
||||
|
||||
public string ToJsonString()
|
||||
{
|
||||
|
||||
@@ -8,6 +8,7 @@ using System.Runtime.CompilerServices;
|
||||
using System.Text.Json;
|
||||
using Microsoft.PowerToys.Settings.UI.Library.Helpers;
|
||||
using Microsoft.PowerToys.Settings.UI.Library.Interfaces;
|
||||
using Microsoft.PowerToys.Settings.UI.Library.Utilities;
|
||||
|
||||
namespace Microsoft.PowerToys.Settings.UI.Library.ViewModels
|
||||
{
|
||||
@@ -51,10 +52,12 @@ namespace Microsoft.PowerToys.Settings.UI.Library.ViewModels
|
||||
_frameEnabled = Settings.Properties.FrameEnabled.Value;
|
||||
_frameThickness = Settings.Properties.FrameThickness.Value;
|
||||
_frameColor = Settings.Properties.FrameColor.Value;
|
||||
_frameAccentColor = Settings.Properties.FrameAccentColor.Value;
|
||||
_soundEnabled = Settings.Properties.SoundEnabled.Value;
|
||||
_doNotActivateOnGameMode = Settings.Properties.DoNotActivateOnGameMode.Value;
|
||||
_roundCornersEnabled = Settings.Properties.RoundCornersEnabled.Value;
|
||||
_excludedApps = Settings.Properties.ExcludedApps.Value;
|
||||
_frameAccentColor = Settings.Properties.FrameAccentColor.Value;
|
||||
_windows11 = Helper.Windows11();
|
||||
|
||||
// set the callback functions value to hangle outgoing IPC message.
|
||||
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
|
||||
{
|
||||
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)
|
||||
{
|
||||
OnPropertyChanged(propertyName);
|
||||
@@ -227,9 +255,11 @@ namespace Microsoft.PowerToys.Settings.UI.Library.ViewModels
|
||||
private bool _frameEnabled;
|
||||
private int _frameThickness;
|
||||
private string _frameColor;
|
||||
private bool _frameAccentColor;
|
||||
private bool _soundEnabled;
|
||||
private bool _doNotActivateOnGameMode;
|
||||
private bool _roundCornersEnabled;
|
||||
private string _excludedApps;
|
||||
private bool _frameAccentColor;
|
||||
private bool _windows11;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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">
|
||||
<value>Geometric Code</value>
|
||||
<comment>File type, do not translate</comment>
|
||||
</data>
|
||||
</data>
|
||||
<data name="FileExplorerPreview_ToggleSwitch_Preview_GCODE.Description" xml:space="preserve">
|
||||
<value>Only .gcode files with embedded thumbnails are supported</value>
|
||||
</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">
|
||||
<value>Disable round corners when window is snapped</value>
|
||||
</data>
|
||||
</root>
|
||||
<data name="AlwaysOnTop_RoundCorners.Content" xml:space="preserve">
|
||||
<value>Enable round corners</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -12,6 +12,7 @@
|
||||
<Page.Resources>
|
||||
<converters:BoolToObjectConverter x:Key="BoolToComboBoxIndexConverter" TrueValue="1" FalseValue="0"/>
|
||||
<converters:BoolToVisibilityConverter x:Key="FalseToVisibleConverter" TrueValue="Collapsed" FalseValue="Visible"/>
|
||||
<converters:BoolToVisibilityConverter x:Key="TrueToVisibleConverter" TrueValue="Visible" FalseValue="Collapsed"/>
|
||||
</Page.Resources>
|
||||
|
||||
<controls:SettingsPageControl x:Uid="AlwaysOnTop" IsTabStop="False"
|
||||
@@ -84,6 +85,12 @@
|
||||
LargeChange="5"/>
|
||||
</controls:Setting.ActionContent>
|
||||
</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>
|
||||
</controls:SettingExpander.Content>
|
||||
</controls:SettingExpander>
|
||||
|
||||
Reference in New Issue
Block a user