[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="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>

View File

@@ -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>

View File

@@ -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();
}

View File

@@ -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;

View File

@@ -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);

View File

@@ -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;

View File

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

View File

@@ -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;
}

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;
}