309 lines
9.8 KiB
C++
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();
|
|
}
|