mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-04-04 10:16:24 +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;
|
||||
}
|
||||
Reference in New Issue
Block a user