[FancyZones] Zone title bar tabs style

This commit is contained in:
float4
2022-01-30 00:00:00 +00:00
parent fa2a9a9172
commit 8d3617cdbd
15 changed files with 636 additions and 64 deletions

View File

@@ -0,0 +1,164 @@
#include "pch.h"
#include "CompositionDrawing.h"
#include <common/logger/logger.h>
void CompositionDrawing::Init(HWND window)
{
m_window = window;
// Obtain the size of the drawing area
if (!GetClientRect(window, m_renderRect.get()))
{
Logger::error("couldn't initialize CompositionDrawing: GetClientRect failed");
return;
}
// Create devices
D2D1_FACTORY_OPTIONS options = {
#ifdef _DEBUG
D2D1_DEBUG_LEVEL_INFORMATION
#endif
};
D2D1CreateFactory(
D2D1_FACTORY_TYPE_MULTI_THREADED,
__uuidof(m_d2dFactory),
&options,
m_d2dFactory.put_void());
if (!m_d2dFactory)
{
return;
}
D3D11CreateDevice(
nullptr,
D3D_DRIVER_TYPE_HARDWARE,
nullptr,
D3D11_CREATE_DEVICE_BGRA_SUPPORT,
nullptr,
0,
D3D11_SDK_VERSION,
m_d3dDevice.put(),
nullptr,
nullptr);
if (!m_d3dDevice)
{
return;
}
m_d3dDevice->QueryInterface(__uuidof(m_dxgiDevice), m_dxgiDevice.put_void());
if (!m_dxgiDevice)
{
return;
}
CreateDXGIFactory2(0, __uuidof(m_dxgiFactory), m_dxgiFactory.put_void());
if (!m_dxgiFactory)
{
return;
}
m_d2dFactory->CreateDevice(m_dxgiDevice.get(), m_d2dDevice.put());
if (!m_d2dDevice)
{
return;
}
winrt::com_ptr<ID2D1DeviceContext5> device_context;
m_d2dDevice->CreateDeviceContext(D2D1_DEVICE_CONTEXT_OPTIONS_NONE, device_context.put());
if (!device_context)
{
return;
}
m_renderTarget = device_context;
// Size specific
if (m_renderRect.width() == 0 || m_renderRect.height() == 0)
{
return;
}
DXGI_SWAP_CHAIN_DESC1 sc_description = {};
sc_description.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
sc_description.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
sc_description.SwapEffect = DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL;
sc_description.BufferCount = 2;
sc_description.SampleDesc.Count = 1;
sc_description.AlphaMode = DXGI_ALPHA_MODE_PREMULTIPLIED;
sc_description.Width = m_renderRect.width();
sc_description.Height = m_renderRect.height();
m_dxgiFactory->CreateSwapChainForComposition(
m_dxgiDevice.get(),
&sc_description,
nullptr,
m_dxgiSwapChain.put());
if (!m_dxgiSwapChain)
{
return;
}
DCompositionCreateDevice(
m_dxgiDevice.get(),
__uuidof(m_compositionDevice),
m_compositionDevice.put_void());
if (!m_compositionDevice)
{
return;
}
m_compositionDevice->CreateTargetForHwnd(m_window, true, m_compositionTarget.put());
if (!m_compositionTarget)
{
return;
}
m_compositionDevice->CreateVisual(m_compositionVisual.put());
if (!m_compositionVisual)
{
return;
}
m_compositionVisual->SetContent(m_dxgiSwapChain.get());
m_compositionTarget->SetRoot(m_compositionVisual.get());
m_dxgiSwapChain->GetBuffer(0, __uuidof(m_dxgiSurface), m_dxgiSurface.put_void());
if (!m_dxgiSurface)
{
return;
}
D2D1_BITMAP_PROPERTIES1 properties = {};
properties.pixelFormat.alphaMode = D2D1_ALPHA_MODE_PREMULTIPLIED;
properties.pixelFormat.format = DXGI_FORMAT_B8G8R8A8_UNORM;
properties.bitmapOptions = D2D1_BITMAP_OPTIONS_TARGET | D2D1_BITMAP_OPTIONS_CANNOT_DRAW;
device_context->CreateBitmapFromDxgiSurface(
m_dxgiSurface.get(),
properties,
m_d2dBitmap.put());
if (!m_d2dBitmap)
{
return;
}
device_context->SetTarget(m_d2dBitmap.get());
}
void CompositionDrawing::BeginDraw()
{
m_renderTarget->BeginDraw();
}
void CompositionDrawing::EndDraw()
{
m_renderTarget->EndDraw();
if (m_dxgiSwapChain && m_compositionDevice)
{
m_dxgiSwapChain->Present(1, 0);
m_compositionDevice->Commit();
}
}

View File

@@ -0,0 +1,34 @@
#pragma once
#include "Drawing.h"
#include <winrt/base.h>
#include <Windows.h>
#include <dxgi1_3.h>
#include <d3d11_2.h>
#include <d2d1_3.h>
#include <d2d1_3helper.h>
#include <d2d1helper.h>
#include <dcomp.h>
#include <dwmapi.h>
#include <string>
class CompositionDrawing : public Drawing
{
public:
void Init(HWND window);
void BeginDraw();
void EndDraw();
private:
winrt::com_ptr<ID3D11Device> m_d3dDevice;
winrt::com_ptr<IDXGIDevice> m_dxgiDevice;
winrt::com_ptr<IDXGIFactory2> m_dxgiFactory;
winrt::com_ptr<IDXGISwapChain1> m_dxgiSwapChain;
winrt::com_ptr<IDCompositionDevice> m_compositionDevice;
winrt::com_ptr<IDCompositionTarget> m_compositionTarget;
winrt::com_ptr<IDCompositionVisual> m_compositionVisual;
winrt::com_ptr<IDXGISurface2> m_dxgiSurface;
winrt::com_ptr<ID2D1Bitmap1> m_d2dBitmap;
winrt::com_ptr<ID2D1Factory6> m_d2dFactory;
winrt::com_ptr<ID2D1Device5> m_d2dDevice;
};

View File

@@ -41,10 +41,15 @@ IWICImagingFactory2* Drawing::GetImageFactory()
return pImageFactory;
}
Drawing::Drawing(HWND window)
Drawing::Drawing()
{
m_window = nullptr;
m_renderTarget = nullptr;
}
void Drawing::Init(HWND window)
{
m_window = window;
m_renderTarget = nullptr;
// Obtain the size of the drawing area.
if (!GetClientRect(window, m_renderRect.get()))
@@ -64,12 +69,15 @@ Drawing::Drawing(HWND window)
auto renderTargetSize = D2D1::SizeU(m_renderRect.width(), m_renderRect.height());
auto hwndRenderTargetProperties = D2D1::HwndRenderTargetProperties(window, renderTargetSize);
auto hr = GetD2DFactory()->CreateHwndRenderTarget(renderTargetProperties, hwndRenderTargetProperties, m_renderTarget.put());
winrt::com_ptr<ID2D1HwndRenderTarget> renderTarget = nullptr;
auto hr = GetD2DFactory()->CreateHwndRenderTarget(renderTargetProperties, hwndRenderTargetProperties, renderTarget.put());
if (!SUCCEEDED(hr))
{
Logger::error("couldn't initialize Drawing: CreateHwndRenderTarget failed with {}", hr);
return;
}
m_renderTarget = renderTarget;
}
Drawing::operator bool() const
@@ -85,7 +93,7 @@ void Drawing::BeginDraw(const D2D1_COLOR_F& backColor)
m_renderTarget->Clear(backColor);
}
winrt::com_ptr<IDWriteTextFormat> Drawing::CreateTextFormat(LPCWSTR fontFamilyName, FLOAT fontSize)
winrt::com_ptr<IDWriteTextFormat> Drawing::CreateTextFormat(LPCWSTR fontFamilyName, FLOAT fontSize, DWRITE_FONT_WEIGHT fontWeight)
{
winrt::com_ptr<IDWriteTextFormat> textFormat = nullptr;
@@ -93,7 +101,7 @@ winrt::com_ptr<IDWriteTextFormat> Drawing::CreateTextFormat(LPCWSTR fontFamilyNa
if (writeFactory)
{
writeFactory->CreateTextFormat(fontFamilyName, nullptr, DWRITE_FONT_WEIGHT_NORMAL, DWRITE_FONT_STYLE_NORMAL, DWRITE_FONT_STRETCH_NORMAL, fontSize, L"en-US", textFormat.put());
writeFactory->CreateTextFormat(fontFamilyName, nullptr, fontWeight, DWRITE_FONT_STYLE_NORMAL, DWRITE_FONT_STRETCH_NORMAL, fontSize, L"en-US", textFormat.put());
}
return textFormat;
@@ -151,6 +159,24 @@ void Drawing::FillRectangle(const D2D1_RECT_F& rect, D2D1_COLOR_F color)
}
}
void Drawing::FillRoundedRectangle(const D2D1_RECT_F& rect, D2D1_COLOR_F color)
{
auto brush = CreateBrush(color);
if (brush)
{
D2D1_ROUNDED_RECT roundedRect;
roundedRect.rect = rect;
roundedRect.radiusX = (rect.right - rect.left) * .1f;
roundedRect.radiusY = (rect.bottom - rect.top) * .1f;
auto radius = min(roundedRect.radiusX, roundedRect.radiusY);
roundedRect.radiusX = radius;
roundedRect.radiusY = radius;
m_renderTarget->FillRoundedRectangle(roundedRect, brush.get());
}
}
void Drawing::DrawRectangle(const D2D1_RECT_F& rect, D2D1_COLOR_F color, float strokeWidth)
{
auto brush = CreateBrush(color);
@@ -184,6 +210,25 @@ void Drawing::DrawTextW(std::wstring text, IDWriteTextFormat* textFormat, const
}
}
void Drawing::DrawTextTrim(std::wstring text, IDWriteTextFormat* textFormat, const D2D1_RECT_F& rect, D2D1_COLOR_F color)
{
auto brush = CreateBrush(color);
winrt::com_ptr<IDWriteInlineObject> ellipsis;
GetWriteFactory()->CreateEllipsisTrimmingSign(textFormat, ellipsis.put());
if (brush && ellipsis)
{
DWRITE_TRIMMING trimming{};
trimming.granularity = DWRITE_TRIMMING_GRANULARITY_CHARACTER;
textFormat->SetTrimming(&trimming, ellipsis.get());
textFormat->SetWordWrapping(DWRITE_WORD_WRAPPING_NO_WRAP);
textFormat->SetTextAlignment(DWRITE_TEXT_ALIGNMENT_LEADING);
textFormat->SetParagraphAlignment(DWRITE_PARAGRAPH_ALIGNMENT_CENTER);
m_renderTarget->DrawTextW(text.c_str(), (UINT32)text.size(), textFormat, rect, brush.get());
}
}
void Drawing::DrawBitmap(const D2D1_RECT_F& rect, ID2D1Bitmap* bitmap)
{
m_renderTarget->DrawBitmap(bitmap, rect);

View File

@@ -11,29 +11,32 @@ class Drawing
public:
static D2D1_COLOR_F ConvertColor(COLORREF color);
Drawing(HWND window);
Drawing();
void Init(HWND window);
operator bool() const;
void BeginDraw(const D2D1_COLOR_F& backColor);
winrt::com_ptr<IDWriteTextFormat> CreateTextFormat(LPCWSTR fontFamilyName, FLOAT fontSize);
winrt::com_ptr<IDWriteTextFormat> CreateTextFormat(LPCWSTR fontFamilyName, FLOAT fontSize, DWRITE_FONT_WEIGHT fontWeight = DWRITE_FONT_WEIGHT_NORMAL);
winrt::com_ptr<ID2D1SolidColorBrush> CreateBrush(D2D1_COLOR_F color);
winrt::com_ptr<ID2D1Bitmap> CreateIcon(HICON icon);
void FillRectangle(const D2D1_RECT_F& rect, D2D1_COLOR_F color);
void FillRoundedRectangle(const D2D1_RECT_F& rect, D2D1_COLOR_F color);
void DrawRectangle(const D2D1_RECT_F& rect, D2D1_COLOR_F color, float strokeWidth = 1.0f);
void DrawRoundedRectangle(const D2D1_RECT_F& rect, D2D1_COLOR_F color, float strokeWidth = 1.0f);
void DrawTextW(std::wstring text, IDWriteTextFormat* format, const D2D1_RECT_F& rect, D2D1_COLOR_F color);
void DrawTextTrim(std::wstring text, IDWriteTextFormat* format, const D2D1_RECT_F& rect, D2D1_COLOR_F color);
void DrawBitmap(const D2D1_RECT_F& rect, ID2D1Bitmap* bitmap);
void EndDraw();
private:
protected:
static ID2D1Factory* GetD2DFactory();
static IDWriteFactory* GetWriteFactory();
static IWICImagingFactory2* GetImageFactory();
HWND m_window = nullptr;
FancyZonesUtils::Rect m_renderRect{};
winrt::com_ptr<ID2D1HwndRenderTarget> m_renderTarget = nullptr;
winrt::com_ptr<ID2D1RenderTarget> m_renderTarget = nullptr;
};

View File

@@ -35,11 +35,15 @@
<PreprocessorDefinitions>_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<AdditionalIncludeDirectories>..\;..\..\..\common\inc;..\..\..\common\Telemetry;..\..\;..\..\..\;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
</ClCompile>
<Lib>
<AdditionalDependencies>Dcomp.lib;%(AdditionalDependencies)</AdditionalDependencies>
</Lib>
</ItemDefinitionGroup>
<ItemGroup>
<ClInclude Include="FancyZonesData\CustomLayouts.h" />
<ClInclude Include="FancyZonesData\AppliedLayouts.h" />
<ClInclude Include="FancyZonesData\AppZoneHistory.h" />
<ClInclude Include="CompositionDrawing.h" />
<ClInclude Include="Drawing.h" />
<ClInclude Include="FancyZones.h" />
<ClInclude Include="FancyZonesDataTypes.h" />
@@ -83,6 +87,7 @@
<PrecompiledHeaderFile Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">../pch.h</PrecompiledHeaderFile>
<PrecompiledHeaderFile Condition="'$(Configuration)|$(Platform)'=='Release|x64'">../pch.h</PrecompiledHeaderFile>
</ClCompile>
<ClCompile Include="CompositionDrawing.cpp" />
<ClCompile Include="Drawing.cpp" />
<ClCompile Include="FancyZones.cpp" />
<ClCompile Include="FancyZonesDataTypes.cpp" />

View File

@@ -123,6 +123,9 @@
<ClInclude Include="Drawing.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="CompositionDrawing.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="pch.cpp">
@@ -206,6 +209,9 @@
<ClCompile Include="Drawing.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="CompositionDrawing.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />

View File

@@ -16,7 +16,8 @@ enum struct ZoneTitleBarStyle : int
None = 0,
Numbers = 1,
Icons = 2,
EnumElements = 3, // number of elements in the enum, not counting this
Tabs = 3,
EnumElements = 4, // number of elements in the enum, not counting this
};
// in reality, this file needs to be kept in sync currently with src/settings-ui/Settings.UI.Library/FZConfigProperties.cs
@@ -48,7 +49,7 @@ struct Settings
std::wstring zoneNumberColor = L"#000000";
int zoneHighlightOpacity = 50;
OverlappingZonesAlgorithm overlappingZonesAlgorithm = OverlappingZonesAlgorithm::Smallest;
ZoneTitleBarStyle zoneTitleBarStyle = ZoneTitleBarStyle::Icons;
ZoneTitleBarStyle zoneTitleBarStyle = ZoneTitleBarStyle::Tabs;
PowerToysSettings::HotkeyObject editorHotkey = PowerToysSettings::HotkeyObject::from_settings(true, false, false, true, VK_OEM_3);
bool windowSwitching = true;
PowerToysSettings::HotkeyObject nextTabHotkey = PowerToysSettings::HotkeyObject::from_settings(true, false, false, false, VK_NEXT);

View File

@@ -7,7 +7,7 @@ namespace NonLocalizable
const wchar_t WindowClassName[] = L"FancyZones_Window";
}
Window::Window(HINSTANCE hinstance, WndProc proc, DWORD style, DWORD extendedStyle, FancyZonesUtils::Rect position, LPCWSTR windowName, HWND parent, HMENU menu) noexcept :
Window::Window(HINSTANCE hinstance, WndProc proc, DWORD style, DWORD extendedStyle, FancyZonesUtils::Rect position, LPCWSTR windowName, HWND parent, HMENU menu, int showCommand) noexcept :
m_window(NULL),
m_proc(proc)
{
@@ -17,6 +17,7 @@ Window::Window(HINSTANCE hinstance, WndProc proc, DWORD style, DWORD extendedSty
WNDCLASSEXW wcex{};
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.lpfnWndProc = s_WndProc;
wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.hInstance = hinstance;
wcex.lpszClassName = reinterpret_cast<LPCWSTR>(NonLocalizable::WindowClassName);
wcex.hCursor = LoadCursorW(nullptr, IDC_ARROW);
@@ -37,13 +38,16 @@ Window::Window(HINSTANCE hinstance, WndProc proc, DWORD style, DWORD extendedSty
hinstance,
this);
ShowWindow(m_window, SW_SHOWNOACTIVATE);
if (showCommand != SW_HIDE)
{
ShowWindow(m_window, showCommand);
}
}
LRESULT CALLBACK Window::s_WndProc(HWND window, UINT message, WPARAM wparam, LPARAM lparam) noexcept
{
auto thisRef = reinterpret_cast<Window*>(GetWindowLongPtrW(window, GWLP_USERDATA));
if (!thisRef && (message == WM_CREATE))
if (!thisRef && (message == WM_NCCREATE))
{
const auto createStruct = reinterpret_cast<LPCREATESTRUCT>(lparam);
thisRef = reinterpret_cast<Window*>(createStruct->lpCreateParams);
@@ -55,8 +59,9 @@ LRESULT CALLBACK Window::s_WndProc(HWND window, UINT message, WPARAM wparam, LPA
SetWindowLongPtrW(window, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(thisRef));
}
return thisRef ? thisRef->m_proc(window, message, wparam, lparam) :
DefWindowProcW(window, message, wparam, lparam);
return (thisRef && thisRef->m_proc) ?
thisRef->m_proc(window, message, wparam, lparam) :
DefWindowProcW(window, message, wparam, lparam);
}
Window::~Window()

View File

@@ -7,7 +7,7 @@ using WndProc = std::function<LRESULT(HWND window, UINT message, WPARAM wparam,
class Window
{
public:
Window(HINSTANCE hinstance, WndProc proc, DWORD style, DWORD extendedStyle, FancyZonesUtils::Rect position, LPCWSTR windowName = NULL, HWND parent = NULL, HMENU menu = NULL) noexcept;
Window(HINSTANCE hinstance, WndProc proc, DWORD style, DWORD extendedStyle, FancyZonesUtils::Rect position, LPCWSTR windowName = NULL, HWND parent = NULL, HMENU menu = NULL, int show = SW_SHOWNOACTIVATE) noexcept;
~Window();
Window(const Window&) = delete;

View File

@@ -1,11 +1,57 @@
#include "pch.h"
#include "ZoneTitleBar.h"
#include "Drawing.h"
#include "CompositionDrawing.h"
#include <set>
#include <windowsx.h>
/* void Cls_OnDwmNcRenderingChanged(HWND hwnd, BOOL fEnabled) */
#define HANDLE_WM_DWMNCRENDERINGCHANGED(hwnd, wParam, lParam, fn) \
((fn)((hwnd), (BOOL)(wParam)), 0L)
using namespace FancyZonesUtils;
static HWND GetWindowAboveAllOthers(const std::vector<HWND>& windows)
{
if (windows.empty())
{
return NULL;
}
std::set<HWND> windowsSet(windows.begin(), windows.end());
// Get the window above all others
HWND max = NULL;
for (HWND current = windows.front(); !windows.empty() && current != NULL; current = GetWindow(current, GW_HWNDPREV))
{
auto i = windowsSet.find(current);
if (i != windowsSet.end())
{
max = current;
windowsSet.erase(i);
}
}
return max;
}
static void DrawWindowIcon(Drawing& drawing, const D2D1_RECT_F& rect, HWND window)
{
auto icon = (HICON)SendMessageW(window, WM_GETICON, ICON_BIG, 0);
if (icon == nullptr)
{
icon = (HICON)GetClassLongPtrW(window, GCLP_HICON);
}
if (icon != nullptr)
{
auto bitmap = drawing.CreateIcon(icon);
drawing.DrawBitmap(rect, bitmap.get());
}
}
class NoZoneTitleBar : public IZoneTitleBar
{
public:
@@ -18,10 +64,10 @@ public:
int GetHeight() const override { return 0; }
};
class ZoneTitleBar : public IZoneTitleBar
class SlimZoneTitleBar : public IZoneTitleBar
{
public:
ZoneTitleBar(HINSTANCE hinstance, Rect zone) noexcept :
SlimZoneTitleBar(HINSTANCE hinstance, Rect zone) noexcept :
m_window(
hinstance,
[this](HWND window, UINT message, WPARAM wParam, LPARAM lParam) { return WndProc(window, message, wParam, lParam); },
@@ -30,8 +76,7 @@ public:
ResetRect(zone),
NULL,
NULL,
NULL),
m_drawing(m_window)
NULL)
{
}
@@ -67,6 +112,7 @@ protected:
{
switch (message)
{
HANDLE_MSG(window, WM_CREATE, Init);
HANDLE_MSG(window, WM_PAINT, Render);
HANDLE_MSG(window, WM_LBUTTONDOWN, Click);
default:
@@ -74,6 +120,13 @@ protected:
}
}
bool Init(HWND hwnd, LPCREATESTRUCT)
{
m_drawing.Init(hwnd);
return true;
}
void Click(HWND hwnd, BOOL doubleClick, int x, int y, UINT keyFlags)
{
auto len = m_height;
@@ -92,26 +145,7 @@ protected:
HWND GetZoneCurrentWindow()
{
if (m_zoneWindows.empty())
{
return NULL;
}
std::set<HWND> zoneWindows(m_zoneWindows.begin(), m_zoneWindows.end());
// Get the window above all others
HWND max = NULL;
for (HWND current = m_zoneWindows.front(); !zoneWindows.empty() && current != NULL; current = GetWindow(current, GW_HWNDPREV))
{
auto i = zoneWindows.find(current);
if (i != zoneWindows.end())
{
max = current;
zoneWindows.erase(i);
}
}
return max;
return GetWindowAboveAllOthers(m_zoneWindows);
}
Rect ResetRect(Rect zone)
@@ -124,15 +158,15 @@ protected:
protected:
int m_height;
std::vector<HWND> m_zoneWindows;
Window m_window;
Drawing m_drawing;
Window m_window;
};
class NumbersZoneTitleBar : public ZoneTitleBar
class NumbersZoneTitleBar : public SlimZoneTitleBar
{
public:
NumbersZoneTitleBar(HINSTANCE hinstance, Rect zone) noexcept :
ZoneTitleBar(hinstance, zone)
SlimZoneTitleBar(hinstance, zone)
{
}
@@ -182,29 +216,14 @@ public:
}
};
class IconsZoneTitleBar : public ZoneTitleBar
class IconsZoneTitleBar : public SlimZoneTitleBar
{
public:
IconsZoneTitleBar(HINSTANCE hinstance, Rect zone) noexcept :
ZoneTitleBar(hinstance, zone)
SlimZoneTitleBar(hinstance, zone)
{
}
void DrawIcon(HWND window, const D2D1_RECT_F& rect)
{
auto icon = (HICON)SendMessageW(window, WM_GETICON, ICON_SMALL2, 0);
if (icon == nullptr)
{
icon = (HICON)GetClassLongPtrW(window, GCLP_HICONSM);
}
if (icon != nullptr)
{
auto bitmap = m_drawing.CreateIcon(icon);
m_drawing.DrawBitmap(rect, bitmap.get());
}
}
void Render(HWND hwnd) override
{
PAINTSTRUCT paint;
@@ -230,7 +249,7 @@ public:
{
constexpr float p = .15f;
auto iconRect = D2D1::Rect(len * (i + p), len * p, len * (i + 1 - p), len * (1 - p));
DrawIcon(m_zoneWindows[i], iconRect);
DrawWindowIcon(m_drawing, iconRect, m_zoneWindows[i]);
constexpr float s = p * .7f;
auto strokeRect = D2D1::Rect(len * (i + .5f * s), len * .5f * s, len * (i + 1 - .5f * s), len * (1 - .5f * s));
@@ -245,6 +264,289 @@ public:
}
};
class HiddenWindow : public Window
{
public:
HiddenWindow(HINSTANCE hinstance) :
Window(hinstance, nullptr, 0, WS_EX_TOOLWINDOW, Rect(0, 0, 0, 0), 0, 0, 0, SW_HIDE)
{
}
void HideWindowFromTaskbar(HWND window)
{
HWND val = *this;
SetWindowLongPtr(window, GWLP_HWNDPARENT, (LONG_PTR)val);
}
};
class TabsZoneTitleBar : public IZoneTitleBar
{
private:
static constexpr int c_style = WS_OVERLAPPED | WS_CAPTION | WS_THICKFRAME;
static constexpr int c_exStyle = WS_EX_NOREDIRECTIONBITMAP;
static constexpr int c_widthFactor = 4;
public:
TabsZoneTitleBar(HINSTANCE hinstance, Rect zone) noexcept :
m_hiddenWindow(hinstance),
m_zoneCurrentWindow(NULL),
m_window(
hinstance,
[this](HWND window, UINT message, WPARAM wParam, LPARAM lParam) { return WndProc(window, message, wParam, lParam); },
c_style,
c_exStyle,
ResetRect(zone),
NULL,
NULL,
NULL)
{
}
void UpdateZoneWindows(std::vector<HWND> zoneWindows) override
{
m_zoneWindows = zoneWindows;
ReadjustPos();
}
void ReadjustPos() override
{
auto zoneCurrentWindow = GetZoneCurrentWindow();
// Put the zone title bar just below the zone current window
if (zoneCurrentWindow != NULL)
{
m_zoneCurrentWindow = zoneCurrentWindow;
// Put the zone title bar just below the zoneCurrentWindow
SetWindowPos(m_window, zoneCurrentWindow, 0, 0, 0, 0, SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOOWNERZORDER | SWP_NOSIZE);
}
Render(m_window);
}
int GetHeight() const override { return m_height; }
protected:
LRESULT WndProc(HWND window, UINT message, WPARAM wParam, LPARAM lParam) noexcept
{
LRESULT result;
auto handled = DwmDefWindowProc(window, message, wParam, lParam, &result);
if (handled)
{
return result;
}
switch (message)
{
HANDLE_MSG(window, WM_CREATE, Init);
HANDLE_MSG(window, WM_NCCALCSIZE, CalcNonClientSize);
HANDLE_MSG(window, WM_WINDOWPOSCHANGING, WindowPosChanging);
HANDLE_MSG(window, WM_DWMNCRENDERINGCHANGED, DwmNonClientRenderingChanged);
HANDLE_MSG(window, WM_PAINT, Render);
HANDLE_MSG(window, WM_LBUTTONDOWN, Click);
default:
return DefWindowProcW(window, message, wParam, lParam);
}
}
bool Init(HWND hwnd, LPCREATESTRUCT)
{
// Hide from taskbar
m_hiddenWindow.HideWindowFromTaskbar(hwnd);
// Disable transitions
BOOL disable = TRUE;
DwmSetWindowAttribute(hwnd, DWMWA_TRANSITIONS_FORCEDISABLED, &disable, sizeof(disable));
// Extend frame (twice the size needed)
MARGINS margins = { 0, 0, 2 * m_height, 0 };
DwmExtendFrameIntoClientArea(hwnd, &margins);
// Update frame
SetWindowPos(hwnd, nullptr, 0, 0, 0, 0, SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE | SWP_NOZORDER);
// Initialize drawing
m_drawing.Init(hwnd);
return true;
}
UINT CalcNonClientSize(HWND hwnd, BOOL calc, NCCALCSIZE_PARAMS* info)
{
if (!calc)
{
return FORWARD_WM_NCCALCSIZE(hwnd, calc, info, DefWindowProcW);
}
auto xBorder = GetSystemMetrics(SM_CXFRAME) + GetSystemMetrics(SM_CXPADDEDBORDER);
auto yBorder = GetSystemMetrics(SM_CYFRAME) + GetSystemMetrics(SM_CYEDGE);
auto& coordinates = info->rgrc[0];
coordinates.left += xBorder;
coordinates.right -= xBorder;
coordinates.bottom -= yBorder;
return 0;
}
void DwmNonClientRenderingChanged(HWND hwnd, BOOL enabled)
{
Rect newWindowRect = m_zone;
RECT windowRect{};
::GetWindowRect(hwnd, &windowRect);
// Take care of borders
RECT frameRect{};
if (SUCCEEDED(DwmGetWindowAttribute(hwnd, DWMWA_EXTENDED_FRAME_BOUNDS, &frameRect, sizeof(frameRect))))
{
LONG leftMargin = frameRect.left - windowRect.left;
LONG rightMargin = frameRect.right - windowRect.right;
LONG bottomMargin = frameRect.bottom - windowRect.bottom;
newWindowRect.get()->left -= leftMargin;
newWindowRect.get()->right -= rightMargin;
newWindowRect.get()->bottom -= bottomMargin;
SetWindowPos(hwnd, nullptr, newWindowRect.left(), newWindowRect.top(), newWindowRect.width(), newWindowRect.height(), SWP_NOACTIVATE | SWP_NOZORDER);
}
}
BOOL WindowPosChanging(HWND hwnd, LPWINDOWPOS pos)
{
if (m_zoneCurrentWindow != NULL)
{
// If changing the Z order and the change does not put it in the correct place
if (pos->hwndInsertAfter != m_zoneCurrentWindow)
{
// Abort the change
pos->flags |= SWP_NOZORDER;
}
}
return true;
}
void Render(HWND hwnd)
{
PAINTSTRUCT paint;
BeginPaint(m_window, &paint);
auto backColor = Drawing::ConvertColor(GetSysColor(COLOR_WINDOW));
auto frameColor = Drawing::ConvertColor(GetSysColor(COLOR_3DFACE));
auto textColor = Drawing::ConvertColor(GetSysColor(COLOR_BTNTEXT));
auto highlightFrameColor = Drawing::ConvertColor(GetSysColor(COLOR_HIGHLIGHT));
auto highlightTextColor = Drawing::ConvertColor(GetSysColor(COLOR_HIGHLIGHTTEXT));
auto captionHeight = GetSystemMetrics(SM_CYCAPTION);
auto zoneCurrentWindow = GetZoneCurrentWindow();
NONCLIENTMETRICS metrics{};
metrics.cbSize = sizeof(metrics);
SystemParametersInfo(SPI_GETNONCLIENTMETRICS, sizeof(NONCLIENTMETRICS), &metrics, 0);
TCHAR text[100]{};
if (m_drawing)
{
m_drawing.BeginDraw();
auto textFormat = m_drawing.CreateTextFormat(
metrics.lfCaptionFont.lfFaceName,
float(-metrics.lfCaptionFont.lfHeight),
(DWRITE_FONT_WEIGHT)metrics.lfCaptionFont.lfWeight);
{
for (auto i = 0; i < m_zoneWindows.size(); ++i)
{
auto xOffset = m_height * (1 + c_widthFactor * i);
auto yOffset = 0;
auto backMargin = (m_height - captionHeight) * .4f;
auto backHeight = m_height - 2 * backMargin;
auto backWidth = c_widthFactor * m_height - 2 * backMargin;
auto backRect = D2D1::RectF(
float(xOffset + backMargin),
float(yOffset + backMargin),
float(xOffset + backMargin + backWidth),
float(yOffset + backMargin + backHeight));
m_drawing.FillRoundedRectangle(backRect, m_zoneWindows[i] == zoneCurrentWindow ? highlightFrameColor : frameColor);
auto iconMargin = (m_height - captionHeight) * .5f;
auto iconRect = D2D1::Rect(
xOffset + iconMargin,
yOffset + iconMargin,
xOffset + iconMargin + captionHeight,
yOffset + iconMargin + captionHeight);
DrawWindowIcon(m_drawing, iconRect, m_zoneWindows[i]);
if (textFormat)
{
auto textMargin = (m_height - captionHeight) * .5f;
auto textRect = D2D1::Rect(
float(xOffset + m_height),
float(yOffset + textMargin),
float(xOffset + c_widthFactor * m_height - textMargin),
float(yOffset + iconMargin + captionHeight));
text[0] = TEXT('\0');
GetWindowText(m_zoneWindows[i], text, ARRAYSIZE(text));
m_drawing.DrawTextTrim(text, textFormat.get(), textRect, m_zoneWindows[i] == zoneCurrentWindow ? highlightTextColor : textColor);
}
}
}
m_drawing.EndDraw();
}
EndPaint(m_window, &paint);
}
void Click(HWND hwnd, BOOL doubleClick, int x, int y, UINT keyFlags)
{
auto len = m_height;
if (len == 0 || x < len)
{
return;
}
auto i = (x - len) / (len * c_widthFactor);
if (i < m_zoneWindows.size())
{
SwitchToWindow(m_zoneWindows[i]);
}
}
HWND GetZoneCurrentWindow()
{
return GetWindowAboveAllOthers(m_zoneWindows);
}
Rect ResetRect(Rect zone)
{
m_zone = zone;
RECT rect{};
AdjustWindowRectEx(&rect, c_style, FALSE, c_exStyle);
auto height = -rect.top;
m_height = height > zone.height() ? 0 : height;
return zone;
}
protected:
HiddenWindow m_hiddenWindow;
HWND m_zoneCurrentWindow;
Rect m_zone;
int m_height;
std::vector<HWND> m_zoneWindows;
CompositionDrawing m_drawing;
Window m_window;
};
std::unique_ptr<IZoneTitleBar> MakeZoneTitleBar(ZoneTitleBarStyle style, HINSTANCE hinstance, Rect zone)
{
switch (style)
@@ -255,6 +557,9 @@ std::unique_ptr<IZoneTitleBar> MakeZoneTitleBar(ZoneTitleBarStyle style, HINSTAN
case ZoneTitleBarStyle::Icons:
return std::make_unique<IconsZoneTitleBar>(hinstance, zone);
case ZoneTitleBarStyle::Tabs:
return std::make_unique<TabsZoneTitleBar>(hinstance, zone);
case ZoneTitleBarStyle::None:
default:
return std::make_unique<NoZoneTitleBar>();

View File

@@ -1,6 +1,5 @@
#pragma once
#include "Window.h"
#include "Drawing.h"
#include "Settings.h"

View File

@@ -17,6 +17,6 @@ namespace Microsoft.PowerToys.Settings.UI.Library
public static readonly bool DefaultUseCursorposEditorStartupscreen = true;
public static readonly bool DefaultFancyzonesQuickLayoutSwitch = true;
public static readonly bool DefaultFancyzonesFlashZonesOnQuickSwitch = true;
public static readonly int DefaultFancyzonesZoneTitleBarStyle = 2;
public static readonly int DefaultFancyzonesZoneTitleBarStyle = 3;
}
}

View File

@@ -44,6 +44,7 @@ namespace Microsoft.PowerToys.Settings.UI.Library.ViewModels
None = 0,
Numbers = 1,
Icons = 2,
Tabs = 3,
}
public FancyZonesViewModel(SettingsUtils settingsUtils, ISettingsRepository<GeneralSettings> settingsRepository, ISettingsRepository<FancyZonesSettings> moduleSettingsRepository, Func<string, int> ipcMSGCallBackFunc, string configFileSubfolder = "")

View File

@@ -1186,6 +1186,9 @@ Made with 💗 by Microsoft and the PowerToys community.</value>
<data name="FancyZones_ZoneTitleBarStyle_Icons.Content" xml:space="preserve">
<value>Icons</value>
</data>
<data name="FancyZones_ZoneTitleBarStyle_Tabs.Content" xml:space="preserve">
<value>Tabs</value>
</data>
<data name="PowerLauncher_Plugins.Header" xml:space="preserve">
<value>Plugins</value>
</data>

View File

@@ -100,6 +100,7 @@
<ComboBoxItem x:Uid="FancyZones_ZoneTitleBarStyle_None" />
<ComboBoxItem x:Uid="FancyZones_ZoneTitleBarStyle_Numbers" />
<ComboBoxItem x:Uid="FancyZones_ZoneTitleBarStyle_Icons" />
<ComboBoxItem x:Uid="FancyZones_ZoneTitleBarStyle_Tabs" />
</ComboBox>
</controls:Setting.ActionContent>
</controls:Setting>