Hello,
I'm trying to create a GUI Framework where I'm using D2D with Direct Composition to Draw the window instead of relying on GDI and I managed to do that but I also want to add custom shadows to the window instead of the bad shadows of windows but I couldn't find anyway to do that I managed to increase the HWND size but it is not that good because many issues started occurring like bad Maximizing and another issues so could any one help me
void VX::VX_WindowsWindow::DrawWindow() {
VX_D;
d->directX11->ClearScreen({ 0.0f, 0.0f, 0.0f, 0.0f });
float m = isMaximized == VX_TRUE ? 0.0f : 30.0f;
if (isMaximized == VX_TRUE) {
// Fill entire surface, no shadow, no rounded corners
d->directX11->FillRoundedRectangle(
{ 0.0f, 0.0f, float(width), float(height) },
{ 0, 0 },
{ 0.12f, 0.12f, 0.12f, 1.0f }
);
} else {
d->directX11->FillRoundedRectangleWithShadow(
{ m, m, float(width) - m * 2.0f, float(height) - m * 2.0f },
{ 8, 8 },
{ 0.12f, 0.12f, 0.12f, 1.0f },
10.0f, 0.0f, 4.0f, 0.7f
);
}
d->closeButtonWidth = 22;
d->closeButtonHeight = 22;
d->closeButtonX = float(width) - m - d->closeButtonWidth - 12;
d->closeButtonY = m + 12;
d->maximizeButtonWidth = 22;
d->maximizeButtonHeight = 22;
d->maximizeButtonX = d->closeButtonX - d->closeButtonWidth - 8;
d->maximizeButtonY = m + 12;
d->minimizeButtonWidth = 22;
d->minimizeButtonHeight = 22;
d->minimizeButtonX = d->maximizeButtonX - d->maximizeButtonWidth - 8;
d->minimizeButtonY = m + 12;
// Then buttons with shadow — now shadow is visible against grey
d->directX11->FillRoundedRectangleWithShadow(
{ d->closeButtonX, d->closeButtonY, d->closeButtonWidth, d->closeButtonHeight },
{ 4, 4 },
d->closeButtonColor,
3.5f, 0.0f, 0.0f, 0.7f
);
d->directX11->FillRoundedRectangleWithShadow(
{ d->maximizeButtonX, d->maximizeButtonY, d->maximizeButtonWidth, d->maximizeButtonHeight },
{ 4, 4 },
d->maximizeButtonColor,
3.5f, 0.0f, 0.0f, 0.7f
);
d->directX11->FillRoundedRectangleWithShadow(
{ d->minimizeButtonX, d->minimizeButtonY, d->minimizeButtonWidth, d->minimizeButtonHeight },
{ 4, 4 },
d->minimizeButtonColor,
3.5f, 0.0f, 0.0f, 0.7f
);
VX_TextFormat format = {};
format.FontFamily = "Arial";
format.FontSize = 16.0f;
format.Bold = true;
format.Italic = false;
format.paragraphAlignment = VX_ParagraphAlignment::Center;
format.textAlignment = VX_TextAlignment::Center;
d->directX11->RenderText(title, { m + 12, m + 12, 200, 32 }, format, VX_Colors::VX_White);
}
void VX::VX_WindowsWindow::SetSize(VX_UNSIGNEDINT width, VX_UNSIGNEDINT height) {
VX_D;
const unsigned int SHADOW_MARGIN = 30;
this->width = width;
this->height = height;
if (d->hwnd) {
VX_UNSIGNEDINT hwndWidth = width + SHADOW_MARGIN * 2;
VX_UNSIGNEDINT hwndHeight = height + SHADOW_MARGIN * 2;
VX_UNSIGNEDINT hwndX = x - SHADOW_MARGIN;
VX_UNSIGNEDINT hwndY = y - SHADOW_MARGIN;
SetWindowPos(d->hwnd, nullptr,
hwndX, hwndY,
hwndWidth, hwndHeight,
SWP_NOZORDER
);
}
}
VX_BOOL VX::VX_WindowsWindow::Initialize()
{
VX_D;
if (!d->isClassRegistered)
{
WNDCLASSEXW wc = {};
wc.cbSize = sizeof(WNDCLASSEXW);
wc.style = 0;
wc.lpfnWndProc = WindowProc;
wc.hInstance = d->hInstance;
wc.hCursor = LoadCursor(nullptr, IDC_ARROW);
wc.hbrBackground = nullptr;
wc.lpszClassName = d->className;
if (!RegisterClassExW(&wc))
return VX_FALSE;
d->isClassRegistered = VX_TRUE;
}
wchar_t* wTitle = ToWideChar(title);
const unsigned int SHADOW_MARGIN = 30;
VX_UNSIGNEDINT hwndWidth = width + SHADOW_MARGIN * 2;
VX_UNSIGNEDINT hwndHeight = height + SHADOW_MARGIN * 2;
VX_UNSIGNEDINT hwndX = x - SHADOW_MARGIN;
VX_UNSIGNEDINT hwndY = y - SHADOW_MARGIN;
d->hwnd = CreateWindowExW(
WS_EX_NOREDIRECTIONBITMAP,
d->className,
wTitle,
WS_POPUP,
hwndX, hwndY,
hwndWidth, hwndHeight,
nullptr,
nullptr,
d->hInstance,
d
);
delete[] wTitle;
if (!d->hwnd)
return VX_FALSE;
VX_BOOL result = VX_FALSE;
MARGINS margins = { 0, 0, 0, 0 };
DwmExtendFrameIntoClientArea(d->hwnd, &margins);
result = d->directX11->Initialize(this);
isInitialized = result;
return result;
}
case WM_NCCALCSIZE:
if (wParam == TRUE)
{
result = 0;
wasHandled = VX_TRUE;
}
break;
case WM_ERASEBKGND:
result = 1;
wasHandled = VX_TRUE;
break;
case WM_NCHITTEST:
{
POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };
ScreenToClient(hwnd, &pt);
RECT rc;
GetClientRect(hwnd, &rc);
const int m = pThis->q_ptr->isMaximized == VX_TRUE ? 0.0f : 30.0f;
const int border = 8;
const int titleBarHeight = 32;
if (pt.x < m || pt.y < m
|| pt.x > rc.right - m || pt.y > rc.bottom - m)
{
result = HTTRANSPARENT;
wasHandled = VX_TRUE;
break;
}
bool left = pt.x < m + border;
bool right = pt.x >= rc.right - m - border;
bool top = pt.y < m + border;
bool bottom = pt.y >= rc.bottom - m - border;
if (left && top) { result = HTTOPLEFT; wasHandled = VX_TRUE; break; }
if (right && top) { result = HTTOPRIGHT; wasHandled = VX_TRUE; break; }
if (left && bottom) { result = HTBOTTOMLEFT; wasHandled = VX_TRUE; break; }
if (right && bottom) { result = HTBOTTOMRIGHT; wasHandled = VX_TRUE; break; }
if (top) { result = HTTOP; wasHandled = VX_TRUE; break; }
if (left) { result = HTLEFT; wasHandled = VX_TRUE; break; }
if (right) { result = HTRIGHT; wasHandled = VX_TRUE; break; }
if (bottom) { result = HTBOTTOM; wasHandled = VX_TRUE; break; }
if (pt.x >= pThis->closeButtonX
&& pt.x <= pThis->closeButtonX + pThis->closeButtonWidth
&& pt.y >= pThis->closeButtonY
&& pt.y <= pThis->closeButtonY + pThis->closeButtonHeight)
{
result = HTCLIENT;
wasHandled = VX_TRUE;
break;
}
// Minimize button
if (pt.x >= pThis->minimizeButtonX
&& pt.x <= pThis->minimizeButtonX + pThis->minimizeButtonWidth
&& pt.y >= pThis->minimizeButtonY
&& pt.y <= pThis->minimizeButtonY + pThis->minimizeButtonHeight)
{
result = HTCLIENT;
wasHandled = VX_TRUE;
break;
}
// Maximize button
if (pt.x >= pThis->maximizeButtonX
&& pt.x <= pThis->maximizeButtonX + pThis->maximizeButtonWidth
&& pt.y >= pThis->maximizeButtonY
&& pt.y <= pThis->maximizeButtonY + pThis->maximizeButtonHeight)
{
result = HTCLIENT;
wasHandled = VX_TRUE;
break;
}
if (pt.y <= m + titleBarHeight)
{
result = HTCAPTION;
wasHandled = VX_TRUE;
break;
}
result = HTCLIENT;
wasHandled = VX_TRUE;
break;
}
case WM_GETMINMAXINFO:
{
MINMAXINFO* mmi = (MINMAXINFO*)lParam;
HMONITOR monitor = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST);
MONITORINFO mi = { sizeof(mi) };
GetMonitorInfo(monitor, &mi);
int m = 30;
mmi->ptMaxPosition.x = mi.rcWork.left - m;
mmi->ptMaxPosition.y = mi.rcWork.top - m;
mmi->ptMaxSize.x = (mi.rcWork.right - mi.rcWork.left) + m * 2;
mmi->ptMaxSize.y = (mi.rcWork.bottom - mi.rcWork.top) + m * 2;
wasHandled = VX_TRUE;
result = 0;
break;
}
case WM_SIZING:
{
RECT* rc = reinterpret_cast<RECT*>(lParam);
unsigned int newWidth = rc->right - rc->left;
unsigned int newHeight = rc->bottom - rc->top;
if (pThis && pThis->q_ptr->isInitialized && newWidth > 0 && newHeight > 0)
{
pThis->q_ptr->width = newWidth;
pThis->q_ptr->height = newHeight;
pThis->directX11->Resize(newWidth, newHeight,
[pThis]() { pThis->q_ptr->Render(); });
}
result = TRUE;
wasHandled = VX_TRUE;
break;
}
case WM_SIZE:
{
pThis->q_ptr->isMaximized = (wParam == SIZE_MAXIMIZED);
unsigned int newWidth = LOWORD(lParam);
unsigned int newHeight = HIWORD(lParam);
if (pThis && newWidth > 0 && newHeight > 0)
{
pThis->q_ptr->width = newWidth;
pThis->q_ptr->height = newHeight;
if (pThis->q_ptr->isInitialized)
{
pThis->directX11->Resize(newWidth, newHeight,
[pThis]() { pThis->q_ptr->Render(); });
pThis->q_ptr->window->SizeChanged(
pThis->q_ptr->window, { newWidth, newHeight });
pThis->isDirty = VX_TRUE;
}
}
result = 0;
wasHandled = VX_TRUE;
break;
}
case WM_MOUSEMOVE:
{
POINT p;
GetCursorPos(&p);
ScreenToClient(hwnd, &p);
bool onCloseButton = p.x >= pThis->closeButtonX
&& p.x <= pThis->closeButtonX + pThis->closeButtonWidth
&& p.y >= pThis->closeButtonY
&& p.y <= pThis->closeButtonY + pThis->closeButtonHeight;
bool onMinimizeButton = p.x >= pThis->minimizeButtonX
&& p.x <= pThis->minimizeButtonX + pThis->minimizeButtonWidth
&& p.y >= pThis->minimizeButtonY
&& p.y <= pThis->minimizeButtonY + pThis->minimizeButtonHeight;
bool onMaximizeButton = p.x >= pThis->maximizeButtonX
&& p.x <= pThis->maximizeButtonX + pThis->maximizeButtonWidth
&& p.y >= pThis->maximizeButtonY
&& p.y <= pThis->maximizeButtonY + pThis->maximizeButtonHeight;
if (onCloseButton)
{
pThis->q_ptr->SetTitleBarButtons(VX_TitleBarButton::Close);
pThis->minimizeButtonColor = VX_Colors::VX_Green;
pThis->maximizeButtonColor = VX_Colors::VX_Yellow;
}
else if (onMinimizeButton)
{
pThis->q_ptr->SetTitleBarButtons(VX_TitleBarButton::Minimize);
pThis->closeButtonColor = VX_Colors::VX_Red;
pThis->maximizeButtonColor = VX_Colors::VX_Yellow;
}
else if (onMaximizeButton)
{
pThis->q_ptr->SetTitleBarButtons(VX_TitleBarButton::Maximize);
pThis->closeButtonColor = VX_Colors::VX_Red;
pThis->minimizeButtonColor = VX_Colors::VX_Green;
}
else
pThis->q_ptr->SetTitleBarButtons(VX_TitleBarButton::None);
result = 0;
wasHandled = VX_TRUE;
break;
}
case WM_LBUTTONDOWN:
{
POINT p;
GetCursorPos(&p);
ScreenToClient(hwnd, &p);
bool onCloseButton = p.x >= pThis->closeButtonX
&& p.x <= pThis->closeButtonX + pThis->closeButtonWidth
&& p.y >= pThis->closeButtonY
&& p.y <= pThis->closeButtonY + pThis->closeButtonHeight;
bool onMinimizeButton = p.x >= pThis->minimizeButtonX
&& p.x <= pThis->minimizeButtonX + pThis->minimizeButtonWidth
&& p.y >= pThis->minimizeButtonY
&& p.y <= pThis->minimizeButtonY + pThis->minimizeButtonHeight;
bool onMaximizeButton = p.x >= pThis->maximizeButtonX
&& p.x <= pThis->maximizeButtonX + pThis->maximizeButtonWidth
&& p.y >= pThis->maximizeButtonY
&& p.y <= pThis->maximizeButtonY + pThis->maximizeButtonHeight;
if (onCloseButton)
{
pThis->q_ptr->Close();
}
else if (onMinimizeButton)
{
pThis->q_ptr->Minimize();
}
else if (onMaximizeButton)
{
if (!pThis->q_ptr->isMaximized)
pThis->q_ptr->Maximize();
else
pThis->q_ptr->Restore();
}
result = 0;
wasHandled = VX_TRUE;
break;
}
#include "VX_DirectX11.h"
#include "VX_DirectX11_P.h"
#include "VX_WindowsWindow.h"
#include "VX_WindowsApplication.h"
#include "VX_ToWideChar.h"
VX::VX_DirectX11::VX_DirectX11()
: VX_Object(*new VX_DirectX11Private)
{
VX_D;
d->d3dDevice = nullptr;
d->d3dDeviceContext = nullptr;
d->dxgiSwapChain = nullptr;
d->d2dFactory = nullptr;
d->d2dDevice = nullptr;
d->d2dDeviceContext = nullptr;
d->d2dFactory = nullptr;
d->d2dRenderTarget = nullptr;
d->dcompDevice = nullptr;
d->dcompTarget = nullptr;
d->dcompVisual = nullptr;
d->backBuffer = nullptr;
d->bgBrush = nullptr;
d->currentHeight = 0;
d->currentWidth = 0;
d->hasDrawingAtLeastOnce = VX_FALSE;
d->isDrawing = VX_FALSE;
d->isInitialized = VX_FALSE;
d->isInitialized = VX_FALSE;
d->isResizing = VX_FALSE;
d->titleBarHovered = VX_FALSE;
d->windowFocused = VX_FALSE;
d->hoveredButton = VX_TitleBarButton::None;
}
VX::VX_DirectX11::~VX_DirectX11()
{
}
VX_BOOL VX::VX_DirectX11::Initialize(VX_WindowsWindow* window)
{
VX_D;
if (FAILED(CreateD3DDeviceAndDeviceContext())) { return VX_FALSE; }
if (FAILED(CreateDXGISwapChain(window->width, window->height))) { return VX_FALSE; }
if (FAILED(CreateD2DFactory())) { return VX_FALSE; }
if (FAILED(CreateD2DDeviceAndDeviceContext())) { return VX_FALSE; }
if (FAILED(CreateD2DBitmap())) { return VX_FALSE; }
if (FAILED(CreateDWriteFactory())) { return VX_FALSE; }
d->d2dDeviceContext->CreateSolidColorBrush(D2D1::ColorF(0.0f, 0.0f, 0.0f, 0.0f), d->bgBrush.GetAddressOf());
if (FAILED(CreateDirectComposition(window->GetHandle()))) { return VX_FALSE; }
d->isInitialized = VX_TRUE;
return VX_TRUE;
}
void VX::VX_DirectX11::ClearScreen(VX_Color color)
{
VX_D;
auto& ctx = d->d2dDeviceContext;
ctx->Clear(D2D1::ColorF(color.r, color.g, color.b, color.a));
}
void VX::VX_DirectX11::FillRoundedRectangle(VX_Coordinates c, VX_Radius radius, VX_Color color)
{
VX_D;
if (d->isInitialized == VX_FALSE) return;
d->bgBrush->SetColor(D2D1::ColorF{ color.r, color.g, color.b, color.a });
D2D1_ROUNDED_RECT rect = {
D2D1::RectF(
c.x, c.y, c.x + c.w, c.y + c.h
),
radius.x,
radius.y
};
d->d2dDeviceContext->FillRoundedRectangle(rect, d->bgBrush.Get());
}
void VX::VX_DirectX11::FillRoundedRectangleWithShadow(
VX_Coordinates c, VX_Radius radius, VX_Color color,
float shadowBlur, float shadowOffsetX, float shadowOffsetY, float shadowOpacity) {
VX_D;
if (!d->isInitialized) return;
auto& ctx = d->d2dDeviceContext;
// Extra space around the shape so blur doesn't get clipped
float margin = shadowBlur * 3.0f;
D2D1_SIZE_F size = D2D1::SizeF(c.w + margin * 2.0f, c.h + margin * 2.0f);
// 1. Create an offscreen render target — has its own BeginDraw/EndDraw
ComPtr<ID2D1BitmapRenderTarget> compatRT;
ctx->CreateCompatibleRenderTarget(
&size, nullptr, nullptr,
D2D1_COMPATIBLE_RENDER_TARGET_OPTIONS_NONE,
compatRT.GetAddressOf()
);
// 2. Draw shape into it (shifted by margin so blur has room)
ComPtr<ID2D1SolidColorBrush> tempBrush;
compatRT->CreateSolidColorBrush(
D2D1::ColorF(color.r, color.g, color.b, color.a),
tempBrush.GetAddressOf()
);
D2D1_ROUNDED_RECT localRect = {
D2D1::RectF(margin, margin, margin + c.w, margin + c.h),
radius.x, radius.y
};
compatRT->BeginDraw();
compatRT->Clear(D2D1::ColorF(0.0f, 0.0f, 0.0f, 0.0f));
compatRT->FillRoundedRectangle(localRect, tempBrush.Get());
compatRT->EndDraw();
// 3. Get bitmap from the offscreen target
ComPtr<ID2D1Bitmap> shapeBitmap;
compatRT->GetBitmap(shapeBitmap.GetAddressOf());
// 4. Apply shadow effect to the bitmap
ComPtr<ID2D1Effect> shadowEffect;
ctx->CreateEffect(CLSID_D2D1Shadow, shadowEffect.GetAddressOf());
shadowEffect->SetInput(0, shapeBitmap.Get());
shadowEffect->SetValue(D2D1_SHADOW_PROP_BLUR_STANDARD_DEVIATION, shadowBlur);
shadowEffect->SetValue(D2D1_SHADOW_PROP_COLOR,
D2D1::Vector4F(0.0f, 0.0f, 0.0f, shadowOpacity));
// 5. Draw shadow behind (offset accounts for the margin we added)
D2D1_POINT_2F shadowPos = D2D1::Point2F(
c.x - margin + shadowOffsetX,
c.y - margin + shadowOffsetY
);
ctx->DrawImage(
shadowEffect.Get(),
&shadowPos,
nullptr,
D2D1_INTERPOLATION_MODE_LINEAR,
D2D1_COMPOSITE_MODE_SOURCE_OVER
);
// 6. Draw original shape on top
D2D1_ROUNDED_RECT worldRect = {
D2D1::RectF(c.x, c.y, c.x + c.w, c.y + c.h),
radius.x, radius.y
};
d->bgBrush->SetColor(D2D1::ColorF(color.r, color.g, color.b, color.a));
ctx->FillRoundedRectangle(worldRect, d->bgBrush.Get());
}
void VX::VX_DirectX11::FillRoundedRectangleWithShadowSpecifiyShadowWidthAndHeight(VX_Coordinates c, VX_Radius radius, VX_Color color, VX_Coordinates shadowCoordinates, float shadowBlur, float shadowOffsetX, float shadowOffsetY, float shadowOpacity) {
VX_D;
if (!d->isInitialized) return;
auto& ctx = d->d2dDeviceContext;
// Extra space around the shape so blur doesn't get clipped
float margin = shadowBlur * 3.0f;
D2D1_SIZE_F size = D2D1::SizeF(shadowCoordinates.w + margin * 2.0f, shadowCoordinates.h + margin * 2.0f);
// 1. Create an offscreen render target — has its own BeginDraw/EndDraw
ComPtr<ID2D1BitmapRenderTarget> compatRT;
ctx->CreateCompatibleRenderTarget(
&size, nullptr, nullptr,
D2D1_COMPATIBLE_RENDER_TARGET_OPTIONS_NONE,
compatRT.GetAddressOf()
);
// 2. Draw shape into it (shifted by margin so blur has room)
ComPtr<ID2D1SolidColorBrush> tempBrush;
compatRT->CreateSolidColorBrush(
D2D1::ColorF(color.r, color.g, color.b, color.a),
tempBrush.GetAddressOf()
);
D2D1_ROUNDED_RECT localRect = {
D2D1::RectF(margin, margin, margin + shadowCoordinates.w, margin + shadowCoordinates.h),
radius.x, radius.y
};
compatRT->BeginDraw();
compatRT->Clear(D2D1::ColorF(0.0f, 0.0f, 0.0f, 0.0f));
compatRT->FillRoundedRectangle(localRect, tempBrush.Get());
compatRT->EndDraw();
// 3. Get bitmap from the offscreen target
ComPtr<ID2D1Bitmap> shapeBitmap;
compatRT->GetBitmap(shapeBitmap.GetAddressOf());
// 4. Apply shadow effect to the bitmap
ComPtr<ID2D1Effect> shadowEffect;
ctx->CreateEffect(CLSID_D2D1Shadow, shadowEffect.GetAddressOf());
shadowEffect->SetInput(0, shapeBitmap.Get());
shadowEffect->SetValue(D2D1_SHADOW_PROP_BLUR_STANDARD_DEVIATION, shadowBlur);
shadowEffect->SetValue(D2D1_SHADOW_PROP_COLOR,
D2D1::Vector4F(0.0f, 0.0f, 0.0f, shadowOpacity));
// 5. Draw shadow behind (offset accounts for the margin we added)
D2D1_POINT_2F shadowPos = D2D1::Point2F(
shadowCoordinates.x - margin + shadowOffsetX,
shadowCoordinates.y - margin + shadowOffsetY
);
ctx->DrawImage(
shadowEffect.Get(),
&shadowPos,
nullptr,
D2D1_INTERPOLATION_MODE_LINEAR,
D2D1_COMPOSITE_MODE_SOURCE_OVER
);
// 6. Draw original shape on top
D2D1_ROUNDED_RECT worldRect = {
D2D1::RectF(c.x, c.y, c.x + c.w, c.y + c.h),
radius.x, radius.y
};
d->bgBrush->SetColor(D2D1::ColorF(color.r, color.g, color.b, color.a));
ctx->FillRoundedRectangle(worldRect, d->bgBrush.Get());
}
void VX::VX_DirectX11::RenderText(const char* text, VX_Coordinates c, VX_TextFormat textFormat, VX_Color color)
{
VX_D;
const wchar_t* wcharText = ToWideChar(text);
const wchar_t* wcharTextFormatFontFamily = ToWideChar(textFormat.FontFamily);
ComPtr<IDWriteTextFormat> format = nullptr;
d->dwriteFactory->CreateTextFormat(
wcharTextFormatFontFamily,
NULL,
textFormat.Bold ? DWRITE_FONT_WEIGHT_BOLD : DWRITE_FONT_WEIGHT_NORMAL,
textFormat.Italic ? DWRITE_FONT_STYLE_ITALIC : DWRITE_FONT_STYLE_NORMAL,
DWRITE_FONT_STRETCH_NORMAL,
textFormat.FontSize,
L"en-us",
&format
);
delete wcharTextFormatFontFamily;
d->textBrush->SetColor(
D2D1::ColorF(color.r, color.g, color.b, color.a)
);
D2D1_RECT_F layoutRect = D2D1::RectF(c.x, c.y, c.x + c.w, c.y + c.h);
d->d2dDeviceContext->DrawText(
wcharText,
wcslen(wcharText),
format.Get(),
layoutRect,
d->textBrush.Get()
);
delete wcharText;
format.Reset();
}
}