c++winapidirect2d

Control a window's alpha blending and events transparency in Win API and Direct 2D (circular window example)


I'm trying to create a circular window using the Win32 API and Direct2D in C++. From what I understand, the only way to achieve this is by creating a WS_POPUP window and handling all the drawing manually. My goal is for the window to include a custom shadow around it. This shadow should be painted with transparency so that the desktop or other windows remain visible behind it. Additionally, it must be transparent to mouse events, allowing users to interact with other applications through the shadowed area.

I found that using the WS_EX_LAYERED style along with SetLayeredWindowAttributes() allows mouse events to be detected only in areas painted with Direct2D, effectively enabling a circular window. However, painting with transparency (alpha values) does not seem to work as expected—rather than blending with the background behind the window, it blends with a solid black background. That said, I was able to make the window transparent to mouse events by adding the WS_EX_TRANSPARENT flag.

Result using a layered window:

image

Another approach I considered was avoiding WS_EX_LAYERED and instead using DwmExtendFrameIntoClientArea(). This method allows proper transparency blending, but WS_EX_TRANSPARENT no longer works, meaning mouse events are not ignored in transparent areas. Additionally, I noticed some flickering when moving the window from outside the screen back into view, which makes this approach less ideal.

Result using DWM. I did not find a way to make the mouse events pass through:

image

Here is the flicker effect that I discovered and I don't like:

image

Here is my full code, the variable isDWM is used to switch from one approach to the other:

#include <dwmapi.h>
#include <windows.h>
#include <iostream>
#include <d2d1_1.h>

#pragma once
#pragma comment(lib, "d2d1.lib")
#pragma comment(lib, "dwrite.lib")
#pragma comment(lib, "Dwmapi.lib")

#define INIT_X_RATIO    0.7f
#define INIT_Y_RATIO    0.7f
#define BORDER_SIZE     2       // pixels
#define SHADOW_OFF      100     // pixels

ID2D1Factory* fact;
ID2D1HwndRenderTarget* renderTarget;
ID2D1SolidColorBrush* brush;
ID2D1RadialGradientBrush* pRadialGradientBrush;

D2D1::ColorF ColorRefToColorF(COLORREF color);
void paint_frame(HWND hwnd);
bool init_d2d1(HWND hwnd);
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
void CreateConsole();

int x, y, w, h;
bool isDWM = false; // Set to true to use DWM and see semi-transparent painting
LPCTSTR cursors[] = {IDC_SIZENESW, IDC_SIZENWSE};

D2D1::ColorF ColorRefToColorF(COLORREF color){
    float r = GetRValue(color) / 255.0f;
    float g = GetGValue(color) / 255.0f;
    float b = GetBValue(color) / 255.0f;
    return D2D1::ColorF(r, g, b);
}

void paint_frame(HWND hwnd) {
    UINT dpi = GetDpiForSystem();
    float ratio = (float)dpi / 96.0f;
    float shadow_off = SHADOW_OFF / ratio / 2.0f;
    D2D1_SIZE_F rtSize = renderTarget->GetSize();
    renderTarget->BeginDraw();
    renderTarget->Clear(D2D1::ColorF(0.0f, 0.0f, 0.0f, 0.0f));
    
    // Create shadow circle
    float cx = rtSize.width / 2.0f;
    float cy = rtSize.height / 2.0f;
    float rad = min(rtSize.width, rtSize.height) / 2.0f - shadow_off;
    D2D1_ELLIPSE circle = D2D1::Ellipse(
        D2D1::Point2F(cx, cy + shadow_off),
        rad,
        rad   
    );
    brush->SetColor(D2D1::ColorF(0.01f, 0.01f, 0.01f, 0.5f));
    renderTarget->FillEllipse(circle, brush);

    // Paint border circle
    circle.point = D2D1::Point2F(cx, cy - shadow_off);
    COLORREF borderColor = GetSysColor(COLOR_ACTIVEBORDER); // Get system border color
    D2D1::ColorF d2dColor = ColorRefToColorF(borderColor);
    brush->SetColor(d2dColor);
    renderTarget->FillEllipse(circle, brush);

    // Paint Content circle
    rad -= BORDER_SIZE / ratio;
    circle.radiusX = rad;
    circle.radiusY = rad;
    brush->SetColor(D2D1::ColorF(0.18f, 0.18f, 0.19f));
    renderTarget->FillEllipse(circle, brush);

    renderTarget->EndDraw();
}

bool init_d2d1(HWND hwnd) {
    // Create factory
    HRESULT hr = D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, &fact);
    if (hr != S_OK) return false;
    RECT r;
    GetClientRect(hwnd, &r);

    // Create render target
    hr = fact->CreateHwndRenderTarget(
        D2D1::RenderTargetProperties(D2D1_RENDER_TARGET_TYPE_DEFAULT, D2D1::PixelFormat(DXGI_FORMAT_B8G8R8A8_UNORM, D2D1_ALPHA_MODE_PREMULTIPLIED)),
        D2D1::HwndRenderTargetProperties(hwnd, D2D1::SizeU(r.right, r.bottom)),
        &renderTarget
    );

    if (hr != S_OK) return false;
    renderTarget->SetAntialiasMode(D2D1_ANTIALIAS_MODE_PER_PRIMITIVE);

    // Create brush
    hr = renderTarget->CreateSolidColorBrush(D2D1::ColorF(0.0f, 0.0f, 0.0f, 1.0f), &brush);
    if (hr != S_OK) return false;

    return true;
}

LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
    switch (uMsg) {
    case WM_DESTROY: {
        PostQuitMessage(0);
        return 0;
    }

    case WM_PAINT: {
        paint_frame(hwnd);
        ValidateRect(hwnd, NULL);
        break;
    }

    case WM_NCHITTEST: 
        return HTCAPTION;  // Enable dragging of the window

    case WM_NCMOUSEMOVE: {
        float xPos = (float) LOWORD(lParam);
        float yPos = (float) HIWORD(lParam);
        std::cout << "Mouse Position: X = " << xPos << ", Y = " << yPos << std::endl;
        break;
    }

    default:
        return DefWindowProc(hwnd, uMsg, wParam, lParam);
    }
}

void CreateConsole() {
    AllocConsole();                                     // Allocate a new console
    freopen_s((FILE**)stdout, "CONOUT$", "w", stdout);  // Redirect stdout to console
    freopen_s((FILE**)stdin, "CONIN$", "r", stdin);     // Redirect stdin to console
    freopen_s((FILE**)stderr, "CONOUT$", "w", stderr);  // Redirect stderr to console
}

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
    int screen_width = GetSystemMetrics(SM_CXSCREEN);
    int screen_height = GetSystemMetrics(SM_CYSCREEN);
    w = (int)((double)screen_width * INIT_X_RATIO);
    h = (int)((double)screen_height * INIT_Y_RATIO);

    // Assure even number
    w += w % 2 == 1;
    h += h % 2 == 1;

    // Square window
    w = min(w, h);
    h = w;
    x = (screen_width - w) / 2;
    y = (screen_height - h) / 2;

    WNDCLASS wc = {};
    wc.lpfnWndProc = WindowProc;
    wc.hInstance = hInstance;
    wc.lpszClassName = L"SampleWindowClass";
    wc.hCursor = LoadCursor(nullptr, IDC_ARROW);
    wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
    RegisterClass(&wc);

    HWND hwnd = CreateWindowEx(
        0,
        L"SampleWindowClass",
        L"Classic Windows App",
        WS_POPUP,
        x, y, w, h,
        nullptr, nullptr, hInstance, nullptr);

    if (!hwnd) return 0;
    if (!init_d2d1(hwnd)) return 0;
    CreateConsole();

    if (isDWM) {
        MARGINS margins = { -1 };
        DwmExtendFrameIntoClientArea(hwnd, &margins);
    }
    else {
        // Set Layered window (transparent)
        SetWindowLong(hwnd, GWL_EXSTYLE, GetWindowLong(hwnd, GWL_EXSTYLE) | WS_EX_LAYERED);
        SetLayeredWindowAttributes(hwnd, RGB(0, 0, 0), 0, LWA_COLORKEY);
    }

    ShowWindow(hwnd, nCmdShow);
    UpdateWindow(hwnd);

    MSG msg = {};
    while (GetMessage(&msg, nullptr, 0, 0)) {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    return static_cast<int>(msg.wParam);
}

Note that I want to use DirectX for the painting (No GDI/GDI+).

What would be the best way to achieve my goal?


Solution

  • I found a way to paint with alpha blending in DirectX while having transparency to events by using Direct Composition (thanks to this repo).

    1. Create a parent window with the WS_EX_TRANSPARENT style and paint the shadow only.

    2. Create a child window, set its region with CreateEllipticRgn() and SetWindowRgn() and paint the border and content. I made sure that the elliptic region is slightly bigger than the painted content so that the circle benefits from anti-aliasing. This child window handles all the events in its procedure.

    3. Since the child window handles the events, dragging the window does not move the parent automatically with it. So when the child receives a WM_MOVE message, it sets its parent's new position using SetWindowPos().

    I played around with a radial brush for the shadow effect. The event transparency seems to work:

    Click-through is working fine

    However, moving a parent window through its child is not ideal: there is a slight delay before the parent window repaints. To see this, I painted on the shadow window a red circle about the size of the child window. If the two windows were correctly "glued" together, this red circle should never be visible.

    Moving delay of the parent window.

    My full code:

    #pragma once
    
    #include <windows.h>
    #pragma comment(lib, "user32.lib")
    #include <wrl.h>
    #include <dxgi1_3.h>
    #include <d3d11_2.h>
    #include <d2d1_2.h>
    #include <d2d1_2helper.h>
    #include <dcomp.h>
    #pragma comment(lib, "dxgi")
    #pragma comment(lib, "d3d11")
    #pragma comment(lib, "d2d1")
    #pragma comment(lib, "dcomp")
    #include <iostream>
    // https://docs.microsoft.com/en-us/archive/msdn-magazine/2014/june/windows-with-c-high-performance-window-layering-using-the-windows-composition-engine
    
    using namespace Microsoft::WRL;
    #define INIT_X_RATIO        0.9f
    #define INIT_Y_RATIO        0.9f
    
    #define BORDER_SIZE         1.0f
    #define PADDING_SIZE        8.0f
    #define SHADOW_SIZE         100
    
    #define CONTENT_COLOR       0.18f, 0.18f, 0.19f, 1.0f
    #define BORDER_COLOR        1.0f, 1.0f, 1.0f, 1.0f
    #define GRADIENT_COLOR_1    1.0f, 1.0f, 1.0f, 0.7f
    #define GRADIENT_COLOR_2    0.0f, 0.0f, 1.0f, 0.0f
    #define BEHIND_COLOR        1.0f, 0.0f, 0.0f, 1.0f
    
    
    int x, y, w, h;
    HINSTANCE instance;
    LPCWSTR pClass = L"pClass";
    LPCWSTR pName = L"Circular window";
    LPCWSTR cClass = L"cClass";
    LPCWSTR cName = L"Circular content";
    
    struct ComException{
        HRESULT result;
        ComException(HRESULT const value) :
            result(value)
        {}
    };
    
    struct DirectRessources {
        ComPtr<ID3D11Device> direct3dDevice;
        ComPtr<IDXGIDevice> dxgiDevice;
        ComPtr<IDXGIFactory2> dxFactory;
        ComPtr<IDXGISwapChain1> swapChain;
        ComPtr<ID2D1Factory2> d2Factory;
        ComPtr<ID2D1Device1> d2Device;
        ComPtr<ID2D1DeviceContext> dc;
        ComPtr<IDXGISurface2> surface;
        ComPtr<ID2D1Bitmap1> bitmap;
        ComPtr<IDCompositionDevice> dcompDevice;
        ComPtr<IDCompositionTarget> target;
        ComPtr<IDCompositionVisual> visual;
        ComPtr<ID2D1SolidColorBrush> brush;
        ID2D1GradientStopCollection* pGradientStopCollection;
        ComPtr<ID2D1RadialGradientBrush> radBrush;
    };
    
    DirectRessources pR; // Parent ressources
    DirectRessources cR; // Child ressources
    
    void HR(HRESULT const result){
        if (S_OK != result)
        {
            throw ComException(result);
        }
    }
    
    D2D1::ColorF ColorRefToColorF(COLORREF color) {
        float r = GetRValue(color) / 255.0f;
        float g = GetGValue(color) / 255.0f;
        float b = GetBValue(color) / 255.0f;
        return D2D1::ColorF(r, g, b);
    }
    
    void InitDirect2D(HWND window, DirectRessources* res){
        HR(D3D11CreateDevice(nullptr,    // Adapter
            D3D_DRIVER_TYPE_HARDWARE,
            nullptr,    // Module
            D3D11_CREATE_DEVICE_BGRA_SUPPORT,
            nullptr, 0, // Highest available feature level
            D3D11_SDK_VERSION,
            &res->direct3dDevice,
            nullptr,    // Actual feature level
            nullptr));  // Device context
    
        HR(res->direct3dDevice.As(&res->dxgiDevice));
    
        HR(CreateDXGIFactory2(
            DXGI_CREATE_FACTORY_DEBUG,
            __uuidof(res->dxFactory),
            reinterpret_cast<void**>(res->dxFactory.GetAddressOf())));
    
        DXGI_SWAP_CHAIN_DESC1 description = {};
        description.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
        description.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
        description.SwapEffect = DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL;
        description.BufferCount = 2;
        description.SampleDesc.Count = 1;
        description.AlphaMode = DXGI_ALPHA_MODE_PREMULTIPLIED;
    
        RECT rect = {};
        GetClientRect(window, &rect);
        description.Width = rect.right - rect.left;
        description.Height = rect.bottom - rect.top;
    
        HR(res->dxFactory->CreateSwapChainForComposition(res->dxgiDevice.Get(),
            &description,
            nullptr, // Don’t restrict
            res->swapChain.GetAddressOf()));
    
        // Create a single-threaded Direct2D factory with debugging information
        D2D1_FACTORY_OPTIONS const options = { D2D1_DEBUG_LEVEL_INFORMATION };
        HR(D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED,
            options,
            res->d2Factory.GetAddressOf()));
    
        // Create the Direct2D device that links back to the Direct3D device
        HR(res->d2Factory->CreateDevice(res->dxgiDevice.Get(),
            res->d2Device.GetAddressOf()));
    
        // Create the Direct2D device context that is the actual render target and exposes drawing commands
        HR(res->d2Device->CreateDeviceContext(D2D1_DEVICE_CONTEXT_OPTIONS_NONE,
            res->dc.GetAddressOf()));
    
        // Retrieve the swap chain's back buffer
        HR(res->swapChain->GetBuffer(
            0, // index
            __uuidof(res->surface),
            reinterpret_cast<void**>(res->surface.GetAddressOf())));
    
        // Create a Direct2D bitmap that points to the swap chain surface
        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;
        HR(res->dc->CreateBitmapFromDxgiSurface(res->surface.Get(),
            properties,
            res->bitmap.GetAddressOf()));
        // Point the device context to the bitmap for rendering
        res->dc->SetTarget(res->bitmap.Get());
    
        HR(DCompositionCreateDevice(
            res->dxgiDevice.Get(),
            __uuidof(res->dcompDevice),
            reinterpret_cast<void**>(res->dcompDevice.GetAddressOf())));
    
    
        HR(res->dcompDevice->CreateTargetForHwnd(window,
            true, // Top most
            res->target.GetAddressOf()));
    
        HR(res->dcompDevice->CreateVisual(res->visual.GetAddressOf()));
        HR(res->visual->SetContent(res->swapChain.Get()));
        HR(res->target->SetRoot(res->visual.Get()));
        HR(res->dcompDevice->Commit());
    
        D2D1_COLOR_F const brushColor = D2D1::ColorF(0.18f,  // red
                                                     0.55f,  // green
                                                     0.34f,  // blue
                                                     0.75f); // alpha
        HR(res->dc->CreateSolidColorBrush(brushColor,
            res->brush.GetAddressOf()));
    }
    
    void paint_content() {
        cR.dc->BeginDraw();
        cR.dc->Clear(D2D1::ColorF(0.0f, 0.0f, 1.0f, 0.0f));
    
        D2D1_SIZE_F rtSize = cR.dc->GetSize();
        UINT dpi = GetDpiForSystem();
        float ratio = (float)dpi / 96.0f;
        float rad = min(rtSize.width, rtSize.height) / 2.0f - PADDING_SIZE/ratio;
    
        cR.brush->SetColor(D2D1::ColorF(BORDER_COLOR)); // Border color
        D2D1_POINT_2F ellipseCenter = D2D1::Point2F(rtSize.width / 2.0f, rtSize.height / 2.0f);
        D2D1_ELLIPSE ellipse = D2D1::Ellipse(ellipseCenter, rad, rad);
        cR.dc->FillEllipse(ellipse, cR.brush.Get());
    
        cR.brush->SetColor(D2D1::ColorF(CONTENT_COLOR)); // Content background color
        rad -= BORDER_SIZE / ratio;
        ellipse.radiusX = rad;
        ellipse.radiusY = rad;
        cR.dc->FillEllipse(ellipse, cR.brush.Get());
    
        HR(cR.dc->EndDraw());
    
        // Make the swap chain available to the composition engine
        HR(cR.swapChain->Present(1,   // sync
            0)); // flags
    }
    
    void CreateConsole() {
        AllocConsole();                                     // Allocate a new console
        freopen_s((FILE**)stdout, "CONOUT$", "w", stdout);  // Redirect stdout to console
        freopen_s((FILE**)stdin, "CONIN$", "r", stdin);     // Redirect stdin to console
        freopen_s((FILE**)stderr, "CONOUT$", "w", stderr);  // Redirect stderr to console
    }
    
    void paint_shadow() {
        pR.dc->BeginDraw();
        pR.dc->Clear(D2D1::ColorF(1.0f, 1.0f, 1.f, 0.0f));
    
        D2D1_SIZE_F rtSize = pR.dc->GetSize();
        UINT dpi = GetDpiForSystem();
        float ratio = (float)dpi / 96.0f;
        float rad = min(rtSize.width, rtSize.height) / 2.0f;
        D2D1_POINT_2F center = D2D1::Point2F(rtSize.width / 2.0f, rtSize.height / 2.0f);
        float start = 1.0f - ((SHADOW_SIZE + PADDING_SIZE) / (rad*ratio));  
    
        D2D1_GRADIENT_STOP gradientStops[] = {
        { start, D2D1::ColorF(GRADIENT_COLOR_1) },      // Start color
        { 1.0f, D2D1::ColorF(GRADIENT_COLOR_2) }        // End color
        };
        pR.dc->CreateGradientStopCollection(
            gradientStops,
            2,  // Number of stops aka size of gradientStops
            D2D1_GAMMA_2_2,
            D2D1_EXTEND_MODE_CLAMP,
            &pR.pGradientStopCollection
        );
    
        D2D1_RADIAL_GRADIENT_BRUSH_PROPERTIES radialProps =
            D2D1::RadialGradientBrushProperties(
                center,  // Gradient center
                D2D1::Point2F(0, 0),  // Gradient offset
                rad,                    // radX
                rad                     // radY
            );
    
        pR.dc->CreateRadialGradientBrush(
            radialProps,
            pR.pGradientStopCollection,
            &pR.radBrush
        );
        
        D2D1_POINT_2F ellipseCenter = D2D1::Point2F(rtSize.width /2.0f, rtSize.height / 2.0f);
        D2D1_ELLIPSE ellipse = D2D1::Ellipse(ellipseCenter, rad, rad); // y radius
        pR.dc->FillEllipse(ellipse, pR.radBrush.Get());
    
        rad -= (SHADOW_SIZE + BORDER_SIZE + PADDING_SIZE) / ratio;
        ellipse.radiusX = rad;
        ellipse.radiusY = rad;
        pR.brush->SetColor(D2D1::ColorF(BEHIND_COLOR));
        pR.dc->FillEllipse(ellipse, pR.brush.Get());
    
        HR(pR.dc->EndDraw());
    
        // Make the swap chain available to the composition engine
        HR(pR.swapChain->Present(1,   // sync
                              0)); // flags
    }
    LRESULT CALLBACK ChildProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
        switch (uMsg)
        {
    
        case WM_NCHITTEST: {
            return HTCAPTION;
        }
    
        case WM_PAINT: {
            paint_content();
            break;
        }
    
        case WM_DESTROY: {
            PostQuitMessage(0);
            return 0;
        }
        case WM_SIZE: {
            return DefWindowProc(hwnd, uMsg, wParam, lParam);
        }
    
        case WM_MOVE: {
            int xPos = (int)((short)LOWORD(lParam)); 
            int yPos = (int)((short)HIWORD(lParam)); 
            HWND parent = GetParent(hwnd);
            x = xPos - SHADOW_SIZE;
            y = yPos - SHADOW_SIZE;
    
            RECT r;
            GetWindowRect(hwnd, &r);
            std::cout << "Rect upper left corner (" << r.left << ", " << r.top << ")" << std::endl;
            std::cout << "Rect lower right corner (" << r.right << ", " << r.bottom << ")" << std::endl;
            std::cout << "Dimensions (" << r.right - r.left << ", " << r.bottom - r.top << ")" << std::endl;
            RECT pr;
            GetWindowRect(parent, &pr);
            std::cout << "Rect upper left corner (" << pr.left << ", " << pr.top << ")" << std::endl;
            std::cout << "Rect lower right corner (" << pr.right << ", " << pr.bottom << ")" << std::endl;
            std::cout << "Dimensions (" << pr.right - pr.left << ", " << pr.bottom - pr.top << ")" << std::endl;
    
            std::cout << "Offset (" << r.left - pr.left << ", " << r.top - pr.top << ")\n" << std::endl;
            SetWindowPos(parent, 0, x, y, w, h, TRUE);
            return 1;
        }
        }
        return DefWindowProc(hwnd, uMsg, wParam, lParam);
    }
    LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
        switch (uMsg)
        {
        case WM_CREATE: {
            int cx = x + SHADOW_SIZE;
            int cy = y + SHADOW_SIZE;
            int cw = w - 2*SHADOW_SIZE;
            int ch = h - 2*SHADOW_SIZE;
    
            HWND child = CreateWindowEx(0,
                cClass, cName,
                WS_VISIBLE | WS_CHILD | WS_POPUP , // | WS_MAXIMIZEBOX | WS_THICKFRAME | WS_MINIMIZEBOX | WS_CAPTION,
                cx, cy,
                cw, ch,
                hwnd, nullptr, nullptr, nullptr);;
    
            RECT r;
            GetWindowRect(child, &r);
            HRGN hRgn = CreateEllipticRgn(0, 0, r.right - r.left, r.bottom - r.top);
            SetWindowRgn(child, hRgn, TRUE);
    
            InitDirect2D(child, &cR);
        }
    
        case WM_NCHITTEST:{
            return 0;
        }
    
        case WM_PAINT:{
            paint_shadow();
            break;
        }
        case WM_DESTROY:{
            PostQuitMessage(0);
            return 0;
        }
        
        }
        return DefWindowProc(hwnd, uMsg, wParam, lParam);
    }
    
    int __stdcall wWinMain(HINSTANCE module, HINSTANCE, PWSTR, int){    
        CreateConsole();
        instance = module;
        int screen_width = GetSystemMetrics(SM_CXSCREEN);
        int screen_height = GetSystemMetrics(SM_CYSCREEN);
        w = (int)((double)screen_width * INIT_X_RATIO);
        h = (int)((double)screen_height * INIT_Y_RATIO);
    
        // Assure even number
        w += w % 2 == 1;
        h += h % 2 == 1;
    
        // Square window
        w = min(w, h);
        h = w;
        x = (screen_width - w) / 2;
        y = (screen_height - h) / 2;
    
        WNDCLASS wc = {};
        wc.hCursor = LoadCursor(nullptr, IDC_ARROW);
        wc.hInstance = instance;
        wc.lpszClassName = pClass;
        wc.style = CS_HREDRAW | CS_VREDRAW;
        wc.lpfnWndProc = WindowProc;
        RegisterClass(&wc);
    
        wc = {};
        wc.hCursor = LoadCursor(nullptr, IDC_ARROW);
        wc.hInstance = instance;
        wc.lpszClassName = cClass;
        wc.style = CS_HREDRAW | CS_VREDRAW;
        wc.lpfnWndProc = ChildProc;
        RegisterClass(&wc);
    
        DWORD overlay_style = WS_EX_TRANSPARENT | WS_EX_LAYERED;
        HWND window = CreateWindowEx(overlay_style,
            pClass, pName,
            WS_POPUP | WS_VISIBLE,
            x, y,
            w, h,
            nullptr, nullptr, instance, nullptr);
    
        InitDirect2D(window, &pR);
        paint_shadow();
    
        MSG message;
        while (BOOL result = GetMessage(&message, 0, 0, 0)){
            if (-1 != result) DispatchMessage(&message);
        }
        return 0;
    }