Support nice Ui::Window under Windows.

This commit is contained in:
John Preston 2019-09-19 11:42:20 +03:00
parent d68dfd3320
commit d5c9ad77a9
35 changed files with 1689 additions and 18 deletions

View file

@ -28,13 +28,20 @@
<(src_loc)/ui/layers/layer_manager.h <(src_loc)/ui/layers/layer_manager.h
<(src_loc)/ui/layers/layer_widget.cpp <(src_loc)/ui/layers/layer_widget.cpp
<(src_loc)/ui/layers/layer_widget.h <(src_loc)/ui/layers/layer_widget.h
<(src_loc)/ui/platform/linux/ui_utility_linux.cpp
<(src_loc)/ui/platform/linux/ui_utility_linux.h
<(src_loc)/ui/platform/mac/ui_utility_mac.h
<(src_loc)/ui/platform/mac/ui_utility_mac.mm
<(src_loc)/ui/platform/win/ui_window_shadow_win.cpp
<(src_loc)/ui/platform/win/ui_window_shadow_win.h
<(src_loc)/ui/platform/win/ui_window_title_win.cpp
<(src_loc)/ui/platform/win/ui_window_title_win.h
<(src_loc)/ui/platform/win/ui_window_win.cpp
<(src_loc)/ui/platform/win/ui_window_win.h
<(src_loc)/ui/platform/win/ui_utility_win.cpp
<(src_loc)/ui/platform/win/ui_utility_win.h
<(src_loc)/ui/platform/ui_platform_window.h
<(src_loc)/ui/platform/ui_platform_utility.h <(src_loc)/ui/platform/ui_platform_utility.h
<(src_loc)/ui/platform/linux/ui_platform_utility_linux.cpp
<(src_loc)/ui/platform/linux/ui_platform_utility_linux.h
<(src_loc)/ui/platform/mac/ui_platform_utility_mac.h
<(src_loc)/ui/platform/mac/ui_platform_utility_mac.mm
<(src_loc)/ui/platform/win/ui_platform_utility_win.cpp
<(src_loc)/ui/platform/win/ui_platform_utility_win.h
<(src_loc)/ui/style/style_core.cpp <(src_loc)/ui/style/style_core.cpp
<(src_loc)/ui/style/style_core.h <(src_loc)/ui/style/style_core.h
<(src_loc)/ui/style/style_core_color.cpp <(src_loc)/ui/style/style_core_color.cpp
@ -88,6 +95,8 @@
<(src_loc)/ui/widgets/shadow.h <(src_loc)/ui/widgets/shadow.h
<(src_loc)/ui/widgets/tooltip.cpp <(src_loc)/ui/widgets/tooltip.cpp
<(src_loc)/ui/widgets/tooltip.h <(src_loc)/ui/widgets/tooltip.h
<(src_loc)/ui/widgets/window.cpp
<(src_loc)/ui/widgets/window.h
<(src_loc)/ui/wrap/fade_wrap.cpp <(src_loc)/ui/wrap/fade_wrap.cpp
<(src_loc)/ui/wrap/fade_wrap.h <(src_loc)/ui/wrap/fade_wrap.h
<(src_loc)/ui/wrap/padding_wrap.cpp <(src_loc)/ui/wrap/padding_wrap.cpp

Binary file not shown.

After

Width:  |  Height:  |  Size: 164 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 224 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 376 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 119 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 144 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 184 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 100 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 122 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 149 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 136 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 160 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 209 B

BIN
icons/window_shadow.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 225 B

BIN
icons/window_shadow@2x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 372 B

BIN
icons/window_shadow@3x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 593 B

View file

@ -697,8 +697,8 @@ void LayerStackWidget::showSpecialLayer(
} }
bool LayerStackWidget::showSectionInternal( bool LayerStackWidget::showSectionInternal(
not_null<Window::SectionMemento*> memento, not_null<::Window::SectionMemento*> memento,
const Window::SectionShow &params) { const ::Window::SectionShow &params) {
if (_specialLayer) { if (_specialLayer) {
return _specialLayer->showSectionInternal(memento, params); return _specialLayer->showSectionInternal(memento, params);
} }

View file

@ -57,8 +57,8 @@ public:
return false; return false;
} }
virtual bool showSectionInternal( virtual bool showSectionInternal(
not_null<Window::SectionMemento*> memento, not_null<::Window::SectionMemento*> memento,
const Window::SectionShow &params) { const ::Window::SectionShow &params) {
return false; return false;
} }
virtual bool closeByOutsideClick() const { virtual bool closeByOutsideClick() const {
@ -118,8 +118,8 @@ public:
void removeBodyCache(); void removeBodyCache();
bool showSectionInternal( bool showSectionInternal(
not_null<Window::SectionMemento*> memento, not_null<::Window::SectionMemento*> memento,
const Window::SectionShow &params); const ::Window::SectionShow &params);
bool layerShown() const; bool layerShown() const;

View file

@ -4,7 +4,7 @@
// For license and copyright information please follow this link: // For license and copyright information please follow this link:
// https://github.com/desktop-app/legal/blob/master/LEGAL // https://github.com/desktop-app/legal/blob/master/LEGAL
// //
#include "ui/platform/linux/ui_platform_utility_linux.h" #include "ui/platform/linux/ui_utility_linux.h"
#include "base/flat_set.h" #include "base/flat_set.h"
#include "ui/ui_log.h" #include "ui/ui_log.h"

View file

@ -4,7 +4,7 @@
// For license and copyright information please follow this link: // For license and copyright information please follow this link:
// https://github.com/desktop-app/legal/blob/master/LEGAL // https://github.com/desktop-app/legal/blob/master/LEGAL
// //
#include "ui/platform/mac/ui_platform_utility_mac.h" #include "ui/platform/mac/ui_platform_mac.h"
#include "ui/integration.h" #include "ui/integration.h"

View file

@ -35,9 +35,9 @@ void DrainMainQueue(); // Needed only if UseMainQueueGeneric() is false.
// Platform dependent implementations. // Platform dependent implementations.
#ifdef Q_OS_MAC #ifdef Q_OS_MAC
#include "ui/platform/mac/ui_platform_utility_mac.h" #include "ui/platform/mac/ui_utility_mac.h"
#elif defined Q_OS_LINUX // Q_OS_MAC #elif defined Q_OS_LINUX // Q_OS_MAC
#include "ui/platform/linux/ui_platform_utility_linux.h" #include "ui/platform/linux/ui_utility_linux.h"
#elif defined Q_OS_WINRT || defined Q_OS_WIN // Q_OS_MAC || Q_OS_LINUX #elif defined Q_OS_WINRT || defined Q_OS_WIN // Q_OS_MAC || Q_OS_LINUX
#include "ui/platform/win/ui_platform_utility_win.h" #include "ui/platform/win/ui_utility_win.h"
#endif // Q_OS_MAC || Q_OS_LINUX || Q_OS_WINRT || Q_OS_WIN #endif // Q_OS_MAC || Q_OS_LINUX || Q_OS_WINRT || Q_OS_WIN

View file

@ -0,0 +1,28 @@
// 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
//
#pragma once
namespace Ui {
class RpWidget;
namespace Platform {
class BasicWindowHelper {
public:
[[nodiscard]] virtual not_null<RpWidget*> body() = 0;
virtual void setTitle(const QString &title) = 0;
virtual void setSizeMin(QSize size) = 0;
virtual ~BasicWindowHelper() = default;
};
[[nodiscard]] std::unique_ptr<BasicWindowHelper> CreateWindowHelper(
not_null<RpWidget*> window);
} // namespace Platform
} // namespace Ui

View file

@ -4,7 +4,7 @@
// For license and copyright information please follow this link: // For license and copyright information please follow this link:
// https://github.com/desktop-app/legal/blob/master/LEGAL // https://github.com/desktop-app/legal/blob/master/LEGAL
// //
#include "ui/platform/win/ui_platform_utility_win.h" #include "ui/platform/win/ui_utility_win.h"
#include <QtWidgets/QApplication> #include <QtWidgets/QApplication>

View file

@ -0,0 +1,612 @@
// 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 <QtGui/QPainter>
#include <QtWidgets/QApplication>
#include <QtWidgets/QDesktopWidget>
#include <windowsx.h>
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)) {
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

View file

@ -0,0 +1,110 @@
// 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
//
#pragma once
#include "base/flags.h"
#include <windows.h>
#define min(a, b) ((a) < (b) ? (a) : (b))
#define max(a, b) ((a) < (b) ? (b) : (a))
#include <gdiplus.h>
#undef min
#undef max
class QColor;
namespace Ui {
class RpWidget;
namespace Platform {
class WindowShadow final {
public:
WindowShadow(not_null<RpWidget*> window, QColor color);
~WindowShadow();
enum class Change {
Moved = (1 << 0),
Resized = (1 << 1),
Activate = (1 << 2),
Deactivate = (1 << 3),
Hidden = (1 << 4),
Shown = (1 << 5),
};
friend inline constexpr bool is_flag_type(Change) { return true; };
using Changes = base::flags<Change>;
void setColor(QColor color);
void update(Changes changes, WINDOWPOS *pos = nullptr);
void updateWindow(int i, POINT *p, SIZE *s = nullptr);
private:
enum class Direction {
Horizontal = (1 << 0),
Vertical = (1 << 1),
All = Horizontal | Vertical,
};
friend inline constexpr bool is_flag_type(Direction) { return true; };
using Directions = base::flags<Direction>;
static LRESULT CALLBACK WindowCallback(
HWND hwnd,
UINT msg,
WPARAM wParam,
LPARAM lParam);
LRESULT windowCallback(
HWND hwnd,
UINT msg,
WPARAM wParam,
LPARAM lParam);
[[nodiscard]] bool working() const;
void destroy();
void init(QColor color);
void initBlend();
void initCorners(Directions directions = Direction::All);
void verCorners(int h, Gdiplus::Graphics *pgraphics1, Gdiplus::Graphics *pgraphics3);
void horCorners(int w, Gdiplus::Graphics *pgraphics0, Gdiplus::Graphics *pgraphics2);
[[nodiscard]] Gdiplus::Color getColor(uchar alpha) const;
[[nodiscard]] Gdiplus::SolidBrush getBrush(uchar alpha) const;
[[nodiscard]] Gdiplus::Pen getPen(uchar alpha) const;
const not_null<RpWidget*> _window;
const HWND _handle;
int _x = 0;
int _y = 0;
int _w = 0;
int _h = 0;
int _metaSize = 0;
int _fullsize = 0;
int _size = 0;
int _shift = 0;
std::vector<BYTE> _alphas;
std::vector<BYTE> _colors;
bool _hidden = true;
HWND _handles[4] = { nullptr };
HDC _contexts[4] = { nullptr };
HBITMAP _bitmaps[4] = { nullptr };
HDC _screenContext = nullptr;
int _widthMax = 0;
int _heightMax = 0;
BLENDFUNCTION _blend;
BYTE _r = 0;
BYTE _g = 0;
BYTE _b = 0;
COLORREF _noKeyColor = RGB(255, 255, 255);
};
} // namespace Platform
} // namespace Ui

View file

@ -0,0 +1,160 @@
// 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_title_win.h"
#include "ui/widgets/buttons.h"
#include "ui/widgets/shadow.h"
#include "ui/ui_utility.h"
#include "styles/style_widgets.h"
#include "styles/palette.h"
#include <QtGui/QPainter>
#include <QtGui/QtEvents>
#include <QtGui/QWindow>
namespace Ui {
namespace Platform {
TitleWidget::TitleWidget(not_null<RpWidget*> parent)
: RpWidget(parent)
, _minimize(this, st::titleButtonMinimize)
, _maximizeRestore(this, st::titleButtonMaximize)
, _close(this, st::titleButtonClose)
, _shadow(this, st::titleShadow)
, _maximizedState(parent->windowState() & Qt::WindowMaximized)
, _activeState(parent->isActiveWindow()) {
init();
}
void TitleWidget::setText(const QString &text) {
window()->setWindowTitle(text);
}
not_null<RpWidget*> TitleWidget::window() const {
return static_cast<RpWidget*>(parentWidget());
}
void TitleWidget::init() {
_minimize->setClickedCallback([=] {
window()->setWindowState(Qt::WindowMinimized);
_minimize->clearState();
});
_minimize->setPointerCursor(false);
_maximizeRestore->setClickedCallback([=] {
window()->setWindowState(_maximizedState
? Qt::WindowNoState
: Qt::WindowMaximized);
_maximizeRestore->clearState();
});
_maximizeRestore->setPointerCursor(false);
_close->setClickedCallback([=] {
window()->close();
_close->clearState();
});
_close->setPointerCursor(false);
setAttribute(Qt::WA_OpaquePaintEvent);
window()->widthValue(
) | rpl::start_with_next([=](int width) {
setGeometry(0, 0, width, st::titleHeight);
}, lifetime());
window()->createWinId();
connect(
window()->windowHandle(),
&QWindow::windowStateChanged,
[=](Qt::WindowState state) { handleWindowStateChanged(state); });
_activeState = isActiveWindow();
updateButtonsState();
}
void TitleWidget::paintEvent(QPaintEvent *e) {
const auto active = isActiveWindow();
if (_activeState != active) {
_activeState = active;
updateButtonsState();
}
QPainter(this).fillRect(e->rect(), active ? st::titleBgActive : st::titleBg);
}
void TitleWidget::updateControlsPosition() {
auto right = 0;
_close->moveToRight(right, 0); right += _close->width();
_maximizeRestore->moveToRight(right, 0); right += _maximizeRestore->width();
_minimize->moveToRight(right, 0);
}
void TitleWidget::resizeEvent(QResizeEvent *e) {
updateControlsPosition();
_shadow->setGeometry(0, height() - st::lineWidth, width(), st::lineWidth);
}
void TitleWidget::updateControlsVisibility() {
updateControlsPosition();
update();
}
void TitleWidget::handleWindowStateChanged(Qt::WindowState state) {
if (state == Qt::WindowMinimized) return;
auto maximized = (state == Qt::WindowMaximized);
if (_maximizedState != maximized) {
_maximizedState = maximized;
updateButtonsState();
}
}
void TitleWidget::updateButtonsState() {
const auto minimize = _activeState
? &st::titleButtonMinimizeIconActive
: nullptr;
const auto minimizeOver = _activeState
? &st::titleButtonMinimizeIconActiveOver
: nullptr;
_minimize->setIconOverride(minimize, minimizeOver);
if (_maximizedState) {
const auto restore = _activeState
? &st::titleButtonRestoreIconActive
: &st::titleButtonRestoreIcon;
const auto restoreOver = _activeState
? &st::titleButtonRestoreIconActiveOver
: &st::titleButtonRestoreIconOver;
_maximizeRestore->setIconOverride(restore, restoreOver);
} else {
const auto maximize = _activeState
? &st::titleButtonMaximizeIconActive
: nullptr;
const auto maximizeOver = _activeState
? &st::titleButtonMaximizeIconActiveOver
: nullptr;
_maximizeRestore->setIconOverride(maximize, maximizeOver);
}
const auto close = _activeState
? &st::titleButtonCloseIconActive
: nullptr;
const auto closeOver = _activeState
? &st::titleButtonCloseIconActiveOver
: nullptr;
_close->setIconOverride(close, closeOver);
}
HitTestResult TitleWidget::hitTest(QPoint point) const {
if (false
|| (_minimize->geometry().contains(point))
|| (_maximizeRestore->geometry().contains(point))
|| (_close->geometry().contains(point))
) {
return HitTestResult::SysButton;
} else if (rect().contains(point)) {
return HitTestResult::Caption;
}
return HitTestResult::None;
}
} // namespace Platform
} // namespace Ui

View file

@ -0,0 +1,68 @@
// 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
//
#pragma once
#include "ui/rp_widget.h"
#include "base/object_ptr.h"
#include <QtCore/QRect>
#include <QtCore/QPoint>
namespace Ui {
class IconButton;
class PlainShadow;
namespace Platform {
enum class HitTestResult {
None = 0,
Client,
SysButton,
Caption,
Top,
TopRight,
Right,
BottomRight,
Bottom,
BottomLeft,
Left,
TopLeft,
};
class TitleWidget : public RpWidget {
public:
explicit TitleWidget(not_null<RpWidget*> parent);
void setText(const QString &text);
HitTestResult hitTest(QPoint point) const;
protected:
void paintEvent(QPaintEvent *e) override;
void resizeEvent(QResizeEvent *e) override;
private:
not_null<RpWidget*> window() const;
void init();
void handleWindowStateChanged(Qt::WindowState state = Qt::WindowNoState);
void updateControlsVisibility();
void updateButtonsState();
void updateControlsPosition();
object_ptr<Ui::IconButton> _minimize;
object_ptr<Ui::IconButton> _maximizeRestore;
object_ptr<Ui::IconButton> _close;
object_ptr<Ui::PlainShadow> _shadow;
bool _maximizedState = false;
bool _activeState = false;
};
} // namespace Platform
} // namespace Ui

View file

@ -0,0 +1,466 @@
// 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_win.h"
#include "ui/inactive_press.h"
#include "ui/platform/win/ui_window_title_win.h"
#include "base/platform/base_platform_info.h"
#include "styles/palette.h"
#include <QtCore/QAbstractNativeEventFilter>
#include <QtGui/QWindow>
#include <QtWidgets/QStyleFactory>
#include <QtWidgets/QApplication>
#include <qpa/qplatformnativeinterface.h>
#include <dwmapi.h>
#include <uxtheme.h>
Q_DECLARE_METATYPE(QMargins);
namespace Ui {
namespace Platform {
namespace {
bool IsCompositionEnabled() {
auto result = BOOL(FALSE);
const auto success = (DwmIsCompositionEnabled(&result) == S_OK);
return success && result;
}
} // namespace
class WindowHelper::NativeFilter final : public QAbstractNativeEventFilter {
public:
void registerWindow(HWND handle, not_null<WindowHelper*> helper);
void unregisterWindow(HWND handle);
bool nativeEventFilter(
const QByteArray &eventType,
void *message,
long *result) override;
private:
base::flat_map<HWND, not_null<WindowHelper*>> _windowByHandle;
};
void WindowHelper::NativeFilter::registerWindow(
HWND handle,
not_null<WindowHelper*> helper) {
_windowByHandle.emplace(handle, helper);
}
void WindowHelper::NativeFilter::unregisterWindow(HWND handle) {
_windowByHandle.remove(handle);
}
bool WindowHelper::NativeFilter::nativeEventFilter(
const QByteArray &eventType,
void *message,
long *result) {
const auto msg = static_cast<MSG*>(message);
const auto i = _windowByHandle.find(msg->hwnd);
return (i != end(_windowByHandle))
? i->second->handleNativeEvent(
msg->message,
msg->wParam,
msg->lParam,
static_cast<LRESULT*>(result))
: false;
}
WindowHelper::WindowHelper(not_null<RpWidget*> window)
: _window(window)
, _handle(GetWindowHandle(_window))
, _title(Ui::CreateChild<TitleWidget>(_window.get()))
, _body(Ui::CreateChild<RpWidget>(_window.get()))
, _shadow(_window, st::windowShadowFg->c) {
Expects(_handle != nullptr);
GetNativeFilter()->registerWindow(_handle, this);
init();
}
WindowHelper::~WindowHelper() {
GetNativeFilter()->unregisterWindow(_handle);
}
not_null<RpWidget*> WindowHelper::body() {
return _body;
}
void WindowHelper::setTitle(const QString &title) {
_title->setText(title);
_window->setWindowTitle(title);
}
void WindowHelper::setSizeMin(QSize size) {
_window->setMinimumSize(size.width(), _title->height() + size.height());
}
void WindowHelper::init() {
style::PaletteChanged(
) | rpl::start_with_next([=] {
_shadow.setColor(st::windowShadowFg->c);
}, _window->lifetime());
rpl::combine(
_window->sizeValue(),
_title->heightValue()
) | rpl::start_with_next([=](QSize size, int titleHeight) {
_body->setGeometry(
0,
titleHeight,
size.width(),
size.height() - titleHeight);
}, _body->lifetime());
updateMargins();
if (!::Platform::IsWindows8OrGreater()) {
SetWindowTheme(_handle, L" ", L" ");
QApplication::setStyle(QStyleFactory::create("Windows"));
}
_menu = GetSystemMenu(_handle, FALSE);
updateSystemMenu();
Ui::Connect(
_window->windowHandle(),
&QWindow::windowStateChanged,
[=](Qt::WindowState state) { updateSystemMenu(state); });
}
bool WindowHelper::handleNativeEvent(
UINT msg,
WPARAM wParam,
LPARAM lParam,
LRESULT *result) {
switch (msg) {
case WM_ACTIVATE: {
if (LOWORD(wParam) == WA_CLICKACTIVE) {
Ui::MarkInactivePress(_window, true);
}
if (LOWORD(wParam) != WA_INACTIVE) {
_shadow.update(WindowShadow::Change::Activate);
} else {
_shadow.update(WindowShadow::Change::Deactivate);
}
_window->update();
} return false;
case WM_NCPAINT: {
if (::Platform::IsWindows8OrGreater()) {
return false;
}
if (result) *result = 0;
} return true;
case WM_NCCALCSIZE: {
WINDOWPLACEMENT wp;
wp.length = sizeof(WINDOWPLACEMENT);
if (GetWindowPlacement(_handle, &wp)
&& (wp.showCmd == SW_SHOWMAXIMIZED)) {
const auto params = (LPNCCALCSIZE_PARAMS)lParam;
const auto r = (wParam == TRUE)
? &params->rgrc[0]
: (LPRECT)lParam;
const auto hMonitor = MonitorFromPoint(
{ (r->left + r->right) / 2, (r->top + r->bottom) / 2 },
MONITOR_DEFAULTTONEAREST);
if (hMonitor) {
MONITORINFO mi;
mi.cbSize = sizeof(mi);
if (GetMonitorInfo(hMonitor, &mi)) {
*r = mi.rcWork;
}
}
}
if (result) *result = 0;
return true;
}
case WM_NCACTIVATE: {
if (IsCompositionEnabled()) {
const auto res = DefWindowProc(_handle, msg, wParam, -1);
if (result) *result = res;
} else {
// Thanks https://github.com/melak47/BorderlessWindow
if (result) *result = 1;
}
} return true;
case WM_WINDOWPOSCHANGING:
case WM_WINDOWPOSCHANGED: {
WINDOWPLACEMENT wp;
wp.length = sizeof(WINDOWPLACEMENT);
if (GetWindowPlacement(_handle, &wp)
&& (wp.showCmd == SW_SHOWMAXIMIZED
|| wp.showCmd == SW_SHOWMINIMIZED)) {
_shadow.update(WindowShadow::Change::Hidden);
} else {
_shadow.update(
WindowShadow::Change::Moved | WindowShadow::Change::Resized,
(WINDOWPOS*)lParam);
}
} return false;
case WM_SIZE: {
if (wParam == SIZE_MAXIMIZED
|| wParam == SIZE_RESTORED
|| wParam == SIZE_MINIMIZED) {
if (wParam != SIZE_RESTORED
|| _window->windowState() != Qt::WindowNoState) {
Qt::WindowState state = Qt::WindowNoState;
if (wParam == SIZE_MAXIMIZED) {
state = Qt::WindowMaximized;
} else if (wParam == SIZE_MINIMIZED) {
state = Qt::WindowMinimized;
}
emit _window->windowHandle()->windowStateChanged(state);
}
updateMargins();
const auto changes = (wParam == SIZE_MINIMIZED
|| wParam == SIZE_MAXIMIZED)
? WindowShadow::Change::Hidden
: (WindowShadow::Change::Resized
| WindowShadow::Change::Shown);
_shadow.update(changes);
}
} return false;
case WM_SHOWWINDOW: {
const auto style = GetWindowLong(_handle, GWL_STYLE);
const auto changes = WindowShadow::Change::Resized
| ((wParam && !(style & (WS_MAXIMIZE | WS_MINIMIZE)))
? WindowShadow::Change::Shown
: WindowShadow::Change::Hidden);
_shadow.update(changes);
} return false;
case WM_MOVE: {
_shadow.update(WindowShadow::Change::Moved);
} return false;
case WM_NCHITTEST: {
if (!result) {
return false;
}
const auto p = MAKEPOINTS(lParam);
auto r = RECT();
GetWindowRect(_handle, &r);
const auto mapped = QPoint(
p.x - r.left + _marginsDelta.left(),
p.y - r.top + _marginsDelta.top());
if (!_window->rect().contains(mapped)) {
*result = HTTRANSPARENT;
} else if (!_title->geometry().contains(mapped)) {
*result = HTCLIENT;
} else switch (_title->hitTest(_title->pos() + mapped)) {
case HitTestResult::Client:
case HitTestResult::SysButton: *result = HTCLIENT; break;
case HitTestResult::Caption: *result = HTCAPTION; break;
case HitTestResult::Top: *result = HTTOP; break;
case HitTestResult::TopRight: *result = HTTOPRIGHT; break;
case HitTestResult::Right: *result = HTRIGHT; break;
case HitTestResult::BottomRight: *result = HTBOTTOMRIGHT; break;
case HitTestResult::Bottom: *result = HTBOTTOM; break;
case HitTestResult::BottomLeft: *result = HTBOTTOMLEFT; break;
case HitTestResult::Left: *result = HTLEFT; break;
case HitTestResult::TopLeft: *result = HTTOPLEFT; break;
case HitTestResult::None:
default: *result = HTTRANSPARENT; break;
};
} return true;
case WM_NCRBUTTONUP: {
SendMessage(_handle, WM_SYSCOMMAND, SC_MOUSEMENU, lParam);
} return true;
case WM_SYSCOMMAND: {
if (wParam == SC_MOUSEMENU) {
POINTS p = MAKEPOINTS(lParam);
updateSystemMenu(_window->windowHandle()->windowState());
TrackPopupMenu(
_menu,
TPM_LEFTALIGN | TPM_TOPALIGN | TPM_LEFTBUTTON,
p.x,
p.y,
0,
_handle,
0);
}
} return false;
case WM_COMMAND: {
if (HIWORD(wParam)) {
return false;
}
const auto command = LOWORD(wParam);
switch (command) {
case SC_CLOSE: _window->close(); return true;
case SC_MINIMIZE:
_window->setWindowState(Qt::WindowMinimized);
return true;
case SC_MAXIMIZE:
_window->setWindowState(Qt::WindowMaximized);
return true;
case SC_RESTORE:
_window->setWindowState(Qt::WindowNoState);
return true;
}
} return true;
}
return false;
}
void WindowHelper::updateMargins() {
if (_updatingMargins) return;
_updatingMargins = true;
const auto guard = gsl::finally([&] { _updatingMargins = false; });
RECT r, a;
GetClientRect(_handle, &r);
a = r;
const auto style = GetWindowLong(_handle, GWL_STYLE);
const auto styleEx = GetWindowLong(_handle, GWL_EXSTYLE);
AdjustWindowRectEx(&a, style, false, styleEx);
auto margins = QMargins(
a.left - r.left,
a.top - r.top,
r.right - a.right,
r.bottom - a.bottom);
if (style & WS_MAXIMIZE) {
RECT w, m;
GetWindowRect(_handle , &w);
m = w;
HMONITOR hMonitor = MonitorFromRect(&w, MONITOR_DEFAULTTONEAREST);
if (hMonitor) {
MONITORINFO mi;
mi.cbSize = sizeof(mi);
GetMonitorInfo(hMonitor, &mi);
m = mi.rcWork;
}
_marginsDelta = QMargins(
w.left - m.left,
w.top - m.top,
m.right - w.right,
m.bottom - w.bottom);
margins.setLeft(margins.left() - _marginsDelta.left());
margins.setRight(margins.right() - _marginsDelta.right());
margins.setBottom(margins.bottom() - _marginsDelta.bottom());
margins.setTop(margins.top() - _marginsDelta.top());
} else if (!_marginsDelta.isNull()) {
RECT w;
GetWindowRect(_handle, &w);
SetWindowPos(
_handle,
0,
0,
0,
w.right - w.left - _marginsDelta.left() - _marginsDelta.right(),
w.bottom - w.top - _marginsDelta.top() - _marginsDelta.bottom(),
(SWP_NOMOVE
| SWP_NOSENDCHANGING
| SWP_NOZORDER
| SWP_NOACTIVATE
| SWP_NOREPOSITION));
_marginsDelta = QMargins();
}
if (const auto native = QGuiApplication::platformNativeInterface()) {
native->setWindowProperty(
_window->windowHandle()->handle(),
"WindowsCustomMargins",
QVariant::fromValue<QMargins>(margins));
}
}
void WindowHelper::updateSystemMenu() {
updateSystemMenu(_window->windowHandle()->windowState());
}
void WindowHelper::updateSystemMenu(Qt::WindowState state) {
if (!_menu) {
return;
}
const auto menuToDisable = (state == Qt::WindowMaximized)
? SC_MAXIMIZE
: (state == Qt::WindowMinimized)
? SC_MINIMIZE
: SC_RESTORE;
const auto itemCount = GetMenuItemCount(_menu);
for (int i = 0; i < itemCount; ++i) {
MENUITEMINFO itemInfo = { 0 };
itemInfo.cbSize = sizeof(itemInfo);
itemInfo.fMask = MIIM_TYPE | MIIM_STATE | MIIM_ID;
if (!GetMenuItemInfo(_menu, i, TRUE, &itemInfo)) {
break;
}
if (itemInfo.fType & MFT_SEPARATOR) {
continue;
} else if (!itemInfo.wID || itemInfo.wID == SC_CLOSE) {
continue;
}
UINT fOldState = itemInfo.fState;
UINT fState = itemInfo.fState & ~(MFS_DISABLED | MFS_DEFAULT);
if (itemInfo.wID == menuToDisable
|| (itemInfo.wID != SC_MINIMIZE
&& itemInfo.wID != SC_MAXIMIZE
&& itemInfo.wID != SC_RESTORE)) {
fState |= MFS_DISABLED;
}
itemInfo.fMask = MIIM_STATE;
itemInfo.fState = fState;
if (!SetMenuItemInfo(_menu, i, TRUE, &itemInfo)) {
break;
}
}
}
not_null<WindowHelper::NativeFilter*> WindowHelper::GetNativeFilter() {
Expects(QCoreApplication::instance() != nullptr);
static const auto GlobalFilter = [&] {
const auto application = QCoreApplication::instance();
const auto filter = Ui::CreateChild<NativeFilter>(application);
application->installNativeEventFilter(filter);
return filter;
}();
return GlobalFilter;
}
HWND GetWindowHandle(not_null<RpWidget*> widget) {
widget->window()->createWinId();
const auto window = widget->window()->windowHandle();
const auto native = QGuiApplication::platformNativeInterface();
Assert(window != nullptr);
Assert(native != nullptr);
return static_cast<HWND>(native->nativeResourceForWindow(
QByteArrayLiteral("handle"),
window));
}
std::unique_ptr<BasicWindowHelper> CreateWindowHelper(
not_null<RpWidget*> window) {
return std::make_unique<WindowHelper>(window);
}
} // namespace Platform
} // namespace Ui

View file

@ -0,0 +1,58 @@
// 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
//
#pragma once
#include "ui/platform/ui_platform_window.h"
#include "ui/platform/win/ui_window_shadow_win.h"
#include <windef.h>
namespace Ui {
namespace Platform {
class TitleWidget;
class WindowHelper final : public BasicWindowHelper {
public:
explicit WindowHelper(not_null<RpWidget*> window);
~WindowHelper();
not_null<RpWidget*> body() override;
void setTitle(const QString &title) override;
void setSizeMin(QSize size) override;
private:
class NativeFilter;
friend class NativeFilter;
void init();
void updateMargins();
void updateSystemMenu();
void updateSystemMenu(Qt::WindowState state);
[[nodiscard]] bool handleNativeEvent(
UINT msg,
WPARAM wParam,
LPARAM lParam,
LRESULT *result);
static not_null<NativeFilter*> GetNativeFilter();
const not_null<RpWidget*> _window;
const HWND _handle = nullptr;
const not_null<TitleWidget*> _title;
const not_null<RpWidget*> _body;
WindowShadow _shadow;
bool _updatingMargins = false;
QMargins _marginsDelta;
HMENU _menu = nullptr;
};
[[nodiscard]] HWND GetWindowHandle(not_null<RpWidget*> widget);
} // namespace Platform
} // namespace Ui

View file

@ -1161,3 +1161,86 @@ backButton: IconButton(defaultIconButton) {
color: windowBgOver; color: windowBgOver;
} }
} }
// Windows specific title
titleHeight: 21px;
titleButtonMinimize: IconButton {
width: 24px;
height: 21px;
icon: icon {
{ size(24px, 21px), titleButtonBg },
{ "title_button_minimize", titleButtonFg, point(4px, 4px) },
};
iconOver: icon {
{ size(24px, 21px), titleButtonBgOver },
{ "title_button_minimize", titleButtonFgOver, point(4px, 4px) },
};
iconPosition: point(0px, 0px);
}
titleButtonMinimizeIconActive: icon {
{ size(24px, 21px), titleButtonBgActive },
{ "title_button_minimize", titleButtonFgActive, point(4px, 4px) },
};
titleButtonMinimizeIconActiveOver: icon {
{ size(24px, 21px), titleButtonBgActiveOver },
{ "title_button_minimize", titleButtonFgActiveOver, point(4px, 4px) },
};
titleButtonMaximize: IconButton(titleButtonMinimize) {
icon: icon {
{ size(24px, 21px), titleButtonBg },
{ "title_button_maximize", titleButtonFg, point(4px, 4px) },
};
iconOver: icon {
{ size(24px, 21px), titleButtonBgOver },
{ "title_button_maximize", titleButtonFgOver, point(4px, 4px) },
};
}
titleButtonMaximizeIconActive: icon {
{ size(24px, 21px), titleButtonBgActive },
{ "title_button_maximize", titleButtonFgActive, point(4px, 4px) },
};
titleButtonMaximizeIconActiveOver: icon {
{ size(24px, 21px), titleButtonBgActiveOver },
{ "title_button_maximize", titleButtonFgActiveOver, point(4px, 4px) },
};
titleButtonRestoreIcon: icon {
{ size(24px, 21px), titleButtonBg },
{ "title_button_restore", titleButtonFg, point(4px, 4px) },
};
titleButtonRestoreIconOver: icon {
{ size(24px, 21px), titleButtonBgOver },
{ "title_button_restore", titleButtonFgOver, point(4px, 4px) },
};
titleButtonRestoreIconActive: icon {
{ size(24px, 21px), titleButtonBgActive },
{ "title_button_restore", titleButtonFgActive, point(4px, 4px) },
};
titleButtonRestoreIconActiveOver: icon {
{ size(24px, 21px), titleButtonBgActiveOver },
{ "title_button_restore", titleButtonFgActiveOver, point(4px, 4px) },
};
titleButtonClose: IconButton(titleButtonMinimize) {
width: 25px;
icon: icon {
{ size(25px, 21px), titleButtonCloseBg },
{ "title_button_close", titleButtonCloseFg, point(5px, 4px) },
};
iconOver: icon {
{ size(25px, 21px), titleButtonCloseBgOver },
{ "title_button_close", titleButtonCloseFgOver, point(5px, 4px) },
};
}
titleButtonCloseIconActive: icon {
{ size(25px, 21px), titleButtonCloseBgActive },
{ "title_button_close", titleButtonCloseFgActive, point(5px, 4px) },
};
titleButtonCloseIconActiveOver: icon {
{ size(25px, 21px), titleButtonCloseBgActiveOver },
{ "title_button_close", titleButtonCloseFgActiveOver, point(5px, 4px) },
};
windowShadow: icon {{ "window_shadow", windowShadowFg }};
windowShadowShift: 1px;

45
ui/widgets/window.cpp Normal file
View file

@ -0,0 +1,45 @@
// 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/widgets/window.h"
#include "ui/platform/ui_platform_window.h"
namespace Ui {
Window::Window(QWidget *parent)
: RpWidget(parent)
, _helper(Platform::CreateWindowHelper(this)) {
hide();
}
Window::~Window() = default;
not_null<RpWidget*> Window::body() {
return _helper ? _helper->body() : this;
}
not_null<const RpWidget*> Window::body() const {
return _helper ? _helper->body().get() : this;
}
void Window::setTitle(const QString &title) {
if (_helper) {
_helper->setTitle(title);
} else {
setWindowTitle(title);
}
}
void Window::setSizeMin(QSize size) {
if (_helper) {
_helper->setSizeMin(size);
} else {
setMinimumSize(size);
}
}
} // namespace Ui

32
ui/widgets/window.h Normal file
View file

@ -0,0 +1,32 @@
// 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
//
#pragma once
#include "ui/rp_widget.h"
namespace Ui {
namespace Platform {
class BasicWindowHelper;
} // namespace Platform
class Window : public RpWidget {
public:
explicit Window(QWidget *parent = nullptr);
~Window();
[[nodiscard]] not_null<RpWidget*> body();
[[nodiscard]] not_null<const RpWidget*> body() const;
void setTitle(const QString &title);
void setSizeMin(QSize size);
private:
const std::unique_ptr<Platform::BasicWindowHelper> _helper;
};
} // namespace Ui