diff --git a/src/modules/fancyzones/FancyZonesLib/CompositionDrawing.cpp b/src/modules/fancyzones/FancyZonesLib/CompositionDrawing.cpp new file mode 100644 index 0000000000..78929fb828 --- /dev/null +++ b/src/modules/fancyzones/FancyZonesLib/CompositionDrawing.cpp @@ -0,0 +1,164 @@ +#include "pch.h" +#include "CompositionDrawing.h" +#include + + +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 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(); + } +} \ No newline at end of file diff --git a/src/modules/fancyzones/FancyZonesLib/CompositionDrawing.h b/src/modules/fancyzones/FancyZonesLib/CompositionDrawing.h new file mode 100644 index 0000000000..bf603a2fd1 --- /dev/null +++ b/src/modules/fancyzones/FancyZonesLib/CompositionDrawing.h @@ -0,0 +1,34 @@ +#pragma once +#include "Drawing.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +class CompositionDrawing : public Drawing +{ +public: + void Init(HWND window); + void BeginDraw(); + void EndDraw(); + +private: + winrt::com_ptr m_d3dDevice; + winrt::com_ptr m_dxgiDevice; + winrt::com_ptr m_dxgiFactory; + winrt::com_ptr m_dxgiSwapChain; + winrt::com_ptr m_compositionDevice; + winrt::com_ptr m_compositionTarget; + winrt::com_ptr m_compositionVisual; + winrt::com_ptr m_dxgiSurface; + winrt::com_ptr m_d2dBitmap; + winrt::com_ptr m_d2dFactory; + winrt::com_ptr m_d2dDevice; +}; \ No newline at end of file diff --git a/src/modules/fancyzones/FancyZonesLib/Drawing.cpp b/src/modules/fancyzones/FancyZonesLib/Drawing.cpp index 9c8b234a1a..415faeea1c 100644 --- a/src/modules/fancyzones/FancyZonesLib/Drawing.cpp +++ b/src/modules/fancyzones/FancyZonesLib/Drawing.cpp @@ -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 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 Drawing::CreateTextFormat(LPCWSTR fontFamilyName, FLOAT fontSize) +winrt::com_ptr Drawing::CreateTextFormat(LPCWSTR fontFamilyName, FLOAT fontSize, DWRITE_FONT_WEIGHT fontWeight) { winrt::com_ptr textFormat = nullptr; @@ -93,7 +101,7 @@ winrt::com_ptr 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 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); diff --git a/src/modules/fancyzones/FancyZonesLib/Drawing.h b/src/modules/fancyzones/FancyZonesLib/Drawing.h index 182c04a439..689358bdbf 100644 --- a/src/modules/fancyzones/FancyZonesLib/Drawing.h +++ b/src/modules/fancyzones/FancyZonesLib/Drawing.h @@ -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 CreateTextFormat(LPCWSTR fontFamilyName, FLOAT fontSize); + winrt::com_ptr CreateTextFormat(LPCWSTR fontFamilyName, FLOAT fontSize, DWRITE_FONT_WEIGHT fontWeight = DWRITE_FONT_WEIGHT_NORMAL); winrt::com_ptr CreateBrush(D2D1_COLOR_F color); winrt::com_ptr 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 m_renderTarget = nullptr; + winrt::com_ptr m_renderTarget = nullptr; }; diff --git a/src/modules/fancyzones/FancyZonesLib/FancyZonesLib.vcxproj b/src/modules/fancyzones/FancyZonesLib/FancyZonesLib.vcxproj index 083db4a6c5..b8af1f445f 100644 --- a/src/modules/fancyzones/FancyZonesLib/FancyZonesLib.vcxproj +++ b/src/modules/fancyzones/FancyZonesLib/FancyZonesLib.vcxproj @@ -35,11 +35,15 @@ _LIB;%(PreprocessorDefinitions) ..\;..\..\..\common\inc;..\..\..\common\Telemetry;..\..\;..\..\..\;%(AdditionalIncludeDirectories) + + Dcomp.lib;%(AdditionalDependencies) + + @@ -83,6 +87,7 @@ ../pch.h ../pch.h + diff --git a/src/modules/fancyzones/FancyZonesLib/FancyZonesLib.vcxproj.filters b/src/modules/fancyzones/FancyZonesLib/FancyZonesLib.vcxproj.filters index 07e7de6c9c..29e47a08ad 100644 --- a/src/modules/fancyzones/FancyZonesLib/FancyZonesLib.vcxproj.filters +++ b/src/modules/fancyzones/FancyZonesLib/FancyZonesLib.vcxproj.filters @@ -123,6 +123,9 @@ Header Files + + Header Files + @@ -206,6 +209,9 @@ Source Files + + Source Files + diff --git a/src/modules/fancyzones/FancyZonesLib/Settings.h b/src/modules/fancyzones/FancyZonesLib/Settings.h index ef82a84185..498f4a30a1 100644 --- a/src/modules/fancyzones/FancyZonesLib/Settings.h +++ b/src/modules/fancyzones/FancyZonesLib/Settings.h @@ -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); diff --git a/src/modules/fancyzones/FancyZonesLib/Window.cpp b/src/modules/fancyzones/FancyZonesLib/Window.cpp index 5043c29e21..a89d6569cd 100644 --- a/src/modules/fancyzones/FancyZonesLib/Window.cpp +++ b/src/modules/fancyzones/FancyZonesLib/Window.cpp @@ -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(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(GetWindowLongPtrW(window, GWLP_USERDATA)); - if (!thisRef && (message == WM_CREATE)) + if (!thisRef && (message == WM_NCCREATE)) { const auto createStruct = reinterpret_cast(lparam); thisRef = reinterpret_cast(createStruct->lpCreateParams); @@ -55,8 +59,9 @@ LRESULT CALLBACK Window::s_WndProc(HWND window, UINT message, WPARAM wparam, LPA SetWindowLongPtrW(window, GWLP_USERDATA, reinterpret_cast(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() diff --git a/src/modules/fancyzones/FancyZonesLib/Window.h b/src/modules/fancyzones/FancyZonesLib/Window.h index 8e9642ac36..d621fced05 100644 --- a/src/modules/fancyzones/FancyZonesLib/Window.h +++ b/src/modules/fancyzones/FancyZonesLib/Window.h @@ -7,7 +7,7 @@ using WndProc = std::function #include +/* 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& windows) +{ + if (windows.empty()) + { + return NULL; + } + + std::set 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 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 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 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 m_zoneWindows; + CompositionDrawing m_drawing; + Window m_window; +}; + std::unique_ptr MakeZoneTitleBar(ZoneTitleBarStyle style, HINSTANCE hinstance, Rect zone) { switch (style) @@ -255,6 +557,9 @@ std::unique_ptr MakeZoneTitleBar(ZoneTitleBarStyle style, HINSTAN case ZoneTitleBarStyle::Icons: return std::make_unique(hinstance, zone); + case ZoneTitleBarStyle::Tabs: + return std::make_unique(hinstance, zone); + case ZoneTitleBarStyle::None: default: return std::make_unique(); diff --git a/src/modules/fancyzones/FancyZonesLib/ZoneTitleBar.h b/src/modules/fancyzones/FancyZonesLib/ZoneTitleBar.h index c930493dbb..abd786dbd6 100644 --- a/src/modules/fancyzones/FancyZonesLib/ZoneTitleBar.h +++ b/src/modules/fancyzones/FancyZonesLib/ZoneTitleBar.h @@ -1,6 +1,5 @@ #pragma once #include "Window.h" -#include "Drawing.h" #include "Settings.h" diff --git a/src/settings-ui/Settings.UI.Library/ConfigDefaults.cs b/src/settings-ui/Settings.UI.Library/ConfigDefaults.cs index 36de049872..e5d2f20c6c 100644 --- a/src/settings-ui/Settings.UI.Library/ConfigDefaults.cs +++ b/src/settings-ui/Settings.UI.Library/ConfigDefaults.cs @@ -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; } } diff --git a/src/settings-ui/Settings.UI.Library/ViewModels/FancyZonesViewModel.cs b/src/settings-ui/Settings.UI.Library/ViewModels/FancyZonesViewModel.cs index 14fe089773..296a30657a 100644 --- a/src/settings-ui/Settings.UI.Library/ViewModels/FancyZonesViewModel.cs +++ b/src/settings-ui/Settings.UI.Library/ViewModels/FancyZonesViewModel.cs @@ -44,6 +44,7 @@ namespace Microsoft.PowerToys.Settings.UI.Library.ViewModels None = 0, Numbers = 1, Icons = 2, + Tabs = 3, } public FancyZonesViewModel(SettingsUtils settingsUtils, ISettingsRepository settingsRepository, ISettingsRepository moduleSettingsRepository, Func ipcMSGCallBackFunc, string configFileSubfolder = "") 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 d02c82e5e2..07cc30e3bb 100644 --- a/src/settings-ui/Settings.UI/Strings/en-us/Resources.resw +++ b/src/settings-ui/Settings.UI/Strings/en-us/Resources.resw @@ -1186,6 +1186,9 @@ Made with 💗 by Microsoft and the PowerToys community. Icons + + Tabs + Plugins diff --git a/src/settings-ui/Settings.UI/Views/FancyZonesPage.xaml b/src/settings-ui/Settings.UI/Views/FancyZonesPage.xaml index 8048fa814f..34770a4765 100644 --- a/src/settings-ui/Settings.UI/Views/FancyZonesPage.xaml +++ b/src/settings-ui/Settings.UI/Views/FancyZonesPage.xaml @@ -100,6 +100,7 @@ +