Support nice Ui::Window under Windows.
|
|
@ -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
|
||||||
|
|
|
||||||
BIN
icons/title_button_close.png
Normal file
|
After Width: | Height: | Size: 164 B |
BIN
icons/title_button_close@2x.png
Normal file
|
After Width: | Height: | Size: 224 B |
BIN
icons/title_button_close@3x.png
Normal file
|
After Width: | Height: | Size: 376 B |
BIN
icons/title_button_maximize.png
Normal file
|
After Width: | Height: | Size: 119 B |
BIN
icons/title_button_maximize@2x.png
Normal file
|
After Width: | Height: | Size: 144 B |
BIN
icons/title_button_maximize@3x.png
Normal file
|
After Width: | Height: | Size: 184 B |
BIN
icons/title_button_minimize.png
Normal file
|
After Width: | Height: | Size: 100 B |
BIN
icons/title_button_minimize@2x.png
Normal file
|
After Width: | Height: | Size: 122 B |
BIN
icons/title_button_minimize@3x.png
Normal file
|
After Width: | Height: | Size: 149 B |
BIN
icons/title_button_restore.png
Normal file
|
After Width: | Height: | Size: 136 B |
BIN
icons/title_button_restore@2x.png
Normal file
|
After Width: | Height: | Size: 160 B |
BIN
icons/title_button_restore@3x.png
Normal file
|
After Width: | Height: | Size: 209 B |
BIN
icons/window_shadow.png
Normal file
|
After Width: | Height: | Size: 225 B |
BIN
icons/window_shadow@2x.png
Normal file
|
After Width: | Height: | Size: 372 B |
BIN
icons/window_shadow@3x.png
Normal file
|
After Width: | Height: | Size: 593 B |
|
|
@ -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 ¶ms) {
|
const ::Window::SectionShow ¶ms) {
|
||||||
if (_specialLayer) {
|
if (_specialLayer) {
|
||||||
return _specialLayer->showSectionInternal(memento, params);
|
return _specialLayer->showSectionInternal(memento, params);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 ¶ms) {
|
const ::Window::SectionShow ¶ms) {
|
||||||
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 ¶ms);
|
const ::Window::SectionShow ¶ms);
|
||||||
|
|
||||||
bool layerShown() const;
|
bool layerShown() const;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
|
|
@ -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"
|
||||||
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
28
ui/platform/ui_platform_window.h
Normal 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
|
||||||
|
|
@ -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>
|
||||||
|
|
||||||
612
ui/platform/win/ui_window_shadow_win.cpp
Normal 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
|
||||||
110
ui/platform/win/ui_window_shadow_win.h
Normal 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
|
||||||
160
ui/platform/win/ui_window_title_win.cpp
Normal 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
|
||||||
68
ui/platform/win/ui_window_title_win.h
Normal 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
|
||||||
466
ui/platform/win/ui_window_win.cpp
Normal 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)
|
||||||
|
? ¶ms->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
|
||||||
58
ui/platform/win/ui_window_win.h
Normal 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
|
||||||
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||||