lib_ui/ui/platform/win/ui_window_shadow_win.cpp
2022-06-18 11:01:00 +04:00

637 lines
17 KiB
C++

// This file is part of Desktop App Toolkit,
// a set of libraries for developing nice desktop applications.
//
// For license and copyright information please follow this link:
// https://github.com/desktop-app/legal/blob/master/LEGAL
//
#include "ui/platform/win/ui_window_shadow_win.h"
#include "ui/rp_widget.h"
#include "ui/platform/win/ui_window_win.h"
#include "base/platform/base_platform_info.h"
#include "styles/style_widgets.h"
#include <QtGui/QPainter>
#include <QtWidgets/QApplication>
#include <QtWidgets/QDesktopWidget>
#include <windowsx.h>
// WM_POINTER support from Windows 8 onwards (WINVER >= 0x0602)
#ifndef WM_POINTERUPDATE
# define WM_NCPOINTERUPDATE 0x0241
# define WM_NCPOINTERDOWN 0x0242
# define WM_NCPOINTERUP 0x0243
# define WM_POINTERUPDATE 0x0245
# define WM_POINTERDOWN 0x0246
# define WM_POINTERUP 0x0247
# define WM_POINTERENTER 0x0249
# define WM_POINTERLEAVE 0x024A
# define WM_POINTERACTIVATE 0x024B
# define WM_POINTERCAPTURECHANGED 0x024C
# define WM_POINTERWHEEL 0x024E
# define WM_POINTERHWHEEL 0x024F
#endif // WM_POINTERUPDATE
namespace Ui {
namespace Platform {
namespace {
base::flat_map<HWND, not_null<WindowShadow*>> ShadowByHandle;
} // namespace
WindowShadow::WindowShadow(not_null<RpWidget*> window, QColor color)
: _window(window)
, _handle(GetWindowHandle(window))
, _windows11(::Platform::IsWindows11OrGreater()) {
init(color);
}
WindowShadow::~WindowShadow() {
destroy();
}
void WindowShadow::setColor(QColor value) {
_r = value.red();
_g = value.green();
_b = value.blue();
if (!working()) {
return;
}
auto brush = getBrush(_alphas[0]);
for (auto i = 0; i != 4; ++i) {
auto graphics = Gdiplus::Graphics(_contexts[i]);
graphics.SetCompositingMode(Gdiplus::CompositingModeSourceCopy);
if ((i % 2) && _h || !(i % 2) && _w) {
const auto width = (i % 2) ? _size : _w;
const auto height = (i % 2) ? _h : _size;
graphics.FillRectangle(&brush, 0, 0, width, height);
}
}
initCorners();
_x = _y = _w = _h = 0;
update(Change::Moved | Change::Resized);
}
bool WindowShadow::working() const {
return (_handle != nullptr) && (_handles[0] != nullptr);
}
void WindowShadow::destroy() {
for (int i = 0; i < 4; ++i) {
if (_contexts[i]) {
DeleteDC(_contexts[i]);
_contexts[i] = nullptr;
}
if (_bitmaps[i]) {
DeleteObject(_bitmaps[i]);
_bitmaps[i] = nullptr;
}
if (_handles[i]) {
ShadowByHandle.remove(_handles[i]);
DestroyWindow(_handles[i]);
_handles[i] = nullptr;
}
}
if (_screenContext) {
ReleaseDC(nullptr, _screenContext);
_screenContext = nullptr;
}
}
void WindowShadow::init(QColor color) {
if (!_handle) {
return;
}
initBlend();
_fullsize = st::windowShadow.width();
_shift = st::windowShadowShift;
auto cornersImage = QImage(
QSize(_fullsize, _fullsize),
QImage::Format_ARGB32_Premultiplied);
cornersImage.fill(QColor(0, 0, 0));
{
QPainter p(&cornersImage);
p.setCompositionMode(QPainter::CompositionMode_Source);
st::windowShadow.paint(p, 0, 0, _fullsize, QColor(255, 255, 255));
}
if (style::RightToLeft()) {
cornersImage = cornersImage.mirrored(true, false);
}
const auto pixels = cornersImage.bits();
const auto pixel = [&](int x, int y) {
if (x < 0 || y < 0) {
return 0;
}
const auto data = pixels
+ (cornersImage.bytesPerLine() * y)
+ (sizeof(uint32) * x);
return int(data[0]);
};
_metaSize = _fullsize + 2 * _shift;
_alphas.reserve(_metaSize);
_colors.reserve(_metaSize * _metaSize);
for (auto j = 0; j != _metaSize; ++j) {
for (auto i = 0; i != _metaSize; ++i) {
const auto value = pixel(i - 2 * _shift, j - 2 * _shift);
_colors.push_back(uchar(std::max(value, 1)));
}
}
auto previous = uchar(0);
for (auto i = 0; i != _metaSize; ++i) {
const auto alpha = _colors[(_metaSize - 1) * _metaSize + i];
if (alpha < previous) {
break;
}
_alphas.push_back(alpha);
previous = alpha;
}
_size = _alphas.size() - 2 * _shift;
Gdiplus::GdiplusStartupInput gdiplusStartupInput;
ULONG_PTR gdiplusToken;
Gdiplus::Status gdiRes = Gdiplus::GdiplusStartup(
&gdiplusToken,
&gdiplusStartupInput,
NULL);
if (gdiRes != Gdiplus::Ok) {
return;
}
_screenContext = GetDC(nullptr);
if (!_screenContext) {
return;
}
const auto avail = QApplication::desktop()->availableGeometry();
_widthMax = std::max(avail.width(), 1);
_heightMax = std::max(avail.height(), 1);
static const auto instance = (HINSTANCE)GetModuleHandle(nullptr);
static const auto className = u"WindowShadow"_q;
static const auto wcharClassName = className.toStdWString();
static const auto registered = [] {
auto wc = WNDCLASSEX();
wc.cbSize = sizeof(wc);
wc.style = 0;
wc.lpfnWndProc = WindowCallback;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = instance;
wc.hIcon = 0;
wc.hCursor = 0;
wc.hbrBackground = 0;
wc.lpszMenuName = NULL;
wc.lpszClassName = wcharClassName.c_str();
wc.hIconSm = 0;
return RegisterClassEx(&wc) ? true : false;
}();
if (!registered) {
return;
}
for (auto i = 0; i != 4; ++i) {
_handles[i] = CreateWindowEx(
WS_EX_LAYERED | WS_EX_TOOLWINDOW,
wcharClassName.c_str(),
0,
WS_POPUP,
0,
0,
0,
0,
0,
0,
instance,
0);
if (!_handles[i]) {
destroy();
return;
}
ShadowByHandle.emplace(_handles[i], this);
SetWindowLongPtr(_handles[i], GWLP_HWNDPARENT, (LONG_PTR)_handle);
_contexts[i] = CreateCompatibleDC(_screenContext);
if (!_contexts[i]) {
destroy();
return;
}
const auto width = (i % 2) ? _size : _widthMax;
const auto height = (i % 2) ? _heightMax : _size;
_bitmaps[i] = CreateCompatibleBitmap(_screenContext, width, height);
if (!_bitmaps[i]) {
return;
}
SelectObject(_contexts[i], _bitmaps[i]);
}
setColor(color);
}
void WindowShadow::initCorners(Directions directions) {
const auto hor = (directions & Direction::Horizontal);
const auto ver = (directions & Direction::Vertical);
auto graphics0 = Gdiplus::Graphics(_contexts[0]);
auto graphics1 = Gdiplus::Graphics(_contexts[1]);
auto graphics2 = Gdiplus::Graphics(_contexts[2]);
auto graphics3 = Gdiplus::Graphics(_contexts[3]);
graphics0.SetCompositingMode(Gdiplus::CompositingModeSourceCopy);
graphics1.SetCompositingMode(Gdiplus::CompositingModeSourceCopy);
graphics2.SetCompositingMode(Gdiplus::CompositingModeSourceCopy);
graphics3.SetCompositingMode(Gdiplus::CompositingModeSourceCopy);
auto brush = getBrush(_alphas[0]);
if (hor) {
graphics0.FillRectangle(&brush, 0, 0, _fullsize - (_size - _shift), 2 * _shift);
}
if (ver) {
graphics1.FillRectangle(&brush, 0, 0, _size, 2 * _shift);
graphics3.FillRectangle(&brush, 0, 0, _size, 2 * _shift);
graphics1.FillRectangle(&brush, _size - _shift, 2 * _shift, _shift, _fullsize);
graphics3.FillRectangle(&brush, 0, 2 * _shift, _shift, _fullsize);
}
if (hor) {
for (int j = 2 * _shift; j < _size; ++j) {
for (int k = 0; k < _fullsize - (_size - _shift); ++k) {
brush.SetColor(getColor(_colors[j * _metaSize + k + (_size + _shift)]));
graphics0.FillRectangle(&brush, k, j, 1, 1);
graphics2.FillRectangle(&brush, k, _size - (j - 2 * _shift) - 1, 1, 1);
}
}
for (int j = _size; j < _size + 2 * _shift; ++j) {
for (int k = 0; k < _fullsize - (_size - _shift); ++k) {
brush.SetColor(getColor(_colors[j * _metaSize + k + (_size + _shift)]));
graphics2.FillRectangle(&brush, k, _size - (j - 2 * _shift) - 1, 1, 1);
}
}
}
if (ver) {
for (int j = 2 * _shift; j < _fullsize + 2 * _shift; ++j) {
for (int k = _shift; k < _size; ++k) {
brush.SetColor(getColor(_colors[j * _metaSize + (k + _shift)]));
graphics1.FillRectangle(&brush, _size - k - 1, j, 1, 1);
graphics3.FillRectangle(&brush, k, j, 1, 1);
}
}
}
}
void WindowShadow::verCorners(int h, Gdiplus::Graphics *pgraphics1, Gdiplus::Graphics *pgraphics3) {
auto brush = getBrush(_alphas[0]);
pgraphics1->FillRectangle(&brush, _size - _shift, h - _fullsize, _shift, _fullsize);
pgraphics3->FillRectangle(&brush, 0, h - _fullsize, _shift, _fullsize);
for (int j = 0; j < _fullsize; ++j) {
for (int k = _shift; k < _size; ++k) {
brush.SetColor(getColor(_colors[(j + 2 * _shift) * _metaSize + k + _shift]));
pgraphics1->FillRectangle(&brush, _size - k - 1, h - j - 1, 1, 1);
pgraphics3->FillRectangle(&brush, k, h - j - 1, 1, 1);
}
}
}
void WindowShadow::horCorners(int w, Gdiplus::Graphics *pgraphics0, Gdiplus::Graphics *pgraphics2) {
auto brush = getBrush(_alphas[0]);
pgraphics0->FillRectangle(&brush, w - 2 * _size - (_fullsize - (_size - _shift)), 0, _fullsize - (_size - _shift), 2 * _shift);
for (int j = 2 * _shift; j < _size; ++j) {
for (int k = 0; k < _fullsize - (_size - _shift); ++k) {
brush.SetColor(getColor(_colors[j * _metaSize + k + (_size + _shift)]));
pgraphics0->FillRectangle(&brush, w - 2 * _size - k - 1, j, 1, 1);
pgraphics2->FillRectangle(&brush, w - 2 * _size - k - 1, _size - (j - 2 * _shift) - 1, 1, 1);
}
}
for (int j = _size; j < _size + 2 * _shift; ++j) {
for (int k = 0; k < _fullsize - (_size - _shift); ++k) {
brush.SetColor(getColor(_colors[j * _metaSize + k + (_size + _shift)]));
pgraphics2->FillRectangle(&brush, w - 2 * _size - k - 1, _size - (j - 2 * _shift) - 1, 1, 1);
}
}
}
Gdiplus::Color WindowShadow::getColor(uchar alpha) const {
return Gdiplus::Color(BYTE(_windows11 ? 1 : alpha), _r, _g, _b);
}
Gdiplus::SolidBrush WindowShadow::getBrush(uchar alpha) const {
return Gdiplus::SolidBrush(getColor(alpha));
}
Gdiplus::Pen WindowShadow::getPen(uchar alpha) const {
return Gdiplus::Pen(getColor(alpha));
}
void WindowShadow::update(Changes changes, WINDOWPOS *pos) {
if (!working()) {
return;
}
if (changes == Changes(Change::Activate)) {
for (int i = 0; i < 4; ++i) {
SetWindowPos(_handles[i], _handle, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
}
return;
}
if (changes & Change::Hidden) {
if (!_hidden) {
for (int i = 0; i < 4; ++i) {
_hidden = true;
ShowWindow(_handles[i], SW_HIDE);
}
}
return;
}
if (_window->isHidden()) {
return;
}
int x = _x, y = _y, w = _w, h = _h;
if (pos && (!(pos->flags & SWP_NOMOVE) || !(pos->flags & SWP_NOSIZE) || !(pos->flags & SWP_NOREPOSITION))) {
if (!(pos->flags & SWP_NOMOVE)) {
x = pos->x - _size;
y = pos->y - _size;
} else if (pos->flags & SWP_NOSIZE) {
for (int i = 0; i < 4; ++i) {
SetWindowPos(_handles[i], _handle, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
}
return;
}
if (!(pos->flags & SWP_NOSIZE)) {
w = pos->cx + 2 * _size;
h = pos->cy + 2 * _size;
}
} else {
RECT r;
GetWindowRect(_handle, &r);
x = r.left - _size;
y = r.top - _size;
w = r.right + _size - x;
h = r.bottom + _size - y;
}
if (h < 2 * _fullsize + 2 * _shift) {
h = 2 * _fullsize + 2 * _shift;
}
if (w < 2 * (_fullsize + _shift)) {
w = 2 * (_fullsize + _shift);
}
if (w != _w) {
int from = (_w > 2 * (_fullsize + _shift)) ? (_w - _size - _fullsize - _shift) : (_fullsize - (_size - _shift));
int to = w - _size - _fullsize - _shift;
if (w > _widthMax) {
from = _fullsize - (_size - _shift);
do {
_widthMax *= 2;
} while (w > _widthMax);
for (int i = 0; i < 4; i += 2) {
DeleteObject(_bitmaps[i]);
_bitmaps[i] = CreateCompatibleBitmap(_screenContext, _widthMax, _size);
SelectObject(_contexts[i], _bitmaps[i]);
}
initCorners(Direction::Horizontal);
}
Gdiplus::Graphics graphics0(_contexts[0]);
Gdiplus::Graphics graphics2(_contexts[2]);
graphics0.SetCompositingMode(Gdiplus::CompositingModeSourceCopy);
graphics2.SetCompositingMode(Gdiplus::CompositingModeSourceCopy);
auto brush = getBrush(_alphas[0]);
if (to > from) {
graphics0.FillRectangle(&brush, from, 0, to - from, 2 * _shift);
for (int i = 2 * _shift; i < _size; ++i) {
auto pen = getPen(_alphas[i]);
graphics0.DrawLine(&pen, from, i, to, i);
graphics2.DrawLine(&pen, from, _size - (i - 2 * _shift) - 1, to, _size - (i - 2 * _shift) - 1);
}
for (int i = _size; i < _size + 2 * _shift; ++i) {
auto pen = getPen(_alphas[i]);
graphics2.DrawLine(&pen, from, _size - (i - 2 * _shift) - 1, to, _size - (i - 2 * _shift) - 1);
}
}
if (_w > w) {
graphics0.FillRectangle(&brush, w - _size - _fullsize - _shift, 0, _fullsize - (_size - _shift), _size);
graphics2.FillRectangle(&brush, w - _size - _fullsize - _shift, 0, _fullsize - (_size - _shift), _size);
}
horCorners(w, &graphics0, &graphics2);
POINT p0 = { x + _size, y }, p2 = { x + _size, y + h - _size }, f = { 0, 0 };
SIZE s = { w - 2 * _size, _size };
updateWindow(0, &p0, &s);
updateWindow(2, &p2, &s);
} else if (x != _x || y != _y) {
POINT p0 = { x + _size, y }, p2 = { x + _size, y + h - _size };
updateWindow(0, &p0);
updateWindow(2, &p2);
} else if (h != _h) {
POINT p2 = { x + _size, y + h - _size };
updateWindow(2, &p2);
}
if (h != _h) {
int from = (_h > 2 * _fullsize + 2 * _shift) ? (_h - _fullsize) : (_fullsize + 2 * _shift);
int to = h - _fullsize;
if (h > _heightMax) {
from = (_fullsize + 2 * _shift);
do {
_heightMax *= 2;
} while (h > _heightMax);
for (int i = 1; i < 4; i += 2) {
DeleteObject(_bitmaps[i]);
_bitmaps[i] = CreateCompatibleBitmap(_contexts[i], _size, _heightMax);
SelectObject(_contexts[i], _bitmaps[i]);
}
initCorners(Direction::Vertical);
}
Gdiplus::Graphics graphics1(_contexts[1]), graphics3(_contexts[3]);
graphics1.SetCompositingMode(Gdiplus::CompositingModeSourceCopy);
graphics3.SetCompositingMode(Gdiplus::CompositingModeSourceCopy);
auto brush = getBrush(_alphas[0]);
if (to > from) {
graphics1.FillRectangle(&brush, _size - _shift, from, _shift, to - from);
graphics3.FillRectangle(&brush, 0, from, _shift, to - from);
for (int i = 2 * _shift; i < _size + _shift; ++i) {
auto pen = getPen(_alphas[i]);
graphics1.DrawLine(&pen, _size + _shift - i - 1, from, _size + _shift - i - 1, to);
graphics3.DrawLine(&pen, i - _shift, from, i - _shift, to);
}
}
if (_h > h) {
graphics1.FillRectangle(&brush, 0, h - _fullsize, _size, _fullsize);
graphics3.FillRectangle(&brush, 0, h - _fullsize, _size, _fullsize);
}
verCorners(h, &graphics1, &graphics3);
POINT p1 = { x + w - _size, y }, p3 = { x, y }, f = { 0, 0 };
SIZE s = { _size, h };
updateWindow(1, &p1, &s);
updateWindow(3, &p3, &s);
} else if (x != _x || y != _y) {
POINT p1 = { x + w - _size, y }, p3 = { x, y };
updateWindow(1, &p1);
updateWindow(3, &p3);
} else if (w != _w) {
POINT p1 = { x + w - _size, y };
updateWindow(1, &p1);
}
_x = x;
_y = y;
_w = w;
_h = h;
if (_hidden && (changes & Change::Shown)) {
for (int i = 0; i < 4; ++i) {
SetWindowPos(
_handles[i],
_handle,
0,
0,
0,
0,
SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW | SWP_NOACTIVATE);
}
_hidden = false;
}
}
void WindowShadow::initBlend() {
_blend.AlphaFormat = AC_SRC_ALPHA;
_blend.SourceConstantAlpha = 255;
_blend.BlendFlags = 0;
_blend.BlendOp = AC_SRC_OVER;
}
void WindowShadow::updateWindow(int i, POINT *p, SIZE *s) {
static POINT f = { 0, 0 };
if (s) {
UpdateLayeredWindow(
_handles[i],
(s ? _screenContext : nullptr),
p,
s,
(s ? _contexts[i] : nullptr),
(s ? (&f) : nullptr),
_noKeyColor,
&_blend,
ULW_ALPHA);
} else {
SetWindowPos(
_handles[i],
0,
p->x,
p->y,
0,
0,
SWP_NOACTIVATE | SWP_NOSIZE | SWP_NOZORDER);
}
}
void WindowShadow::setResizeEnabled(bool enabled) {
_resizeEnabled = enabled;
}
LRESULT CALLBACK WindowShadow::WindowCallback(
HWND hwnd,
UINT msg,
WPARAM wParam,
LPARAM lParam) {
const auto i = ShadowByHandle.find(hwnd);
return (i != end(ShadowByHandle))
? i->second->windowCallback(hwnd, msg, wParam, lParam)
: DefWindowProc(hwnd, msg, wParam, lParam);
}
LRESULT WindowShadow::windowCallback(
HWND hwnd,
UINT msg,
WPARAM wParam,
LPARAM lParam) {
if (!working()) {
return DefWindowProc(hwnd, msg, wParam, lParam);
}
switch (msg) {
case WM_CLOSE:
_window->close();
return 0;
case WM_NCHITTEST: {
if (!_resizeEnabled) {
return HTNOWHERE;
}
const auto xPos = GET_X_LPARAM(lParam);
const auto yPos = GET_Y_LPARAM(lParam);
if (hwnd == _handles[0]) {
return HTTOP;
} else if (hwnd == _handles[1]) {
return (yPos < _y + _size)
? HTTOPRIGHT
: (yPos >= _y + _h - _size)
? HTBOTTOMRIGHT
: HTRIGHT;
} else if (hwnd == _handles[2]) {
return HTBOTTOM;
} else if (hwnd == _handles[3]) {
return (yPos < _y + _size)
? HTTOPLEFT
: (yPos >= _y + _h - _size)
? HTBOTTOMLEFT
: HTLEFT;
} else {
Unexpected("Handle in WindowShadow::windowCallback.");
}
} break;
case WM_NCACTIVATE:
return DefWindowProc(hwnd, msg, wParam, lParam);
case WM_NCLBUTTONDOWN:
case WM_NCLBUTTONUP:
case WM_NCLBUTTONDBLCLK:
case WM_NCMBUTTONDOWN:
case WM_NCMBUTTONUP:
case WM_NCMBUTTONDBLCLK:
case WM_NCRBUTTONDOWN:
case WM_NCRBUTTONUP:
case WM_NCRBUTTONDBLCLK:
case WM_NCXBUTTONDOWN:
case WM_NCXBUTTONUP:
case WM_NCXBUTTONDBLCLK:
case WM_NCMOUSEHOVER:
case WM_NCMOUSELEAVE:
case WM_NCMOUSEMOVE:
case WM_NCPOINTERUPDATE:
case WM_NCPOINTERDOWN:
case WM_NCPOINTERUP:
if (msg == WM_NCLBUTTONDOWN) {
::SetForegroundWindow(_handle);
}
return SendMessage(_handle, msg, wParam, lParam);
case WM_ACTIVATE:
if (wParam == WA_ACTIVE) {
if ((HWND)lParam != _handle) {
::SetForegroundWindow(hwnd);
::SetWindowPos(
_handle,
hwnd,
0,
0,
0,
0,
SWP_NOMOVE | SWP_NOSIZE);
}
}
return DefWindowProc(hwnd, msg, wParam, lParam);
default:
return DefWindowProc(hwnd, msg, wParam, lParam);
}
}
} // namespace Platform
} // namespace Ui