diff --git a/src/modules/alwaysontop/AlwaysOnTop/AlwaysOnTop.vcxproj b/src/modules/alwaysontop/AlwaysOnTop/AlwaysOnTop.vcxproj
index f06126b3fb..b7091defa9 100644
--- a/src/modules/alwaysontop/AlwaysOnTop/AlwaysOnTop.vcxproj
+++ b/src/modules/alwaysontop/AlwaysOnTop/AlwaysOnTop.vcxproj
@@ -136,6 +136,7 @@
+
@@ -150,6 +151,7 @@
+
diff --git a/src/modules/alwaysontop/AlwaysOnTop/AlwaysOnTop.vcxproj.filters b/src/modules/alwaysontop/AlwaysOnTop/AlwaysOnTop.vcxproj.filters
index 8eeaaded4f..d0ec584675 100644
--- a/src/modules/alwaysontop/AlwaysOnTop/AlwaysOnTop.vcxproj.filters
+++ b/src/modules/alwaysontop/AlwaysOnTop/AlwaysOnTop.vcxproj.filters
@@ -42,6 +42,9 @@
Source Files
+
+ Source Files
+
@@ -83,5 +86,8 @@
Header Files
+
+ Header Files
+
\ No newline at end of file
diff --git a/src/modules/alwaysontop/AlwaysOnTop/FrameDrawer.cpp b/src/modules/alwaysontop/AlwaysOnTop/FrameDrawer.cpp
index b22e8644f1..ab0833ca86 100644
--- a/src/modules/alwaysontop/AlwaysOnTop/FrameDrawer.cpp
+++ b/src/modules/alwaysontop/AlwaysOnTop/FrameDrawer.cpp
@@ -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(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(m_sceneRect.thickness * 2));
+ }
+ else if (m_sceneRect.rect)
+ {
+ m_renderTarget->DrawRectangle(m_sceneRect.rect.value(), m_borderBrush.get(), static_cast(m_sceneRect.thickness * 2));
+ }
+
m_renderTarget->EndDraw();
}
\ No newline at end of file
diff --git a/src/modules/alwaysontop/AlwaysOnTop/FrameDrawer.h b/src/modules/alwaysontop/AlwaysOnTop/FrameDrawer.h
index 19f44487e6..d3fb27d411 100644
--- a/src/modules/alwaysontop/AlwaysOnTop/FrameDrawer.h
+++ b/src/modules/alwaysontop/AlwaysOnTop/FrameDrawer.h
@@ -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 rect;
+ std::optional 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;
diff --git a/src/modules/alwaysontop/AlwaysOnTop/Settings.cpp b/src/modules/alwaysontop/AlwaysOnTop/Settings.cpp
index 8122f0cf86..f91ce253a8 100644
--- a/src/modules/alwaysontop/AlwaysOnTop/Settings.cpp
+++ b/src/modules/alwaysontop/AlwaysOnTop/Settings.cpp
@@ -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);
diff --git a/src/modules/alwaysontop/AlwaysOnTop/Settings.h b/src/modules/alwaysontop/AlwaysOnTop/Settings.h
index 95a9192126..7adf839423 100644
--- a/src/modules/alwaysontop/AlwaysOnTop/Settings.h
+++ b/src/modules/alwaysontop/AlwaysOnTop/Settings.h
@@ -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;
diff --git a/src/modules/alwaysontop/AlwaysOnTop/SettingsConstants.h b/src/modules/alwaysontop/AlwaysOnTop/SettingsConstants.h
index 87bd5d6e91..f6e9cc6cb1 100644
--- a/src/modules/alwaysontop/AlwaysOnTop/SettingsConstants.h
+++ b/src/modules/alwaysontop/AlwaysOnTop/SettingsConstants.h
@@ -9,5 +9,6 @@ enum class SettingId
FrameColor,
BlockInGameMode,
ExcludeApps,
- FrameAccentColor
+ FrameAccentColor,
+ RoundCornersEnabled
};
\ No newline at end of file
diff --git a/src/modules/alwaysontop/AlwaysOnTop/WindowBorder.cpp b/src/modules/alwaysontop/AlwaysOnTop/WindowBorder.cpp
index 02001bd0af..f4232b1c49 100644
--- a/src/modules/alwaysontop/AlwaysOnTop/WindowBorder.cpp
+++ b/src/modules/alwaysontop/AlwaysOnTop/WindowBorder.cpp
@@ -6,6 +6,7 @@
#include
#include
+#include
// Non-Localizable strings
namespace NonLocalizable
@@ -31,7 +32,7 @@ std::optional 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;
}
diff --git a/src/modules/alwaysontop/AlwaysOnTop/WindowCornersUtil.cpp b/src/modules/alwaysontop/AlwaysOnTop/WindowCornersUtil.cpp
new file mode 100644
index 0000000000..9a594d211f
--- /dev/null
+++ b/src/modules/alwaysontop/AlwaysOnTop/WindowCornersUtil.cpp
@@ -0,0 +1,39 @@
+#include "pch.h"
+#include "WindowCornersUtil.h"
+
+#include
+
+#include
+
+// 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;
+}
diff --git a/src/modules/alwaysontop/AlwaysOnTop/WindowCornersUtil.h b/src/modules/alwaysontop/AlwaysOnTop/WindowCornersUtil.h
new file mode 100644
index 0000000000..db48178532
--- /dev/null
+++ b/src/modules/alwaysontop/AlwaysOnTop/WindowCornersUtil.h
@@ -0,0 +1,4 @@
+namespace WindowBordersUtils
+{
+ bool AreCornersRounded(HWND window) noexcept;
+}
\ No newline at end of file
diff --git a/src/settings-ui/Settings.UI.Library/AlwaysOnTopProperties.cs b/src/settings-ui/Settings.UI.Library/AlwaysOnTopProperties.cs
index a4cf4c0b95..d92d1f0c67 100644
--- a/src/settings-ui/Settings.UI.Library/AlwaysOnTopProperties.cs
+++ b/src/settings-ui/Settings.UI.Library/AlwaysOnTopProperties.cs
@@ -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()
{
diff --git a/src/settings-ui/Settings.UI.Library/ViewModels/AlwaysOnTopViewModel.cs b/src/settings-ui/Settings.UI.Library/ViewModels/AlwaysOnTopViewModel.cs
index 4fac7c85c9..9cdd21092a 100644
--- a/src/settings-ui/Settings.UI.Library/ViewModels/AlwaysOnTopViewModel.cs
+++ b/src/settings-ui/Settings.UI.Library/ViewModels/AlwaysOnTopViewModel.cs
@@ -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;
}
}
diff --git a/src/settings-ui/Settings.UI/Strings/en-us/Resources.resw b/src/settings-ui/Settings.UI/Strings/en-us/Resources.resw
index 53f8b38a77..6f5eb64fba 100644
--- a/src/settings-ui/Settings.UI/Strings/en-us/Resources.resw
+++ b/src/settings-ui/Settings.UI/Strings/en-us/Resources.resw
@@ -1996,7 +1996,7 @@ From there, simply click on one of the supported files in the File Explorer and
Geometric Code
File type, do not translate
-
+
Only .gcode files with embedded thumbnails are supported
@@ -2125,4 +2125,7 @@ From there, simply click on one of the supported files in the File Explorer and
Disable round corners when window is snapped
-
+
+ Enable round corners
+
+
\ No newline at end of file
diff --git a/src/settings-ui/Settings.UI/Views/AlwaysOnTopPage.xaml b/src/settings-ui/Settings.UI/Views/AlwaysOnTopPage.xaml
index 236327e65d..053079e3e3 100644
--- a/src/settings-ui/Settings.UI/Views/AlwaysOnTopPage.xaml
+++ b/src/settings-ui/Settings.UI/Views/AlwaysOnTopPage.xaml
@@ -12,6 +12,7 @@
+
+
+
+