// 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 "styles/style_widgets.h" #include #include #include #include namespace Ui { namespace Platform { namespace { base::flat_map> ShadowByHandle; } // namespace WindowShadow::WindowShadow(not_null window, QColor color) : _window(window) , _handle(GetWindowHandle(window)) { window->hide(); 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); const auto instance = (HINSTANCE)GetModuleHandle(nullptr); for (auto i = 0; i != 4; ++i) { const auto className = "WindowShadow" + QString::number(i); const auto wcharClassName = className.toStdWString(); 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; if (!RegisterClassEx(&wc)) { return; } _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); SetWindowLong(_handles[i], GWL_HWNDPARENT, (LONG)_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(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); } } 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: { 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