Files
Gitree/src/managers/window_manager.cpp

309 lines
9.8 KiB
C++

#include "window_manager.h"
#include <GLFW/glfw3.h>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <filesystem>
#include <vector>
#ifdef _WIN32
#define GLFW_EXPOSE_NATIVE_WIN32
#include <GLFW/glfw3native.h>
#include <dwmapi.h>
#include <windows.h>
#include <wincodec.h>
#endif
#ifdef _WIN32
namespace
{
HICON loadPngIcon(const std::filesystem::path &path, UINT size, bool rounded)
{
const HRESULT com_result = CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);
const bool uninitialize_com = SUCCEEDED(com_result);
IWICImagingFactory *factory = nullptr;
IWICBitmapDecoder *decoder = nullptr;
IWICBitmapFrameDecode *frame = nullptr;
IWICBitmapScaler *scaler = nullptr;
IWICFormatConverter *converter = nullptr;
HICON icon = nullptr;
if (FAILED(CoCreateInstance(CLSID_WICImagingFactory, nullptr, CLSCTX_INPROC_SERVER,
IID_PPV_ARGS(&factory))) ||
FAILED(factory->CreateDecoderFromFilename(path.c_str(), nullptr, GENERIC_READ,
WICDecodeMetadataCacheOnLoad, &decoder)) ||
FAILED(decoder->GetFrame(0, &frame)) ||
FAILED(factory->CreateBitmapScaler(&scaler)) ||
FAILED(scaler->Initialize(frame, size, size, WICBitmapInterpolationModeFant)) ||
FAILED(factory->CreateFormatConverter(&converter)) ||
FAILED(converter->Initialize(scaler, GUID_WICPixelFormat32bppBGRA,
WICBitmapDitherTypeNone, nullptr, 0.0,
WICBitmapPaletteTypeCustom)))
{
goto cleanup;
}
{
std::vector<BYTE> pixels(static_cast<size_t>(size) * size * 4);
if (FAILED(converter->CopyPixels(nullptr, size * 4,
static_cast<UINT>(pixels.size()), pixels.data())))
{
goto cleanup;
}
if (rounded)
{
const float radius = static_cast<float>(size) * 0.22f;
const float maximum = static_cast<float>(size) - radius - 0.5f;
const float minimum = radius - 0.5f;
for (UINT y = 0; y < size; ++y)
{
for (UINT x = 0; x < size; ++x)
{
const float nearest_x = std::clamp(static_cast<float>(x), minimum, maximum);
const float nearest_y = std::clamp(static_cast<float>(y), minimum, maximum);
const float dx = static_cast<float>(x) - nearest_x;
const float dy = static_cast<float>(y) - nearest_y;
const float coverage = std::clamp(radius + 0.5f - std::sqrt(dx * dx + dy * dy),
0.0f, 1.0f);
pixels[(static_cast<size_t>(y) * size + x) * 4 + 3] =
static_cast<BYTE>(coverage * 255.0f);
}
}
}
BITMAPV5HEADER header{};
header.bV5Size = sizeof(header);
header.bV5Width = static_cast<LONG>(size);
header.bV5Height = -static_cast<LONG>(size);
header.bV5Planes = 1;
header.bV5BitCount = 32;
header.bV5Compression = BI_BITFIELDS;
header.bV5RedMask = 0x00ff0000;
header.bV5GreenMask = 0x0000ff00;
header.bV5BlueMask = 0x000000ff;
header.bV5AlphaMask = 0xff000000;
void *bitmap_bits = nullptr;
const HDC screen = GetDC(nullptr);
const HBITMAP color = CreateDIBSection(screen, reinterpret_cast<BITMAPINFO *>(&header),
DIB_RGB_COLORS, &bitmap_bits, nullptr, 0);
ReleaseDC(nullptr, screen);
const HBITMAP mask = CreateBitmap(size, size, 1, 1, nullptr);
if (color && mask && bitmap_bits)
{
std::memcpy(bitmap_bits, pixels.data(), pixels.size());
ICONINFO info{};
info.fIcon = TRUE;
info.hbmColor = color;
info.hbmMask = mask;
icon = CreateIconIndirect(&info);
}
if (color)
DeleteObject(color);
if (mask)
DeleteObject(mask);
}
cleanup:
if (converter)
converter->Release();
if (scaler)
scaler->Release();
if (frame)
frame->Release();
if (decoder)
decoder->Release();
if (factory)
factory->Release();
if (uninitialize_com)
CoUninitialize();
return icon;
}
} // namespace
#endif
WindowManager::~WindowManager()
{
destroy();
}
bool WindowManager::create(const char *title, int width, int height)
{
#ifdef _WIN32
using SetDpiAwarenessContext = BOOL(WINAPI *)(HANDLE);
const HMODULE user32 = GetModuleHandleW(L"user32.dll");
const FARPROC dpi_address = GetProcAddress(user32, "SetProcessDpiAwarenessContext");
SetDpiAwarenessContext set_dpi_awareness = nullptr;
static_assert(sizeof(set_dpi_awareness) == sizeof(dpi_address));
std::memcpy(&set_dpi_awareness, &dpi_address, sizeof(set_dpi_awareness));
if (set_dpi_awareness)
set_dpi_awareness(reinterpret_cast<HANDLE>(-4)); // Per-monitor v2
#endif
if (!glfwInit())
return false;
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
glfwWindowHint(GLFW_SCALE_TO_MONITOR, GLFW_TRUE);
window_ = glfwCreateWindow(width, height, title, nullptr, nullptr);
if (!window_)
{
glfwTerminate();
return false;
}
glfwMakeContextCurrent(window_);
glfwSwapInterval(1);
glfwSetWindowUserPointer(window_, this);
glfwSetWindowContentScaleCallback(window_, contentScaleCallback);
float x_scale = 1.0f;
float y_scale = 1.0f;
glfwGetWindowContentScale(window_, &x_scale, &y_scale);
dpi_scale_ = std::max(x_scale, y_scale);
applyNativeCaption();
applyNativeIcons();
return true;
}
void WindowManager::destroy()
{
if (!window_)
return;
glfwDestroyWindow(window_);
window_ = nullptr;
destroyNativeIcons();
glfwTerminate();
}
void WindowManager::pollEvents() { glfwPollEvents(); }
void WindowManager::swapBuffers() { glfwSwapBuffers(window_); }
void WindowManager::requestClose() { glfwSetWindowShouldClose(window_, GLFW_TRUE); }
bool WindowManager::shouldClose() const { return !window_ || glfwWindowShouldClose(window_); }
bool WindowManager::consumeDpiChange()
{
const bool changed = dpi_changed_;
dpi_changed_ = false;
return changed;
}
void WindowManager::contentScaleCallback(GLFWwindow *window, float x_scale, float y_scale)
{
auto *manager = static_cast<WindowManager *>(glfwGetWindowUserPointer(window));
if (manager)
manager->updateDpi(std::max(x_scale, y_scale));
}
void WindowManager::updateDpi(float scale)
{
scale = std::clamp(scale, 1.0f, 4.0f);
if (scale == dpi_scale_)
return;
dpi_scale_ = scale;
dpi_changed_ = true;
applyNativeCaption();
}
void WindowManager::applyNativeCaption() const
{
#ifdef _WIN32
if (!window_)
return;
const HWND hwnd = glfwGetWin32Window(window_);
if (!hwnd)
return;
const BOOL dark = TRUE;
// DWMWA_USE_IMMERSIVE_DARK_MODE
DwmSetWindowAttribute(hwnd, 20, &dark, sizeof(dark));
DWORD corner_pref = 0;
switch (corner_mode_)
{
case WindowCornerMode::Default:
corner_pref = 0; // DWMWCP_DEFAULT
break;
case WindowCornerMode::DoNotRound:
corner_pref = 1; // DWMWCP_DONOTROUND
break;
case WindowCornerMode::Round:
corner_pref = 2; // DWMWCP_ROUND
break;
case WindowCornerMode::RoundSmall:
corner_pref = 3; // DWMWCP_ROUNDSMALL
break;
}
// DWMWA_WINDOW_CORNER_PREFERENCE
DwmSetWindowAttribute(hwnd, 33, &corner_pref, sizeof(corner_pref));
// Windows 11 caption customization. Older versions safely ignore these.
const COLORREF caption = static_cast<COLORREF>(caption_color_);
const COLORREF border = RGB(51, 55, 63);
const COLORREF text = RGB(199, 203, 209);
// DWMWA_BORDER_COLOR
DwmSetWindowAttribute(hwnd, 34, &border, sizeof(border));
// DWMWA_CAPTION_COLOR
DwmSetWindowAttribute(hwnd, 35, &caption, sizeof(caption));
// DWMWA_TEXT_COLOR
DwmSetWindowAttribute(hwnd, 36, &text, sizeof(text));
#endif
}
void WindowManager::applyNativeIcons()
{
#ifdef _WIN32
destroyNativeIcons();
const HWND hwnd = glfwGetWin32Window(window_);
const auto asset_dir = std::filesystem::path(GITREE_IMAGE_ASSET_DIR);
window_icon_ = loadPngIcon(asset_dir / L"gitree_logo.png", 32, true);
taskbar_icon_ = loadPngIcon(asset_dir / L"gitree_logo.png", 64, true);
if (window_icon_)
{
SendMessageW(hwnd, WM_SETICON, ICON_SMALL, reinterpret_cast<LPARAM>(window_icon_));
SendMessageW(hwnd, WM_SETICON, ICON_SMALL2, reinterpret_cast<LPARAM>(window_icon_));
}
if (taskbar_icon_)
{
SendMessageW(hwnd, WM_SETICON, ICON_BIG, reinterpret_cast<LPARAM>(taskbar_icon_));
}
#endif
}
void WindowManager::destroyNativeIcons()
{
#ifdef _WIN32
if (window_icon_)
DestroyIcon(static_cast<HICON>(window_icon_));
if (taskbar_icon_)
DestroyIcon(static_cast<HICON>(taskbar_icon_));
#endif
window_icon_ = nullptr;
taskbar_icon_ = nullptr;
}
void WindowManager::setCornerMode(WindowCornerMode mode)
{
corner_mode_ = mode;
applyNativeCaption();
}
void WindowManager::setCaptionColor(std::uint32_t color)
{
caption_color_ = color;
applyNativeCaption();
}