init
Some checks failed
Docker. / Ubuntu (push) Has been cancelled
User-agent updater. / User-agent (push) Failing after 15s
Lock Threads / lock (push) Failing after 10s
Waiting for answer. / waiting-for-answer (push) Failing after 22s
Needs user action. / needs-user-action (push) Failing after 8s
Can't reproduce. / cant-reproduce (push) Failing after 8s
Close stale issues and PRs / stale (push) Has been cancelled
Some checks failed
Docker. / Ubuntu (push) Has been cancelled
User-agent updater. / User-agent (push) Failing after 15s
Lock Threads / lock (push) Failing after 10s
Waiting for answer. / waiting-for-answer (push) Failing after 22s
Needs user action. / needs-user-action (push) Failing after 8s
Can't reproduce. / cant-reproduce (push) Failing after 8s
Close stale issues and PRs / stale (push) Has been cancelled
This commit is contained in:
184
Telegram/lib_ui/ui/platform/win/ui_utility_win.cpp
Normal file
184
Telegram/lib_ui/ui/platform/win/ui_utility_win.cpp
Normal file
@@ -0,0 +1,184 @@
|
||||
// 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_utility_win.h"
|
||||
|
||||
#include "ui/widgets/popup_menu.h"
|
||||
|
||||
#include <QtWidgets/QApplication>
|
||||
#include <QtGui/QWindow>
|
||||
#include <QtCore/QAbstractNativeEventFilter>
|
||||
|
||||
#include <windows.h>
|
||||
#include <wrl/client.h>
|
||||
#include <Shobjidl.h>
|
||||
|
||||
#include "base/event_filter.h"
|
||||
#include <QTimer>
|
||||
#include <QScreen>
|
||||
|
||||
using namespace Microsoft::WRL;
|
||||
|
||||
namespace Ui::Platform {
|
||||
|
||||
bool IsApplicationActive() {
|
||||
return QApplication::activeWindow() != nullptr;
|
||||
}
|
||||
|
||||
void IgnoreAllActivation(not_null<QWidget*> widget) {
|
||||
widget->createWinId();
|
||||
|
||||
const auto handle = reinterpret_cast<HWND>(widget->winId());
|
||||
Assert(handle != nullptr);
|
||||
|
||||
ShowWindow(handle, SW_HIDE);
|
||||
const auto style = GetWindowLongPtr(handle, GWL_EXSTYLE);
|
||||
SetWindowLongPtr(
|
||||
handle,
|
||||
GWL_EXSTYLE,
|
||||
style | WS_EX_NOACTIVATE | WS_EX_APPWINDOW);
|
||||
ShowWindow(handle, SW_SHOW);
|
||||
}
|
||||
|
||||
std::optional<bool> IsOverlapped(
|
||||
not_null<QWidget*> widget,
|
||||
const QRect &rect) {
|
||||
const auto handle = HWND(widget->winId());
|
||||
Expects(handle != nullptr);
|
||||
|
||||
ComPtr<IVirtualDesktopManager> virtualDesktopManager;
|
||||
HRESULT hr = CoCreateInstance(
|
||||
CLSID_VirtualDesktopManager,
|
||||
nullptr,
|
||||
CLSCTX_ALL,
|
||||
IID_PPV_ARGS(&virtualDesktopManager));
|
||||
|
||||
if (SUCCEEDED(hr)) {
|
||||
BOOL isCurrent;
|
||||
hr = virtualDesktopManager->IsWindowOnCurrentVirtualDesktop(
|
||||
handle,
|
||||
&isCurrent);
|
||||
if (SUCCEEDED(hr) && !isCurrent) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
const auto nativeRect = [&] {
|
||||
const auto topLeft = [&] {
|
||||
const auto qpoints = rect.topLeft()
|
||||
* widget->windowHandle()->devicePixelRatio();
|
||||
POINT result{
|
||||
qpoints.x(),
|
||||
qpoints.y(),
|
||||
};
|
||||
ClientToScreen(handle, &result);
|
||||
return result;
|
||||
}();
|
||||
const auto bottomRight = [&] {
|
||||
const auto qpoints = rect.bottomRight()
|
||||
* widget->windowHandle()->devicePixelRatio();
|
||||
POINT result{
|
||||
qpoints.x(),
|
||||
qpoints.y(),
|
||||
};
|
||||
ClientToScreen(handle, &result);
|
||||
return result;
|
||||
}();
|
||||
return RECT{
|
||||
topLeft.x,
|
||||
topLeft.y,
|
||||
bottomRight.x,
|
||||
bottomRight.y,
|
||||
};
|
||||
}();
|
||||
|
||||
std::vector<HWND> visited;
|
||||
for (auto curHandle = handle;
|
||||
curHandle != nullptr && !ranges::contains(visited, curHandle);
|
||||
curHandle = GetWindow(curHandle, GW_HWNDPREV)) {
|
||||
visited.push_back(curHandle);
|
||||
if (curHandle == handle) {
|
||||
continue;
|
||||
}
|
||||
RECT testRect, intersection;
|
||||
if (IsWindowVisible(curHandle)
|
||||
&& GetWindowRect(curHandle, &testRect)
|
||||
&& IntersectRect(&intersection, &nativeRect, &testRect)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void ShowWindowMenu(not_null<QWidget*> widget, const QPoint &point) {
|
||||
const auto handle = HWND(widget->winId());
|
||||
const auto mapped = point * widget->windowHandle()->devicePixelRatio();
|
||||
POINT p{ mapped.x(), mapped.y() };
|
||||
ClientToScreen(handle, &p);
|
||||
SendMessage(
|
||||
handle,
|
||||
0x313 /* WM_POPUPSYSTEMMENU */,
|
||||
0,
|
||||
MAKELPARAM(p.x, p.y));
|
||||
}
|
||||
|
||||
void FixPopupMenuNativeEmojiPopup(not_null<PopupMenu*> menu) {
|
||||
// Windows native emoji selector, that can be called by Win+. shortcut,
|
||||
// is behaving strangely within an input field in a popup menu.
|
||||
//
|
||||
// When the selector is shown and a mouse button is pressed the system
|
||||
// sends two events "MousePress + MouseRelease" to the popup menu, even
|
||||
// before the button is physically released. That way we hide the menu
|
||||
// on this MousePress, that we shouldn't have received (in case of
|
||||
// input field in the main window no such events are sent at all).
|
||||
//
|
||||
// To workaround this we detect a WM_MOUSELEAVE event that is sent to
|
||||
// the popup menu when the selector is shown and skip all mouse press
|
||||
// events while we don't receive mouse move events. If we receive mouse
|
||||
// move events that means the selector was hidden and the mouse is
|
||||
// captured by the popup menu again.
|
||||
class Filter final : public QAbstractNativeEventFilter {
|
||||
public:
|
||||
explicit Filter(not_null<PopupMenu*> menu) : _menu(menu) {
|
||||
}
|
||||
|
||||
bool nativeEventFilter(
|
||||
const QByteArray &eventType,
|
||||
void *message,
|
||||
native_event_filter_result *result) override {
|
||||
const auto msg = static_cast<MSG*>(message);
|
||||
switch (msg->message) {
|
||||
case WM_MOUSELEAVE: if (msg->hwnd == hwnd()) {
|
||||
_skipMouseDown = true;
|
||||
} break;
|
||||
case WM_MOUSEMOVE: if (msg->hwnd == hwnd()) {
|
||||
_skipMouseDown = false;
|
||||
} break;
|
||||
case WM_LBUTTONDOWN:
|
||||
case WM_LBUTTONDBLCLK: if (msg->hwnd == hwnd()) {
|
||||
return _skipMouseDown;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private:
|
||||
[[nodiscard]] HWND hwnd() const {
|
||||
const auto top = _menu->window()->windowHandle();
|
||||
return top ? reinterpret_cast<HWND>(top->winId()) : nullptr;
|
||||
}
|
||||
|
||||
not_null<PopupMenu*> _menu;
|
||||
bool _skipMouseDown = false;
|
||||
|
||||
};
|
||||
|
||||
QGuiApplication::instance()->installNativeEventFilter(
|
||||
menu->lifetime().make_state<Filter>(menu));
|
||||
}
|
||||
|
||||
} // namespace Ui::Platform
|
||||
56
Telegram/lib_ui/ui/platform/win/ui_utility_win.h
Normal file
56
Telegram/lib_ui/ui/platform/win/ui_utility_win.h
Normal file
@@ -0,0 +1,56 @@
|
||||
// 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_utility.h"
|
||||
|
||||
#include <QtCore/QPoint>
|
||||
|
||||
class QPainter;
|
||||
class QPaintEvent;
|
||||
|
||||
namespace Ui {
|
||||
namespace Platform {
|
||||
|
||||
inline bool TranslucentWindowsSupported() {
|
||||
return true;
|
||||
}
|
||||
|
||||
inline void InitOnTopPanel(not_null<QWidget*> panel) {
|
||||
}
|
||||
|
||||
inline void DeInitOnTopPanel(not_null<QWidget*> panel) {
|
||||
}
|
||||
|
||||
inline void ReInitOnTopPanel(not_null<QWidget*> panel) {
|
||||
}
|
||||
|
||||
inline void ShowOverAll(not_null<QWidget*> widget, bool canFocus) {
|
||||
}
|
||||
|
||||
inline void AcceptAllMouseInput(not_null<QWidget*> widget) {
|
||||
}
|
||||
|
||||
inline void ClearTransientParent(not_null<QWidget*> widget) {
|
||||
}
|
||||
|
||||
inline void DisableSystemWindowResize(not_null<QWidget*> widget, QSize ratio) {
|
||||
}
|
||||
|
||||
inline constexpr bool UseMainQueueGeneric() {
|
||||
return true;
|
||||
}
|
||||
|
||||
inline bool WindowMarginsSupported() {
|
||||
return false;
|
||||
}
|
||||
|
||||
inline void SetWindowMargins(not_null<QWidget*> widget, const QMargins &margins) {
|
||||
}
|
||||
|
||||
} // namespace Platform
|
||||
} // namespace Ui
|
||||
643
Telegram/lib_ui/ui/platform/win/ui_window_shadow_win.cpp
Normal file
643
Telegram/lib_ui/ui/platform/win/ui_window_shadow_win.cpp
Normal file
@@ -0,0 +1,643 @@
|
||||
// This file is part of Desktop App Toolkit,
|
||||
// a set of libraries for developing nice desktop applications.
|
||||
//
|
||||
// For license and copyright information please follow this link:
|
||||
// https://github.com/desktop-app/legal/blob/master/LEGAL
|
||||
//
|
||||
#include "ui/platform/win/ui_window_shadow_win.h"
|
||||
|
||||
#include "ui/rp_widget.h"
|
||||
#include "ui/platform/win/ui_window_win.h"
|
||||
#include "base/platform/base_platform_info.h"
|
||||
#include "styles/style_widgets.h"
|
||||
|
||||
#include <QtGui/QPainter>
|
||||
#include <QtGui/QScreen>
|
||||
#include <QtWidgets/QApplication>
|
||||
|
||||
#include <windowsx.h>
|
||||
|
||||
// WM_POINTER support from Windows 8 onwards (WINVER >= 0x0602)
|
||||
#ifndef WM_POINTERUPDATE
|
||||
# define WM_NCPOINTERUPDATE 0x0241
|
||||
# define WM_NCPOINTERDOWN 0x0242
|
||||
# define WM_NCPOINTERUP 0x0243
|
||||
# define WM_POINTERUPDATE 0x0245
|
||||
# define WM_POINTERDOWN 0x0246
|
||||
# define WM_POINTERUP 0x0247
|
||||
# define WM_POINTERENTER 0x0249
|
||||
# define WM_POINTERLEAVE 0x024A
|
||||
# define WM_POINTERACTIVATE 0x024B
|
||||
# define WM_POINTERCAPTURECHANGED 0x024C
|
||||
# define WM_POINTERWHEEL 0x024E
|
||||
# define WM_POINTERHWHEEL 0x024F
|
||||
#endif // WM_POINTERUPDATE
|
||||
|
||||
namespace Ui {
|
||||
namespace Platform {
|
||||
namespace {
|
||||
|
||||
base::flat_map<HWND, not_null<WindowShadow*>> ShadowByHandle;
|
||||
|
||||
} // namespace
|
||||
|
||||
WindowShadow::WindowShadow(not_null<RpWidget*> window, QColor color)
|
||||
: _window(window) {
|
||||
setColor(color);
|
||||
|
||||
window->winIdValue(
|
||||
) | rpl::on_next([=](WId id) {
|
||||
destroy();
|
||||
_handle = reinterpret_cast<HWND>(id);
|
||||
create();
|
||||
}, window->lifetime());
|
||||
}
|
||||
|
||||
WindowShadow::~WindowShadow() {
|
||||
destroy();
|
||||
}
|
||||
|
||||
void WindowShadow::setColor(QColor value) {
|
||||
_r = value.red();
|
||||
_g = value.green();
|
||||
_b = value.blue();
|
||||
if (working()) {
|
||||
updateColor();
|
||||
}
|
||||
}
|
||||
|
||||
void WindowShadow::updateColor() {
|
||||
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::create() {
|
||||
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::primaryScreen()->availableGeometry();
|
||||
_widthMax = std::max(avail.width(), 1);
|
||||
_heightMax = std::max(avail.height(), 1);
|
||||
|
||||
static const auto instance = (HINSTANCE)GetModuleHandle(nullptr);
|
||||
static const auto className = u"WindowShadow"_q;
|
||||
static const auto wcharClassName = className.toStdWString();
|
||||
static const auto registered = [] {
|
||||
auto wc = WNDCLASSEX();
|
||||
wc.cbSize = sizeof(wc);
|
||||
wc.style = 0;
|
||||
wc.lpfnWndProc = WindowCallback;
|
||||
wc.cbClsExtra = 0;
|
||||
wc.cbWndExtra = 0;
|
||||
wc.hInstance = instance;
|
||||
wc.hIcon = 0;
|
||||
wc.hCursor = 0;
|
||||
wc.hbrBackground = 0;
|
||||
wc.lpszMenuName = NULL;
|
||||
wc.lpszClassName = wcharClassName.c_str();
|
||||
wc.hIconSm = 0;
|
||||
return RegisterClassEx(&wc) ? true : false;
|
||||
}();
|
||||
if (!registered) {
|
||||
return;
|
||||
}
|
||||
for (auto i = 0; i != 4; ++i) {
|
||||
_handles[i] = CreateWindowEx(
|
||||
WS_EX_LAYERED | WS_EX_TOOLWINDOW,
|
||||
wcharClassName.c_str(),
|
||||
0,
|
||||
WS_POPUP,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
instance,
|
||||
0);
|
||||
if (!_handles[i]) {
|
||||
destroy();
|
||||
return;
|
||||
}
|
||||
ShadowByHandle.emplace(_handles[i], this);
|
||||
SetWindowLongPtr(_handles[i], GWLP_HWNDPARENT, (LONG_PTR)_handle);
|
||||
|
||||
_contexts[i] = CreateCompatibleDC(_screenContext);
|
||||
if (!_contexts[i]) {
|
||||
destroy();
|
||||
return;
|
||||
}
|
||||
|
||||
const auto width = (i % 2) ? _size : _widthMax;
|
||||
const auto height = (i % 2) ? _heightMax : _size;
|
||||
_bitmaps[i] = CreateCompatibleBitmap(_screenContext, width, height);
|
||||
if (!_bitmaps[i]) {
|
||||
return;
|
||||
}
|
||||
|
||||
SelectObject(_contexts[i], _bitmaps[i]);
|
||||
}
|
||||
updateColor();
|
||||
}
|
||||
|
||||
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(::Platform::IsWindows11OrGreater() ? 1 : alpha), _r, _g, _b);
|
||||
}
|
||||
|
||||
Gdiplus::SolidBrush WindowShadow::getBrush(uchar alpha) const {
|
||||
return Gdiplus::SolidBrush(getColor(alpha));
|
||||
}
|
||||
|
||||
Gdiplus::Pen WindowShadow::getPen(uchar alpha) const {
|
||||
return Gdiplus::Pen(getColor(alpha));
|
||||
}
|
||||
|
||||
void WindowShadow::update(Changes changes, WINDOWPOS *pos) {
|
||||
if (!working()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (changes == Changes(Change::Activate)) {
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
SetWindowPos(_handles[i], _handle, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (changes & Change::Hidden) {
|
||||
if (!_hidden) {
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
_hidden = true;
|
||||
ShowWindow(_handles[i], SW_HIDE);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (_window->isHidden()) {
|
||||
return;
|
||||
}
|
||||
|
||||
int x = _x, y = _y, w = _w, h = _h;
|
||||
if (pos && (!(pos->flags & SWP_NOMOVE) || !(pos->flags & SWP_NOSIZE) || !(pos->flags & SWP_NOREPOSITION))) {
|
||||
if (!(pos->flags & SWP_NOMOVE)) {
|
||||
x = pos->x - _size;
|
||||
y = pos->y - _size;
|
||||
} else if (pos->flags & SWP_NOSIZE) {
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
SetWindowPos(_handles[i], _handle, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (!(pos->flags & SWP_NOSIZE)) {
|
||||
w = pos->cx + 2 * _size;
|
||||
h = pos->cy + 2 * _size;
|
||||
}
|
||||
} else {
|
||||
RECT r;
|
||||
GetWindowRect(_handle, &r);
|
||||
x = r.left - _size;
|
||||
y = r.top - _size;
|
||||
w = r.right + _size - x;
|
||||
h = r.bottom + _size - y;
|
||||
}
|
||||
if (h < 2 * _fullsize + 2 * _shift) {
|
||||
h = 2 * _fullsize + 2 * _shift;
|
||||
}
|
||||
if (w < 2 * (_fullsize + _shift)) {
|
||||
w = 2 * (_fullsize + _shift);
|
||||
}
|
||||
|
||||
if (w != _w) {
|
||||
int from = (_w > 2 * (_fullsize + _shift)) ? (_w - _size - _fullsize - _shift) : (_fullsize - (_size - _shift));
|
||||
int to = w - _size - _fullsize - _shift;
|
||||
if (w > _widthMax) {
|
||||
from = _fullsize - (_size - _shift);
|
||||
do {
|
||||
_widthMax *= 2;
|
||||
} while (w > _widthMax);
|
||||
for (int i = 0; i < 4; i += 2) {
|
||||
DeleteObject(_bitmaps[i]);
|
||||
_bitmaps[i] = CreateCompatibleBitmap(_screenContext, _widthMax, _size);
|
||||
SelectObject(_contexts[i], _bitmaps[i]);
|
||||
}
|
||||
initCorners(Direction::Horizontal);
|
||||
}
|
||||
Gdiplus::Graphics graphics0(_contexts[0]);
|
||||
Gdiplus::Graphics graphics2(_contexts[2]);
|
||||
graphics0.SetCompositingMode(Gdiplus::CompositingModeSourceCopy);
|
||||
graphics2.SetCompositingMode(Gdiplus::CompositingModeSourceCopy);
|
||||
auto brush = getBrush(_alphas[0]);
|
||||
if (to > from) {
|
||||
graphics0.FillRectangle(&brush, from, 0, to - from, 2 * _shift);
|
||||
for (int i = 2 * _shift; i < _size; ++i) {
|
||||
auto pen = getPen(_alphas[i]);
|
||||
graphics0.DrawLine(&pen, from, i, to, i);
|
||||
graphics2.DrawLine(&pen, from, _size - (i - 2 * _shift) - 1, to, _size - (i - 2 * _shift) - 1);
|
||||
}
|
||||
for (int i = _size; i < _size + 2 * _shift; ++i) {
|
||||
auto pen = getPen(_alphas[i]);
|
||||
graphics2.DrawLine(&pen, from, _size - (i - 2 * _shift) - 1, to, _size - (i - 2 * _shift) - 1);
|
||||
}
|
||||
}
|
||||
if (_w > w) {
|
||||
graphics0.FillRectangle(&brush, w - _size - _fullsize - _shift, 0, _fullsize - (_size - _shift), _size);
|
||||
graphics2.FillRectangle(&brush, w - _size - _fullsize - _shift, 0, _fullsize - (_size - _shift), _size);
|
||||
}
|
||||
horCorners(w, &graphics0, &graphics2);
|
||||
POINT p0 = { x + _size, y }, p2 = { x + _size, y + h - _size }, f = { 0, 0 };
|
||||
SIZE s = { w - 2 * _size, _size };
|
||||
updateWindow(0, &p0, &s);
|
||||
updateWindow(2, &p2, &s);
|
||||
} else if (x != _x || y != _y) {
|
||||
POINT p0 = { x + _size, y }, p2 = { x + _size, y + h - _size };
|
||||
updateWindow(0, &p0);
|
||||
updateWindow(2, &p2);
|
||||
} else if (h != _h) {
|
||||
POINT p2 = { x + _size, y + h - _size };
|
||||
updateWindow(2, &p2);
|
||||
}
|
||||
|
||||
if (h != _h) {
|
||||
int from = (_h > 2 * _fullsize + 2 * _shift) ? (_h - _fullsize) : (_fullsize + 2 * _shift);
|
||||
int to = h - _fullsize;
|
||||
if (h > _heightMax) {
|
||||
from = (_fullsize + 2 * _shift);
|
||||
do {
|
||||
_heightMax *= 2;
|
||||
} while (h > _heightMax);
|
||||
for (int i = 1; i < 4; i += 2) {
|
||||
DeleteObject(_bitmaps[i]);
|
||||
_bitmaps[i] = CreateCompatibleBitmap(_contexts[i], _size, _heightMax);
|
||||
SelectObject(_contexts[i], _bitmaps[i]);
|
||||
}
|
||||
initCorners(Direction::Vertical);
|
||||
}
|
||||
Gdiplus::Graphics graphics1(_contexts[1]), graphics3(_contexts[3]);
|
||||
graphics1.SetCompositingMode(Gdiplus::CompositingModeSourceCopy);
|
||||
graphics3.SetCompositingMode(Gdiplus::CompositingModeSourceCopy);
|
||||
|
||||
auto brush = getBrush(_alphas[0]);
|
||||
if (to > from) {
|
||||
graphics1.FillRectangle(&brush, _size - _shift, from, _shift, to - from);
|
||||
graphics3.FillRectangle(&brush, 0, from, _shift, to - from);
|
||||
for (int i = 2 * _shift; i < _size + _shift; ++i) {
|
||||
auto pen = getPen(_alphas[i]);
|
||||
graphics1.DrawLine(&pen, _size + _shift - i - 1, from, _size + _shift - i - 1, to);
|
||||
graphics3.DrawLine(&pen, i - _shift, from, i - _shift, to);
|
||||
}
|
||||
}
|
||||
if (_h > h) {
|
||||
graphics1.FillRectangle(&brush, 0, h - _fullsize, _size, _fullsize);
|
||||
graphics3.FillRectangle(&brush, 0, h - _fullsize, _size, _fullsize);
|
||||
}
|
||||
verCorners(h, &graphics1, &graphics3);
|
||||
|
||||
POINT p1 = { x + w - _size, y }, p3 = { x, y }, f = { 0, 0 };
|
||||
SIZE s = { _size, h };
|
||||
updateWindow(1, &p1, &s);
|
||||
updateWindow(3, &p3, &s);
|
||||
} else if (x != _x || y != _y) {
|
||||
POINT p1 = { x + w - _size, y }, p3 = { x, y };
|
||||
updateWindow(1, &p1);
|
||||
updateWindow(3, &p3);
|
||||
} else if (w != _w) {
|
||||
POINT p1 = { x + w - _size, y };
|
||||
updateWindow(1, &p1);
|
||||
}
|
||||
_x = x;
|
||||
_y = y;
|
||||
_w = w;
|
||||
_h = h;
|
||||
|
||||
if (_hidden && (changes & Change::Shown)) {
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
SetWindowPos(
|
||||
_handles[i],
|
||||
_handle,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW | SWP_NOACTIVATE);
|
||||
}
|
||||
_hidden = false;
|
||||
}
|
||||
}
|
||||
|
||||
void WindowShadow::initBlend() {
|
||||
_blend.AlphaFormat = AC_SRC_ALPHA;
|
||||
_blend.SourceConstantAlpha = 255;
|
||||
_blend.BlendFlags = 0;
|
||||
_blend.BlendOp = AC_SRC_OVER;
|
||||
}
|
||||
|
||||
void WindowShadow::updateWindow(int i, POINT *p, SIZE *s) {
|
||||
static POINT f = { 0, 0 };
|
||||
if (s) {
|
||||
UpdateLayeredWindow(
|
||||
_handles[i],
|
||||
(s ? _screenContext : nullptr),
|
||||
p,
|
||||
s,
|
||||
(s ? _contexts[i] : nullptr),
|
||||
(s ? (&f) : nullptr),
|
||||
_noKeyColor,
|
||||
&_blend,
|
||||
ULW_ALPHA);
|
||||
} else {
|
||||
SetWindowPos(
|
||||
_handles[i],
|
||||
0,
|
||||
p->x,
|
||||
p->y,
|
||||
0,
|
||||
0,
|
||||
SWP_NOACTIVATE | SWP_NOSIZE | SWP_NOZORDER);
|
||||
}
|
||||
}
|
||||
|
||||
void WindowShadow::setResizeEnabled(bool enabled) {
|
||||
_resizeEnabled = enabled;
|
||||
}
|
||||
|
||||
LRESULT CALLBACK WindowShadow::WindowCallback(
|
||||
HWND hwnd,
|
||||
UINT msg,
|
||||
WPARAM wParam,
|
||||
LPARAM lParam) {
|
||||
const auto i = ShadowByHandle.find(hwnd);
|
||||
return (i != end(ShadowByHandle))
|
||||
? i->second->windowCallback(hwnd, msg, wParam, lParam)
|
||||
: DefWindowProc(hwnd, msg, wParam, lParam);
|
||||
}
|
||||
|
||||
LRESULT WindowShadow::windowCallback(
|
||||
HWND hwnd,
|
||||
UINT msg,
|
||||
WPARAM wParam,
|
||||
LPARAM lParam) {
|
||||
if (!working()) {
|
||||
return DefWindowProc(hwnd, msg, wParam, lParam);
|
||||
}
|
||||
|
||||
switch (msg) {
|
||||
case WM_CLOSE:
|
||||
_window->close();
|
||||
return 0;
|
||||
|
||||
case WM_NCHITTEST: {
|
||||
if (!_resizeEnabled) {
|
||||
return HTNOWHERE;
|
||||
}
|
||||
const auto 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
|
||||
107
Telegram/lib_ui/ui/platform/win/ui_window_shadow_win.h
Normal file
107
Telegram/lib_ui/ui/platform/win/ui_window_shadow_win.h
Normal file
@@ -0,0 +1,107 @@
|
||||
// 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/platform/win/base_windows_rpcndr_h.h"
|
||||
#include "base/platform/win/base_windows_gdiplus_h.h"
|
||||
#include "base/flags.h"
|
||||
|
||||
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);
|
||||
void setResizeEnabled(bool enabled);
|
||||
|
||||
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 create();
|
||||
void initBlend();
|
||||
void updateColor();
|
||||
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;
|
||||
HWND _handle = nullptr;
|
||||
|
||||
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;
|
||||
bool _resizeEnabled = 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
|
||||
262
Telegram/lib_ui/ui/platform/win/ui_window_title_win.cpp
Normal file
262
Telegram/lib_ui/ui/platform/win/ui_window_title_win.cpp
Normal file
@@ -0,0 +1,262 @@
|
||||
// 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/platform/win/ui_window_win.h"
|
||||
#include "ui/widgets/buttons.h"
|
||||
#include "ui/widgets/shadow.h"
|
||||
#include "ui/widgets/rp_window.h"
|
||||
#include "ui/ui_utility.h"
|
||||
#include "base/platform/base_platform_info.h"
|
||||
#include "base/platform/win/base_windows_safe_library.h"
|
||||
#include "base/debug_log.h"
|
||||
#include "styles/style_widgets.h"
|
||||
#include "styles/palette.h"
|
||||
|
||||
#include <QtGui/QPainter>
|
||||
#include <QtGui/QtEvents>
|
||||
#include <QtGui/QWindow>
|
||||
|
||||
#include <windows.h>
|
||||
#include <shellscalingapi.h>
|
||||
|
||||
namespace Ui {
|
||||
namespace Platform {
|
||||
namespace {
|
||||
|
||||
HRESULT(__stdcall *GetScaleFactorForMonitor)(
|
||||
_In_ HMONITOR hMon,
|
||||
_Out_ DEVICE_SCALE_FACTOR *pScale);
|
||||
|
||||
[[nodiscard]] bool ScaleQuerySupported() {
|
||||
static const auto Result = [&] {
|
||||
#define LOAD_SYMBOL(lib, name) base::Platform::LoadMethod(lib, #name, name)
|
||||
const auto shcore = base::Platform::SafeLoadLibrary(L"Shcore.dll");
|
||||
return LOAD_SYMBOL(shcore, GetScaleFactorForMonitor);
|
||||
#undef LOAD_SYMBOL
|
||||
}();
|
||||
return Result;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
std::shared_ptr<TitleControlsLayout> TitleControlsLayout::Create() {
|
||||
return std::shared_ptr<TitleControlsLayout>(new TitleControlsLayout({
|
||||
.right = {
|
||||
TitleControls::Control::Minimize,
|
||||
TitleControls::Control::Maximize,
|
||||
TitleControls::Control::Close,
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
struct TitleWidget::PaddingHelper {
|
||||
explicit PaddingHelper(QWidget *parent) : controlsParent(parent) {
|
||||
}
|
||||
|
||||
RpWidget controlsParent;
|
||||
rpl::variable<int> padding = 0;
|
||||
};
|
||||
|
||||
TitleWidget::TitleWidget(not_null<RpWidget*> parent)
|
||||
: RpWidget(parent)
|
||||
, _paddingHelper(CheckTitlePaddingRequired()
|
||||
? std::make_unique<PaddingHelper>(this)
|
||||
: nullptr)
|
||||
, _controls(
|
||||
_paddingHelper ? &_paddingHelper->controlsParent : this,
|
||||
st::defaultWindowTitle)
|
||||
, _shadow(this, st::titleShadow) {
|
||||
setAttribute(Qt::WA_OpaquePaintEvent);
|
||||
|
||||
parent->widthValue(
|
||||
) | rpl::on_next([=](int width) {
|
||||
refreshGeometryWithWidth(width);
|
||||
}, lifetime());
|
||||
}
|
||||
|
||||
void TitleWidget::initInWindow(not_null<RpWindow*> window) {
|
||||
window->hitTestRequests(
|
||||
) | rpl::filter([=](not_null<HitTestRequest*> request) {
|
||||
return !isHidden() && geometry().contains(request->point);
|
||||
}) | rpl::on_next([=](not_null<HitTestRequest*> request) {
|
||||
request->result = hitTest(request->point);
|
||||
}, lifetime());
|
||||
|
||||
SetupSemiNativeSystemButtons(&_controls, window, lifetime(), [=] {
|
||||
return !isHidden() && (_controls.st()->height > 0);
|
||||
});
|
||||
}
|
||||
|
||||
TitleWidget::~TitleWidget() = default;
|
||||
|
||||
void TitleWidget::setText(const QString &text) {
|
||||
window()->setWindowTitle(text);
|
||||
}
|
||||
|
||||
void TitleWidget::setStyle(const style::WindowTitle &st) {
|
||||
_controls.setStyle(st);
|
||||
if (!st.shadow) {
|
||||
_shadow.destroy();
|
||||
} else if (!_shadow) {
|
||||
_shadow.create(this, st::titleShadow);
|
||||
updateShadowGeometry();
|
||||
}
|
||||
refreshGeometryWithWidth(window()->width());
|
||||
}
|
||||
|
||||
void TitleWidget::updateShadowGeometry() {
|
||||
const auto thickness = st::lineWidth;
|
||||
_shadow->setGeometry(0, height() - thickness, width(), thickness);
|
||||
}
|
||||
|
||||
void TitleWidget::refreshGeometryWithWidth(int width) {
|
||||
const auto add = additionalPadding();
|
||||
setGeometry(0, 0, width, _controls.st()->height + add);
|
||||
if (_paddingHelper) {
|
||||
_paddingHelper->controlsParent.setGeometry(
|
||||
0,
|
||||
add,
|
||||
width,
|
||||
_controls.st()->height);
|
||||
}
|
||||
update();
|
||||
}
|
||||
|
||||
not_null<const style::WindowTitle*> TitleWidget::st() const {
|
||||
return _controls.st();
|
||||
}
|
||||
|
||||
void TitleWidget::setResizeEnabled(bool enabled) {
|
||||
_controls.setResizeEnabled(enabled);
|
||||
}
|
||||
|
||||
void TitleWidget::paintEvent(QPaintEvent *e) {
|
||||
const auto active = window()->isActiveWindow();
|
||||
QPainter(this).fillRect(
|
||||
e->rect(),
|
||||
active ? _controls.st()->bgActive : _controls.st()->bg);
|
||||
}
|
||||
|
||||
void TitleWidget::resizeEvent(QResizeEvent *e) {
|
||||
if (_shadow) {
|
||||
updateShadowGeometry();
|
||||
}
|
||||
}
|
||||
|
||||
HitTestResult TitleWidget::hitTest(QPoint point) const {
|
||||
const auto controlsResult = _controls.hitTest(point);
|
||||
return (controlsResult != HitTestResult::None)
|
||||
? controlsResult
|
||||
: HitTestResult::Caption;
|
||||
}
|
||||
|
||||
bool TitleWidget::additionalPaddingRequired() const {
|
||||
return _paddingHelper && !isHidden();
|
||||
}
|
||||
|
||||
void TitleWidget::refreshAdditionalPaddings() {
|
||||
if (!additionalPaddingRequired()) {
|
||||
return;
|
||||
} else if (const auto handle = GetCurrentHandle(this)) {
|
||||
refreshAdditionalPaddings(handle);
|
||||
}
|
||||
}
|
||||
|
||||
void TitleWidget::refreshAdditionalPaddings(HWND handle) {
|
||||
if (!additionalPaddingRequired()) {
|
||||
return;
|
||||
}
|
||||
auto placement = WINDOWPLACEMENT{
|
||||
.length = sizeof(WINDOWPLACEMENT),
|
||||
};
|
||||
if (!GetWindowPlacement(handle, &placement)) {
|
||||
LOG(("System Error: GetWindowPlacement failed."));
|
||||
return;
|
||||
}
|
||||
refreshAdditionalPaddings(handle, placement);
|
||||
}
|
||||
|
||||
void TitleWidget::refreshAdditionalPaddings(
|
||||
HWND handle,
|
||||
const WINDOWPLACEMENT &placement) {
|
||||
if (!additionalPaddingRequired()) {
|
||||
return;
|
||||
}
|
||||
auto geometry = RECT();
|
||||
if (!GetWindowRect(handle, &geometry)) {
|
||||
LOG(("System Error: GetWindowRect failed."));
|
||||
return;
|
||||
}
|
||||
const auto normal = placement.rcNormalPosition;
|
||||
const auto rounded = (normal.left == geometry.left)
|
||||
&& (normal.right == geometry.right)
|
||||
&& (normal.top == geometry.top)
|
||||
&& (normal.bottom == geometry.bottom);
|
||||
const auto padding = [&] {
|
||||
if (!rounded) {
|
||||
return 0;
|
||||
}
|
||||
const auto monitor = MonitorFromWindow(
|
||||
handle,
|
||||
MONITOR_DEFAULTTONEAREST);
|
||||
if (!monitor) {
|
||||
LOG(("System Error: MonitorFromWindow failed."));
|
||||
return -1;
|
||||
}
|
||||
auto factor = DEVICE_SCALE_FACTOR();
|
||||
if (!SUCCEEDED(GetScaleFactorForMonitor(monitor, &factor))) {
|
||||
LOG(("System Error: GetScaleFactorForMonitor failed."));
|
||||
return -1;
|
||||
} else if (factor < 100 || factor > 500) {
|
||||
LOG(("System Error: Bad scale factor %1.").arg(int(factor)));
|
||||
return -1;
|
||||
}
|
||||
const auto pixels = (factor + 50) / 100;
|
||||
return int(base::SafeRound(
|
||||
pixels / window()->windowHandle()->devicePixelRatio()));
|
||||
}();
|
||||
if (padding < 0) {
|
||||
return;
|
||||
}
|
||||
setAdditionalPadding(padding);
|
||||
}
|
||||
|
||||
int TitleWidget::additionalPadding() const {
|
||||
return _paddingHelper ? _paddingHelper->padding.current() : 0;
|
||||
}
|
||||
|
||||
rpl::producer<int> TitleWidget::additionalPaddingValue() const {
|
||||
return _paddingHelper ? _paddingHelper->padding.value() : rpl::single(0);
|
||||
}
|
||||
|
||||
void TitleWidget::setAdditionalPadding(int padding) {
|
||||
Expects(_paddingHelper != nullptr);
|
||||
|
||||
padding /= window()->devicePixelRatio();
|
||||
if (_paddingHelper->padding.current() == padding) {
|
||||
return;
|
||||
}
|
||||
_paddingHelper->padding = padding;
|
||||
refreshGeometryWithWidth(window()->width());
|
||||
}
|
||||
|
||||
void TitleWidget::setVisibleHook(bool visible) {
|
||||
RpWidget::setVisibleHook(visible);
|
||||
if (additionalPaddingRequired()) {
|
||||
PostponeCall(this, [=] {
|
||||
refreshAdditionalPaddings();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
bool CheckTitlePaddingRequired() {
|
||||
return ::Platform::IsWindows11OrGreater() && ScaleQuerySupported();
|
||||
}
|
||||
|
||||
} // namespace Platform
|
||||
} // namespace Ui
|
||||
73
Telegram/lib_ui/ui/platform/win/ui_window_title_win.h
Normal file
73
Telegram/lib_ui/ui/platform/win/ui_window_title_win.h
Normal file
@@ -0,0 +1,73 @@
|
||||
// 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_title.h"
|
||||
#include "ui/rp_widget.h"
|
||||
#include "base/object_ptr.h"
|
||||
|
||||
#include <QtCore/QRect>
|
||||
#include <QtCore/QPoint>
|
||||
|
||||
#include <Windows.h> // HWND, WINDOWPLACEMENT
|
||||
|
||||
namespace style {
|
||||
struct WindowTitle;
|
||||
} // namespace style
|
||||
|
||||
namespace Ui {
|
||||
|
||||
class RpWindow;
|
||||
class IconButton;
|
||||
class PlainShadow;
|
||||
|
||||
namespace Platform {
|
||||
|
||||
class TitleWidget : public RpWidget {
|
||||
public:
|
||||
explicit TitleWidget(not_null<RpWidget*> parent);
|
||||
~TitleWidget();
|
||||
|
||||
void initInWindow(not_null<RpWindow*> window);
|
||||
void setText(const QString &text);
|
||||
void setStyle(const style::WindowTitle &st);
|
||||
[[nodiscard]] not_null<const style::WindowTitle*> st() const;
|
||||
void setResizeEnabled(bool enabled);
|
||||
|
||||
void refreshAdditionalPaddings();
|
||||
void refreshAdditionalPaddings(HWND handle);
|
||||
void refreshAdditionalPaddings(
|
||||
HWND handle,
|
||||
const WINDOWPLACEMENT &placement);
|
||||
[[nodiscard]] int additionalPadding() const;
|
||||
[[nodiscard]] rpl::producer<int> additionalPaddingValue() const;
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent *e) override;
|
||||
void resizeEvent(QResizeEvent *e) override;
|
||||
|
||||
void setVisibleHook(bool visible) override;
|
||||
|
||||
private:
|
||||
struct PaddingHelper;
|
||||
|
||||
[[nodiscard]] HitTestResult hitTest(QPoint point) const;
|
||||
[[nodiscard]] bool additionalPaddingRequired() const;
|
||||
void refreshGeometryWithWidth(int width);
|
||||
void setAdditionalPadding(int padding);
|
||||
void updateShadowGeometry();
|
||||
|
||||
std::unique_ptr<PaddingHelper> _paddingHelper;
|
||||
TitleControls _controls;
|
||||
object_ptr<Ui::PlainShadow> _shadow;
|
||||
|
||||
};
|
||||
|
||||
[[nodiscard]] bool CheckTitlePaddingRequired();
|
||||
|
||||
} // namespace Platform
|
||||
} // namespace Ui
|
||||
973
Telegram/lib_ui/ui/platform/win/ui_window_win.cpp
Normal file
973
Telegram/lib_ui/ui/platform/win/ui_window_win.cpp
Normal file
@@ -0,0 +1,973 @@
|
||||
// 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 "ui/platform/win/ui_windows_direct_manipulation.h"
|
||||
#include "ui/platform/ui_platform_utility.h"
|
||||
#include "ui/widgets/rp_window.h"
|
||||
#include "ui/widgets/elastic_scroll.h"
|
||||
#include "ui/qt_object_factory.h"
|
||||
#include "ui/ui_utility.h"
|
||||
#include "base/platform/win/base_windows_safe_library.h"
|
||||
#include "base/platform/base_platform_info.h"
|
||||
#include "base/event_filter.h"
|
||||
#include "base/integration.h"
|
||||
#include "base/invoke_queued.h"
|
||||
#include "base/debug_log.h"
|
||||
#include "styles/palette.h"
|
||||
#include "styles/style_widgets.h"
|
||||
|
||||
#include <QtCore/QPoint>
|
||||
#include <QtGui/QWindow>
|
||||
#include <QtWidgets/QStyleFactory>
|
||||
#include <QtWidgets/QApplication>
|
||||
#include <qpa/qplatformnativeinterface.h>
|
||||
#include <qpa/qwindowsysteminterface.h>
|
||||
|
||||
#include <dwmapi.h>
|
||||
#include <shellapi.h>
|
||||
#include <uxtheme.h>
|
||||
#include <windowsx.h>
|
||||
|
||||
Q_DECLARE_METATYPE(QMargins);
|
||||
|
||||
namespace Ui::Platform {
|
||||
namespace {
|
||||
|
||||
constexpr auto kDWMWCP_ROUND = DWORD(2);
|
||||
constexpr auto kDWMWCP_DONOTROUND = DWORD(1);
|
||||
constexpr auto kDWMWA_WINDOW_CORNER_PREFERENCE = DWORD(33);
|
||||
constexpr auto kDWMWA_CAPTION_COLOR = DWORD(35);
|
||||
constexpr auto kDWMWA_TEXT_COLOR = DWORD(36);
|
||||
|
||||
UINT(__stdcall *GetDpiForWindow)(_In_ HWND hwnd);
|
||||
|
||||
int(__stdcall *GetSystemMetricsForDpi)(
|
||||
_In_ int nIndex,
|
||||
_In_ UINT dpi);
|
||||
|
||||
BOOL(__stdcall *AdjustWindowRectExForDpi)(
|
||||
_Inout_ LPRECT lpRect,
|
||||
_In_ DWORD dwStyle,
|
||||
_In_ BOOL bMenu,
|
||||
_In_ DWORD dwExStyle,
|
||||
_In_ UINT dpi);
|
||||
|
||||
[[nodiscard]] bool GetDpiForWindowSupported() {
|
||||
static const auto Result = [&] {
|
||||
#define LOAD_SYMBOL(lib, name) base::Platform::LoadMethod(lib, #name, name)
|
||||
const auto user32 = base::Platform::SafeLoadLibrary(L"User32.dll");
|
||||
return LOAD_SYMBOL(user32, GetDpiForWindow);
|
||||
#undef LOAD_SYMBOL
|
||||
}();
|
||||
return Result;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool GetSystemMetricsForDpiSupported() {
|
||||
static const auto Result = [&] {
|
||||
#define LOAD_SYMBOL(lib, name) base::Platform::LoadMethod(lib, #name, name)
|
||||
const auto user32 = base::Platform::SafeLoadLibrary(L"User32.dll");
|
||||
return LOAD_SYMBOL(user32, GetSystemMetricsForDpi);
|
||||
#undef LOAD_SYMBOL
|
||||
}();
|
||||
return Result;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool AdjustWindowRectExForDpiSupported() {
|
||||
static const auto Result = [&] {
|
||||
#define LOAD_SYMBOL(lib, name) base::Platform::LoadMethod(lib, #name, name)
|
||||
const auto user32 = base::Platform::SafeLoadLibrary(L"User32.dll");
|
||||
return LOAD_SYMBOL(user32, AdjustWindowRectExForDpi);
|
||||
#undef LOAD_SYMBOL
|
||||
}();
|
||||
return Result;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool IsCompositionEnabled() {
|
||||
auto result = BOOL(FALSE);
|
||||
const auto success = (DwmIsCompositionEnabled(&result) == S_OK);
|
||||
return success && result;
|
||||
}
|
||||
|
||||
[[nodiscard]] HWND FindTaskbarWindow(LPRECT rcMon = nullptr) {
|
||||
HWND hTaskbar = nullptr;
|
||||
RECT rcTaskbar, rcMatch;
|
||||
|
||||
while ((hTaskbar = FindWindowEx(
|
||||
nullptr,
|
||||
hTaskbar,
|
||||
L"Shell_TrayWnd",
|
||||
nullptr)) != nullptr) {
|
||||
if (!rcMon) {
|
||||
break; // OK, return first found
|
||||
}
|
||||
if (GetWindowRect(hTaskbar, &rcTaskbar)
|
||||
&& IntersectRect(&rcMatch, &rcTaskbar, rcMon)) {
|
||||
break; // OK, taskbar match monitor
|
||||
}
|
||||
}
|
||||
|
||||
return hTaskbar;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool IsTaskbarAutoHidden(
|
||||
LPRECT rcMon = nullptr,
|
||||
PUINT pEdge = nullptr) {
|
||||
HWND hTaskbar = FindTaskbarWindow(rcMon);
|
||||
if (!hTaskbar) {
|
||||
if (pEdge) {
|
||||
*pEdge = (UINT)-1;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
APPBARDATA state = {sizeof(state), hTaskbar};
|
||||
APPBARDATA pos = {sizeof(pos), hTaskbar};
|
||||
|
||||
LRESULT lState = SHAppBarMessage(ABM_GETSTATE, &state);
|
||||
bool bAutoHidden = (lState & ABS_AUTOHIDE);
|
||||
|
||||
if (SHAppBarMessage(ABM_GETTASKBARPOS, &pos)) {
|
||||
if (pEdge) {
|
||||
*pEdge = pos.uEdge;
|
||||
}
|
||||
} else {
|
||||
DEBUG_LOG(("Failed to get taskbar pos"));
|
||||
if (pEdge) {
|
||||
*pEdge = ABE_BOTTOM;
|
||||
}
|
||||
}
|
||||
|
||||
return bAutoHidden;
|
||||
}
|
||||
|
||||
void FixAeroSnap(HWND handle) {
|
||||
SetWindowLongPtr(
|
||||
handle,
|
||||
GWL_STYLE,
|
||||
GetWindowLongPtr(handle, GWL_STYLE) | WS_CAPTION | WS_THICKFRAME);
|
||||
}
|
||||
|
||||
[[nodiscard]] Qt::KeyboardModifiers LookupModifiers() {
|
||||
const auto check = [](int key) {
|
||||
return (GetKeyState(key) & 0x8000) != 0;
|
||||
};
|
||||
|
||||
auto result = Qt::KeyboardModifiers();
|
||||
if (check(VK_SHIFT)) {
|
||||
result |= Qt::ShiftModifier;
|
||||
}
|
||||
// NB AltGr key (i.e., VK_RMENU on some keyboard layout) is not handled.
|
||||
if (check(VK_RMENU) || check(VK_MENU)) {
|
||||
result |= Qt::AltModifier;
|
||||
}
|
||||
if (check(VK_CONTROL)) {
|
||||
result |= Qt::ControlModifier;
|
||||
}
|
||||
if (check(VK_LWIN) || check(VK_RWIN)) {
|
||||
result |= Qt::MetaModifier;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
WindowHelper::WindowHelper(not_null<RpWidget*> window)
|
||||
: BasicWindowHelper(window)
|
||||
, NativeEventFilter(window)
|
||||
, _title(Ui::CreateChild<TitleWidget>(window.get()))
|
||||
, _body(Ui::CreateChild<RpWidget>(window.get())) {
|
||||
if (!::Platform::IsWindows8OrGreater()) {
|
||||
window->setWindowFlag(Qt::FramelessWindowHint);
|
||||
}
|
||||
init();
|
||||
}
|
||||
|
||||
WindowHelper::~WindowHelper() = default;
|
||||
|
||||
void WindowHelper::initInWindow(not_null<RpWindow*> window) {
|
||||
_title->initInWindow(window);
|
||||
}
|
||||
|
||||
not_null<RpWidget*> WindowHelper::body() {
|
||||
return _body;
|
||||
}
|
||||
|
||||
QMargins WindowHelper::frameMargins() {
|
||||
return _title->isHidden()
|
||||
? BasicWindowHelper::nativeFrameMargins()
|
||||
: QMargins{ 0, _title->height(), 0, 0 };
|
||||
}
|
||||
|
||||
int WindowHelper::additionalContentPadding() const {
|
||||
return _title->isHidden() ? 0 : _title->additionalPadding();
|
||||
}
|
||||
|
||||
rpl::producer<int> WindowHelper::additionalContentPaddingValue() const {
|
||||
return rpl::combine(
|
||||
_title->shownValue(),
|
||||
_title->additionalPaddingValue()
|
||||
) | rpl::map([](bool shown, int padding) {
|
||||
return shown ? padding : 0;
|
||||
}) | rpl::distinct_until_changed();
|
||||
}
|
||||
|
||||
void WindowHelper::setTitle(const QString &title) {
|
||||
_title->setText(title);
|
||||
|
||||
// Windows shows them as FSI and PDI in squares(!)
|
||||
window()->setWindowTitle(QString(title)
|
||||
.replace(QChar(0x2068), QString())
|
||||
.replace(QChar(0x2069), QString()));
|
||||
}
|
||||
|
||||
void WindowHelper::setTitleStyle(const style::WindowTitle &st) {
|
||||
_title->setStyle(st);
|
||||
updateWindowFrameColors();
|
||||
}
|
||||
|
||||
void WindowHelper::setNativeFrame(bool enabled) {
|
||||
if (_handle && !::Platform::IsWindows8OrGreater()) {
|
||||
window()->windowHandle()->setFlag(Qt::FramelessWindowHint, !enabled);
|
||||
if (!enabled) {
|
||||
FixAeroSnap(_handle);
|
||||
}
|
||||
}
|
||||
_title->setVisible(!enabled);
|
||||
if (_handle) {
|
||||
updateShadow();
|
||||
updateCornersRounding();
|
||||
updateMargins();
|
||||
updateWindowFrameColors();
|
||||
fixMaximizedWindow();
|
||||
SetWindowPos(
|
||||
_handle,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
(SWP_FRAMECHANGED
|
||||
| SWP_NOMOVE
|
||||
| SWP_NOSIZE
|
||||
| SWP_NOZORDER
|
||||
| SWP_NOACTIVATE));
|
||||
}
|
||||
}
|
||||
|
||||
void WindowHelper::updateShadow() {
|
||||
if (!_handle || _title->isHidden() || nativeResize()) {
|
||||
_shadow.reset();
|
||||
return;
|
||||
}
|
||||
_shadow.emplace(window(), st::windowShadowFg->c);
|
||||
_shadow->setResizeEnabled(!fixedSize());
|
||||
using Change = WindowShadow::Change;
|
||||
const auto noShadowStates = (Qt::WindowMinimized | Qt::WindowMaximized);
|
||||
if ((window()->windowState() & noShadowStates) || window()->isHidden()) {
|
||||
_shadow->update(Change::Hidden);
|
||||
} else {
|
||||
_shadow->update(Change::Moved | Change::Resized | Change::Shown);
|
||||
}
|
||||
}
|
||||
|
||||
void WindowHelper::updateCornersRounding() {
|
||||
if (!_handle || !::Platform::IsWindows11OrGreater()) {
|
||||
return;
|
||||
}
|
||||
const auto preference = (_isFullScreen || _isMaximizedAndTranslucent)
|
||||
? kDWMWCP_DONOTROUND
|
||||
: kDWMWCP_ROUND;
|
||||
DwmSetWindowAttribute(
|
||||
_handle,
|
||||
kDWMWA_WINDOW_CORNER_PREFERENCE,
|
||||
&preference,
|
||||
sizeof(preference));
|
||||
}
|
||||
|
||||
void WindowHelper::setMinimumSize(QSize size) {
|
||||
window()->setMinimumSize(size.width(), titleHeight() + size.height());
|
||||
}
|
||||
|
||||
void WindowHelper::setFixedSize(QSize size) {
|
||||
window()->setFixedSize(size.width(), titleHeight() + size.height());
|
||||
_title->setResizeEnabled(false);
|
||||
if (_shadow) {
|
||||
_shadow->setResizeEnabled(false);
|
||||
}
|
||||
}
|
||||
|
||||
void WindowHelper::setGeometry(QRect rect) {
|
||||
window()->setGeometry(rect.marginsAdded({ 0, titleHeight(), 0, 0 }));
|
||||
}
|
||||
|
||||
void WindowHelper::showFullScreen() {
|
||||
if (!_isFullScreen) {
|
||||
_isFullScreen = true;
|
||||
updateMargins();
|
||||
updateCornersRounding();
|
||||
updateCloaking();
|
||||
}
|
||||
window()->showFullScreen();
|
||||
}
|
||||
|
||||
void WindowHelper::showNormal() {
|
||||
window()->showNormal();
|
||||
if (_isFullScreen) {
|
||||
_isFullScreen = false;
|
||||
updateMargins();
|
||||
updateCornersRounding();
|
||||
updateCloaking();
|
||||
}
|
||||
}
|
||||
|
||||
auto WindowHelper::hitTestRequests() const
|
||||
-> rpl::producer<not_null<HitTestRequest*>> {
|
||||
return _hitTestRequests.events();
|
||||
}
|
||||
|
||||
rpl::producer<HitTestResult> WindowHelper::systemButtonOver() const {
|
||||
return _systemButtonOver.events();
|
||||
}
|
||||
|
||||
rpl::producer<HitTestResult> WindowHelper::systemButtonDown() const {
|
||||
return _systemButtonDown.events();
|
||||
}
|
||||
|
||||
void WindowHelper::overrideSystemButtonOver(HitTestResult button) {
|
||||
_systemButtonOver.fire_copy(button);
|
||||
}
|
||||
|
||||
void WindowHelper::overrideSystemButtonDown(HitTestResult button) {
|
||||
_systemButtonDown.fire_copy(button);
|
||||
}
|
||||
|
||||
void WindowHelper::init() {
|
||||
_title->show();
|
||||
|
||||
window()->winIdValue() | rpl::on_next([=](WId winId) {
|
||||
_handle = reinterpret_cast<HWND>(winId);
|
||||
|
||||
if (!::Platform::IsWindows8OrGreater()) {
|
||||
const auto native = _title->isHidden();
|
||||
window()->setWindowFlag(Qt::FramelessWindowHint, !native);
|
||||
if (_handle && !native) {
|
||||
FixAeroSnap(_handle);
|
||||
}
|
||||
}
|
||||
|
||||
if (_handle) {
|
||||
_dpi = GetDpiForWindowSupported()
|
||||
? GetDpiForWindow(_handle)
|
||||
: 0;
|
||||
updateWindowFrameColors();
|
||||
updateShadow();
|
||||
updateCornersRounding();
|
||||
updateMargins();
|
||||
if (window()->isHidden()) {
|
||||
enableCloakingForHidden();
|
||||
}
|
||||
}
|
||||
}, window()->lifetime());
|
||||
|
||||
style::PaletteChanged(
|
||||
) | rpl::on_next([=] {
|
||||
if (_shadow) {
|
||||
_shadow->setColor(st::windowShadowFg->c);
|
||||
}
|
||||
updateWindowFrameColors();
|
||||
Ui::ForceFullRepaint(window());
|
||||
}, window()->lifetime());
|
||||
|
||||
rpl::combine(
|
||||
window()->sizeValue(),
|
||||
_title->heightValue(),
|
||||
_title->shownValue()
|
||||
) | rpl::on_next([=](
|
||||
QSize size,
|
||||
int titleHeight,
|
||||
bool titleShown) {
|
||||
_body->setGeometry(
|
||||
0,
|
||||
titleShown ? titleHeight : 0,
|
||||
size.width(),
|
||||
size.height() - (titleShown ? titleHeight : 0));
|
||||
}, _body->lifetime());
|
||||
|
||||
_dpi.value() | rpl::on_next([=](uint dpi) {
|
||||
updateMargins();
|
||||
}, window()->lifetime());
|
||||
|
||||
if (_handle && !::Platform::IsWindows8OrGreater()) {
|
||||
SetWindowTheme(_handle, L" ", L" ");
|
||||
QApplication::setStyle(QStyleFactory::create("Windows"));
|
||||
}
|
||||
|
||||
const auto handleStateChanged = [=](Qt::WindowState state) {
|
||||
if (fixedSize() && (state & Qt::WindowMaximized)) {
|
||||
crl::on_main(window().get(), [=] {
|
||||
window()->setWindowState(
|
||||
window()->windowState() & ~Qt::WindowMaximized);
|
||||
});
|
||||
}
|
||||
if (state != Qt::WindowMinimized) {
|
||||
const auto is = (state == Qt::WindowMaximized)
|
||||
&& window()->testAttribute(Qt::WA_TranslucentBackground);
|
||||
if (_isMaximizedAndTranslucent != is) {
|
||||
_isMaximizedAndTranslucent = is;
|
||||
updateCornersRounding();
|
||||
}
|
||||
}
|
||||
};
|
||||
Ui::Connect(
|
||||
window()->windowHandle(),
|
||||
&QWindow::windowStateChanged,
|
||||
handleStateChanged);
|
||||
|
||||
ActivateDirectManipulation(window());
|
||||
|
||||
window()->shownValue() | rpl::filter([=](bool shown) {
|
||||
return _handle && !shown;
|
||||
}) | rpl::on_next([=] {
|
||||
enableCloakingForHidden();
|
||||
}, window()->lifetime());
|
||||
}
|
||||
|
||||
bool WindowHelper::filterNativeEvent(
|
||||
UINT msg,
|
||||
WPARAM wParam,
|
||||
LPARAM lParam,
|
||||
LRESULT *result) {
|
||||
Expects(_handle != nullptr);
|
||||
|
||||
if (handleSystemButtonEvent(msg, wParam, lParam, result)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
switch (msg) {
|
||||
|
||||
case WM_ACTIVATE: {
|
||||
if (LOWORD(wParam) == WA_CLICKACTIVE) {
|
||||
Ui::MarkInactivePress(window(), true);
|
||||
}
|
||||
const auto active = (LOWORD(wParam) != WA_INACTIVE);
|
||||
if (_shadow) {
|
||||
if (active) {
|
||||
_shadow->update(WindowShadow::Change::Activate);
|
||||
} else {
|
||||
_shadow->update(WindowShadow::Change::Deactivate);
|
||||
}
|
||||
}
|
||||
updateWindowFrameColors(active);
|
||||
window()->update();
|
||||
_title->update();
|
||||
} return false;
|
||||
|
||||
case WM_NCPAINT: {
|
||||
if (::Platform::IsWindows8OrGreater() || _title->isHidden()) {
|
||||
return false;
|
||||
}
|
||||
if (result) *result = 0;
|
||||
} return true;
|
||||
|
||||
case WM_NCCALCSIZE: {
|
||||
if (_title->isHidden() || window()->isFullScreen() || !wParam) {
|
||||
return false;
|
||||
}
|
||||
const auto r = &((LPNCCALCSIZE_PARAMS)lParam)->rgrc[0];
|
||||
const auto maximized = [&] {
|
||||
WINDOWPLACEMENT wp;
|
||||
wp.length = sizeof(WINDOWPLACEMENT);
|
||||
return GetWindowPlacement(_handle, &wp)
|
||||
&& (wp.showCmd == SW_SHOWMAXIMIZED);
|
||||
}();
|
||||
const auto addBorders = maximized || nativeResize();
|
||||
if (addBorders) {
|
||||
const auto dpi = _dpi.current();
|
||||
const auto borderWidth = (GetSystemMetricsForDpiSupported() && dpi)
|
||||
? GetSystemMetricsForDpi(SM_CXSIZEFRAME, dpi)
|
||||
+ GetSystemMetricsForDpi(SM_CXPADDEDBORDER, dpi)
|
||||
: GetSystemMetrics(SM_CXSIZEFRAME)
|
||||
+ GetSystemMetrics(SM_CXPADDEDBORDER);
|
||||
const auto borderHeight = (GetSystemMetricsForDpiSupported() && dpi)
|
||||
? GetSystemMetricsForDpi(SM_CYSIZEFRAME, dpi)
|
||||
+ GetSystemMetricsForDpi(SM_CXPADDEDBORDER, dpi)
|
||||
: GetSystemMetrics(SM_CYSIZEFRAME)
|
||||
+ GetSystemMetrics(SM_CXPADDEDBORDER);
|
||||
r->left += borderWidth;
|
||||
r->right -= borderWidth;
|
||||
if (maximized) {
|
||||
r->top += borderHeight;
|
||||
}
|
||||
r->bottom -= borderHeight;
|
||||
}
|
||||
if (maximized) {
|
||||
const auto hMonitor = MonitorFromWindow(
|
||||
_handle,
|
||||
MONITOR_DEFAULTTONEAREST);
|
||||
MONITORINFO mi;
|
||||
mi.cbSize = sizeof(mi);
|
||||
UINT uEdge = (UINT)-1;
|
||||
if (GetMonitorInfo(hMonitor, &mi)
|
||||
&& IsTaskbarAutoHidden(&mi.rcMonitor, &uEdge)) {
|
||||
switch (uEdge) {
|
||||
case ABE_LEFT: r->left += 1; break;
|
||||
case ABE_RIGHT: r->right -= 1; break;
|
||||
case ABE_TOP: r->top += 1; break;
|
||||
case ABE_BOTTOM: r->bottom -= 1; break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (result) *result = addBorders ? 0 : WVR_REDRAW;
|
||||
} return true;
|
||||
|
||||
case WM_NCRBUTTONUP: {
|
||||
if (_title->isHidden()) {
|
||||
return false;
|
||||
}
|
||||
POINT p{ GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };
|
||||
ScreenToClient(_handle, &p);
|
||||
const auto mapped = QPoint(p.x, p.y)
|
||||
/ window()->windowHandle()->devicePixelRatio();
|
||||
ShowWindowMenu(window(), mapped);
|
||||
if (result) *result = 0;
|
||||
} return true;
|
||||
|
||||
case WM_NCACTIVATE: {
|
||||
if (_title->isHidden()) {
|
||||
return false;
|
||||
}
|
||||
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: {
|
||||
auto placement = WINDOWPLACEMENT{
|
||||
.length = sizeof(WINDOWPLACEMENT),
|
||||
};
|
||||
if (!GetWindowPlacement(_handle, &placement)) {
|
||||
LOG(("System Error: GetWindowPlacement failed."));
|
||||
return false;
|
||||
}
|
||||
_title->refreshAdditionalPaddings(_handle, placement);
|
||||
if (_shadow) {
|
||||
if (placement.showCmd == SW_SHOWMAXIMIZED
|
||||
|| placement.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) {
|
||||
const auto now = window()->windowState();
|
||||
if (wParam != SIZE_RESTORED
|
||||
|| (now != Qt::WindowNoState
|
||||
&& now != Qt::WindowFullScreen)) {
|
||||
Qt::WindowState state = Qt::WindowNoState;
|
||||
if (wParam == SIZE_MAXIMIZED) {
|
||||
state = Qt::WindowMaximized;
|
||||
} else if (wParam == SIZE_MINIMIZED) {
|
||||
state = Qt::WindowMinimized;
|
||||
}
|
||||
window()->windowHandle()->windowStateChanged(state);
|
||||
}
|
||||
updateMargins();
|
||||
_title->refreshAdditionalPaddings(_handle);
|
||||
if (_shadow) {
|
||||
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: {
|
||||
if (_shadow) {
|
||||
const auto style = GetWindowLongPtr(_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: {
|
||||
_title->refreshAdditionalPaddings(_handle);
|
||||
if (_shadow) {
|
||||
_shadow->update(WindowShadow::Change::Moved);
|
||||
}
|
||||
} return false;
|
||||
|
||||
case WM_NCHITTEST: {
|
||||
if (!result || _title->isHidden()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
POINT p{ GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };
|
||||
ScreenToClient(_handle, &p);
|
||||
const auto ratio = window()->windowHandle()->devicePixelRatio();
|
||||
const auto mapped = QPoint(
|
||||
int(std::floor(p.x / ratio)),
|
||||
int(std::floor(p.y / ratio)));
|
||||
*result = [&]() -> LRESULT {
|
||||
if (!window()->rect().contains(mapped)) {
|
||||
return DefWindowProc(_handle, msg, wParam, lParam);
|
||||
}
|
||||
const auto maximized = window()->isMaximized()
|
||||
|| window()->isFullScreen();
|
||||
const auto px = int(std::ceil(
|
||||
st::windowTitleHeight
|
||||
* window()->windowHandle()->devicePixelRatio()
|
||||
/ 10.));
|
||||
if (nativeResize() && !maximized && (mapped.y() < px)) {
|
||||
return HTTOP;
|
||||
}
|
||||
auto request = HitTestRequest{
|
||||
.point = mapped,
|
||||
};
|
||||
_hitTestRequests.fire(&request);
|
||||
switch (const auto result = request.result) {
|
||||
case HitTestResult::Client: return HTCLIENT;
|
||||
case HitTestResult::Caption: return HTCAPTION;
|
||||
case HitTestResult::Top: return HTTOP;
|
||||
case HitTestResult::TopRight: return HTTOPRIGHT;
|
||||
case HitTestResult::Right: return HTRIGHT;
|
||||
case HitTestResult::BottomRight: return HTBOTTOMRIGHT;
|
||||
case HitTestResult::Bottom: return HTBOTTOM;
|
||||
case HitTestResult::BottomLeft: return HTBOTTOMLEFT;
|
||||
case HitTestResult::Left: return HTLEFT;
|
||||
case HitTestResult::TopLeft: return HTTOPLEFT;
|
||||
|
||||
case HitTestResult::Minimize:
|
||||
case HitTestResult::MaximizeRestore:
|
||||
case HitTestResult::Close: return systemButtonHitTest(result);
|
||||
|
||||
case HitTestResult::None:
|
||||
default: return DefWindowProc(_handle, msg, wParam, lParam);
|
||||
};
|
||||
}();
|
||||
_systemButtonOver.fire(systemButtonHitTest(*result));
|
||||
} return true;
|
||||
|
||||
case WM_DPICHANGED: {
|
||||
_dpi = LOWORD(wParam);
|
||||
InvokeQueued(_title, [=] {
|
||||
_title->refreshAdditionalPaddings(_handle);
|
||||
});
|
||||
} return false;
|
||||
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool WindowHelper::fixedSize() const {
|
||||
return window()->minimumSize() == window()->maximumSize();
|
||||
}
|
||||
|
||||
bool WindowHelper::handleSystemButtonEvent(
|
||||
UINT msg,
|
||||
WPARAM wParam,
|
||||
LPARAM lParam,
|
||||
LRESULT *result) {
|
||||
if (_title->isHidden()) {
|
||||
return false;
|
||||
}
|
||||
const auto testResult = LOWORD(wParam);
|
||||
const auto sysButtons = { HTMINBUTTON, HTMAXBUTTON, HTCLOSE };
|
||||
const auto overSysButton = ranges::contains(sysButtons, testResult);
|
||||
switch (msg) {
|
||||
case WM_NCLBUTTONDBLCLK:
|
||||
case WM_NCMBUTTONDBLCLK:
|
||||
case WM_NCRBUTTONDBLCLK:
|
||||
case WM_NCXBUTTONDBLCLK: {
|
||||
if (!overSysButton || fixedSize()) {
|
||||
return false;
|
||||
}
|
||||
// Ignore double clicks on system buttons.
|
||||
if (result) *result = 0;
|
||||
} return true;
|
||||
|
||||
case WM_NCLBUTTONDOWN:
|
||||
case WM_NCLBUTTONUP:
|
||||
_systemButtonDown.fire((msg == WM_NCLBUTTONDOWN)
|
||||
? systemButtonHitTest(testResult)
|
||||
: HitTestResult::None);
|
||||
if (overSysButton) {
|
||||
if (result) *result = 0;
|
||||
}
|
||||
return overSysButton;
|
||||
case WM_NCMBUTTONDOWN:
|
||||
case WM_NCMBUTTONUP:
|
||||
case WM_NCRBUTTONDOWN:
|
||||
case WM_NCRBUTTONUP:
|
||||
case WM_NCXBUTTONDOWN:
|
||||
case WM_NCXBUTTONUP:
|
||||
if (!overSysButton) {
|
||||
return false;
|
||||
}
|
||||
if (result) *result = 0;
|
||||
return true;
|
||||
case WM_NCMOUSEHOVER:
|
||||
case WM_NCMOUSEMOVE:
|
||||
_systemButtonOver.fire(systemButtonHitTest(testResult));
|
||||
if (overSysButton) {
|
||||
if (result) *result = 0;
|
||||
}
|
||||
return overSysButton;
|
||||
case WM_NCMOUSELEAVE:
|
||||
_systemButtonOver.fire(HitTestResult::None);
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
int WindowHelper::systemButtonHitTest(HitTestResult result) const {
|
||||
if (!SemiNativeSystemButtonProcessing()) {
|
||||
return HTCLIENT;
|
||||
}
|
||||
switch (result) {
|
||||
case HitTestResult::Minimize: return HTMINBUTTON;
|
||||
case HitTestResult::MaximizeRestore: return HTMAXBUTTON;
|
||||
case HitTestResult::Close: return HTCLOSE;
|
||||
}
|
||||
return HTTRANSPARENT;
|
||||
}
|
||||
|
||||
HitTestResult WindowHelper::systemButtonHitTest(int result) const {
|
||||
if (!SemiNativeSystemButtonProcessing()) {
|
||||
return HitTestResult::None;
|
||||
}
|
||||
switch (result) {
|
||||
case HTMINBUTTON: return HitTestResult::Minimize;
|
||||
case HTMAXBUTTON: return HitTestResult::MaximizeRestore;
|
||||
case HTCLOSE: return HitTestResult::Close;
|
||||
}
|
||||
return HitTestResult::None;
|
||||
}
|
||||
|
||||
int WindowHelper::titleHeight() const {
|
||||
return _title->isHidden() ? 0 : _title->height();
|
||||
}
|
||||
|
||||
bool WindowHelper::nativeResize() const {
|
||||
Expects(window()->windowHandle() != nullptr);
|
||||
|
||||
if (::Platform::IsWindows11OrGreater()) {
|
||||
switch (window()->windowHandle()->surfaceType()) {
|
||||
case QSurface::RasterSurface:
|
||||
case QSurface::RasterGLSurface:
|
||||
return window()->windowHandle()->format().alphaBufferSize() <= 0;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void WindowHelper::updateWindowFrameColors() {
|
||||
updateWindowFrameColors(window()->isActiveWindow());
|
||||
}
|
||||
|
||||
void WindowHelper::updateWindowFrameColors(bool active) {
|
||||
if (!_handle || !::Platform::IsWindows11OrGreater()) {
|
||||
return;
|
||||
}
|
||||
const auto bg = active
|
||||
? _title->st()->bgActive->c
|
||||
: _title->st()->bg->c;
|
||||
COLORREF bgRef = RGB(bg.red(), bg.green(), bg.blue());
|
||||
DwmSetWindowAttribute(
|
||||
_handle,
|
||||
kDWMWA_CAPTION_COLOR,
|
||||
&bgRef,
|
||||
sizeof(COLORREF));
|
||||
const auto fg = active
|
||||
? _title->st()->fgActive->c
|
||||
: _title->st()->fg->c;
|
||||
COLORREF fgRef = RGB(fg.red(), fg.green(), fg.blue());
|
||||
DwmSetWindowAttribute(
|
||||
_handle,
|
||||
kDWMWA_TEXT_COLOR,
|
||||
&fgRef,
|
||||
sizeof(COLORREF));
|
||||
}
|
||||
|
||||
void WindowHelper::updateCloaking() {
|
||||
if (!_handle) {
|
||||
return;
|
||||
}
|
||||
const auto enabled = window()->isHidden() && !_isFullScreen;
|
||||
const auto flag = BOOL(enabled ? TRUE : FALSE);
|
||||
DwmSetWindowAttribute(_handle, DWMWA_CLOAK, &flag, sizeof(flag));
|
||||
}
|
||||
|
||||
void WindowHelper::enableCloakingForHidden() {
|
||||
Expects(_handle != nullptr);
|
||||
|
||||
updateCloaking();
|
||||
|
||||
const auto qwindow = window()->windowHandle();
|
||||
const auto firstExposeFilter = std::make_shared<QObject*>();
|
||||
const auto filter = [=](not_null<QEvent*> e) {
|
||||
if (e->type() == QEvent::Expose && qwindow->isExposed()) {
|
||||
InvokeQueued(qwindow, [=] {
|
||||
InvokeQueued(qwindow, [=] {
|
||||
updateCloaking();
|
||||
});
|
||||
});
|
||||
delete base::take(*firstExposeFilter);
|
||||
}
|
||||
return base::EventFilterResult::Continue;
|
||||
};
|
||||
*firstExposeFilter = base::install_event_filter(qwindow, filter);
|
||||
}
|
||||
|
||||
void WindowHelper::updateMargins() {
|
||||
if (!_handle || _updatingMargins) {
|
||||
return;
|
||||
}
|
||||
|
||||
_updatingMargins = true;
|
||||
const auto guard = gsl::finally([&] { _updatingMargins = false; });
|
||||
|
||||
RECT r{};
|
||||
const auto style = GetWindowLongPtr(_handle, GWL_STYLE);
|
||||
const auto styleEx = GetWindowLongPtr(_handle, GWL_EXSTYLE);
|
||||
const auto dpi = _dpi.current();
|
||||
if (AdjustWindowRectExForDpiSupported() && dpi) {
|
||||
AdjustWindowRectExForDpi(&r, style, false, styleEx, dpi);
|
||||
} else {
|
||||
AdjustWindowRectEx(&r, style, false, styleEx);
|
||||
}
|
||||
auto margins = nativeResize()
|
||||
? QMargins(0, r.top, 0, 0)
|
||||
: QMargins(r.left, r.top, -r.right, -r.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 (_isFullScreen || _title->isHidden()) {
|
||||
margins = QMargins();
|
||||
if (_title->isHidden()) {
|
||||
_marginsDelta = QMargins();
|
||||
}
|
||||
}
|
||||
if (const auto native = QGuiApplication::platformNativeInterface()) {
|
||||
native->setWindowProperty(
|
||||
window()->windowHandle()->handle(),
|
||||
"WindowsCustomMargins",
|
||||
QVariant::fromValue<QMargins>(margins));
|
||||
}
|
||||
}
|
||||
|
||||
void WindowHelper::fixMaximizedWindow() {
|
||||
if (!_handle) {
|
||||
return;
|
||||
}
|
||||
const auto style = GetWindowLongPtr(_handle, GWL_STYLE);
|
||||
if (style & WS_MAXIMIZE) {
|
||||
auto w = RECT();
|
||||
GetWindowRect(_handle, &w);
|
||||
if (const auto hMonitor = MonitorFromRect(&w, MONITOR_DEFAULTTONEAREST)) {
|
||||
MONITORINFO mi;
|
||||
mi.cbSize = sizeof(mi);
|
||||
GetMonitorInfo(hMonitor, &mi);
|
||||
const auto m = mi.rcWork;
|
||||
SetWindowPos(
|
||||
_handle,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
m.right - m.left - _marginsDelta.left() - _marginsDelta.right(),
|
||||
m.bottom - m.top - _marginsDelta.top() - _marginsDelta.bottom(),
|
||||
SWP_NOMOVE | SWP_NOSENDCHANGING | SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOREPOSITION);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
HWND GetCurrentHandle(not_null<QWidget*> widget) {
|
||||
const auto toplevel = widget->window();
|
||||
const auto window = toplevel->windowHandle();
|
||||
return window ? GetCurrentHandle(window) : nullptr;
|
||||
}
|
||||
|
||||
HWND GetCurrentHandle(not_null<QWindow*> window) {
|
||||
return reinterpret_cast<HWND>(window->winId());
|
||||
}
|
||||
|
||||
std::unique_ptr<BasicWindowHelper> CreateSpecialWindowHelper(
|
||||
not_null<RpWidget*> window) {
|
||||
return std::make_unique<WindowHelper>(window);
|
||||
}
|
||||
|
||||
bool NativeWindowFrameSupported() {
|
||||
return true;
|
||||
}
|
||||
|
||||
rpl::producer<FullScreenEvent> FullScreenEvents(
|
||||
not_null<RpWidget*> window) {
|
||||
return rpl::never<FullScreenEvent>();
|
||||
}
|
||||
|
||||
} // namespace Ui::Platform
|
||||
94
Telegram/lib_ui/ui/platform/win/ui_window_win.h
Normal file
94
Telegram/lib_ui/ui/platform/win/ui_window_win.h
Normal file
@@ -0,0 +1,94 @@
|
||||
// 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 "ui/platform/win/ui_windows_native_event_filter.h"
|
||||
|
||||
namespace Ui {
|
||||
namespace Platform {
|
||||
|
||||
class TitleWidget;
|
||||
struct HitTestRequest;
|
||||
enum class HitTestResult;
|
||||
|
||||
class WindowHelper final : public BasicWindowHelper, public NativeEventFilter {
|
||||
public:
|
||||
explicit WindowHelper(not_null<RpWidget*> window);
|
||||
~WindowHelper();
|
||||
|
||||
void initInWindow(not_null<RpWindow*> window) override;
|
||||
not_null<RpWidget*> body() override;
|
||||
QMargins frameMargins() override;
|
||||
int additionalContentPadding() const override;
|
||||
rpl::producer<int> additionalContentPaddingValue() const override;
|
||||
void setTitle(const QString &title) override;
|
||||
void setTitleStyle(const style::WindowTitle &st) override;
|
||||
void setNativeFrame(bool enabled) override;
|
||||
void setMinimumSize(QSize size) override;
|
||||
void setFixedSize(QSize size) override;
|
||||
void setGeometry(QRect rect) override;
|
||||
void showFullScreen() override;
|
||||
void showNormal() override;
|
||||
|
||||
[[nodiscard]] auto hitTestRequests() const
|
||||
-> rpl::producer<not_null<HitTestRequest*>> override;
|
||||
[[nodiscard]] auto systemButtonOver() const
|
||||
-> rpl::producer<HitTestResult> override;
|
||||
[[nodiscard]] auto systemButtonDown() const
|
||||
-> rpl::producer<HitTestResult> override;
|
||||
void overrideSystemButtonOver(HitTestResult button) override;
|
||||
void overrideSystemButtonDown(HitTestResult button) override;
|
||||
|
||||
private:
|
||||
void init();
|
||||
void updateMargins();
|
||||
void updateCloaking();
|
||||
void enableCloakingForHidden();
|
||||
void updateWindowFrameColors();
|
||||
void updateWindowFrameColors(bool active);
|
||||
void updateShadow();
|
||||
void updateCornersRounding();
|
||||
void fixMaximizedWindow();
|
||||
[[nodiscard]] bool filterNativeEvent(
|
||||
UINT msg,
|
||||
WPARAM wParam,
|
||||
LPARAM lParam,
|
||||
LRESULT *result) override;
|
||||
[[nodiscard]] bool handleSystemButtonEvent(
|
||||
UINT msg,
|
||||
WPARAM wParam,
|
||||
LPARAM lParam,
|
||||
LRESULT *result);
|
||||
[[nodiscard]] bool fixedSize() const;
|
||||
[[nodiscard]] int systemButtonHitTest(HitTestResult result) const;
|
||||
[[nodiscard]] HitTestResult systemButtonHitTest(int result) const;
|
||||
|
||||
[[nodiscard]] int titleHeight() const;
|
||||
[[nodiscard]] bool nativeResize() const;
|
||||
|
||||
const not_null<TitleWidget*> _title;
|
||||
const not_null<RpWidget*> _body;
|
||||
rpl::event_stream<not_null<HitTestRequest*>> _hitTestRequests;
|
||||
rpl::event_stream<HitTestResult> _systemButtonOver;
|
||||
rpl::event_stream<HitTestResult> _systemButtonDown;
|
||||
std::optional<WindowShadow> _shadow;
|
||||
rpl::variable<uint> _dpi;
|
||||
QMargins _marginsDelta;
|
||||
HWND _handle = nullptr;
|
||||
bool _updatingMargins = false;
|
||||
bool _isFullScreen = false;
|
||||
bool _isMaximizedAndTranslucent = false;
|
||||
|
||||
};
|
||||
|
||||
[[nodiscard]] HWND GetCurrentHandle(not_null<QWidget*> widget);
|
||||
[[nodiscard]] HWND GetCurrentHandle(not_null<QWindow*> window);
|
||||
|
||||
} // namespace Platform
|
||||
} // namespace Ui
|
||||
@@ -0,0 +1,563 @@
|
||||
// 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_windows_direct_manipulation.h"
|
||||
|
||||
#include "base/integration.h"
|
||||
#include "base/platform/base_platform_info.h"
|
||||
#include "base/platform/win/base_windows_safe_library.h"
|
||||
#include "ui/platform/win/ui_window_win.h"
|
||||
#include "ui/ui_utility.h" // kPixelToAngleDelta
|
||||
#include "ui/rp_widget.h"
|
||||
|
||||
#include <qpa/qwindowsysteminterface.h>
|
||||
#include <qpa/qwindowsysteminterface_p.h>
|
||||
|
||||
namespace Ui::Platform {
|
||||
namespace {
|
||||
|
||||
[[nodiscard]] Qt::KeyboardModifiers LookupModifiers() {
|
||||
const auto check = [](int key) {
|
||||
return (GetKeyState(key) & 0x8000) != 0;
|
||||
};
|
||||
|
||||
auto result = Qt::KeyboardModifiers();
|
||||
if (check(VK_SHIFT)) {
|
||||
result |= Qt::ShiftModifier;
|
||||
}
|
||||
// NB AltGr key (i.e., VK_RMENU on some keyboard layout) is not handled.
|
||||
if (check(VK_RMENU) || check(VK_MENU)) {
|
||||
result |= Qt::AltModifier;
|
||||
}
|
||||
if (check(VK_CONTROL)) {
|
||||
result |= Qt::ControlModifier;
|
||||
}
|
||||
if (check(VK_LWIN) || check(VK_RWIN)) {
|
||||
result |= Qt::MetaModifier;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
UINT(__stdcall *GetDpiForWindow)(_In_ HWND hwnd);
|
||||
|
||||
BOOL(__stdcall *GetPointerType)(
|
||||
UINT32 pointerId,
|
||||
POINTER_INPUT_TYPE *pointerType);
|
||||
|
||||
[[nodiscard]] bool GetDpiForWindowSupported() {
|
||||
static const auto Result = [&] {
|
||||
#define LOAD_SYMBOL(lib, name) base::Platform::LoadMethod(lib, #name, name)
|
||||
const auto user32 = base::Platform::SafeLoadLibrary(L"User32.dll");
|
||||
return LOAD_SYMBOL(user32, GetDpiForWindow);
|
||||
#undef LOAD_SYMBOL
|
||||
}();
|
||||
return Result;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool GetPointerTypeSupported() {
|
||||
static const auto Result = [&] {
|
||||
#define LOAD_SYMBOL(lib, name) base::Platform::LoadMethod(lib, #name, name)
|
||||
const auto user32 = base::Platform::SafeLoadLibrary(L"User32.dll");
|
||||
return LOAD_SYMBOL(user32, GetPointerType);
|
||||
#undef LOAD_SYMBOL
|
||||
}();
|
||||
return Result;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
class DirectManipulation::Handler
|
||||
: public IDirectManipulationViewportEventHandler
|
||||
, public IDirectManipulationInteractionEventHandler {
|
||||
public:
|
||||
Handler();
|
||||
|
||||
void setViewportSize(QSize size);
|
||||
|
||||
HRESULT STDMETHODCALLTYPE QueryInterface(
|
||||
REFIID iid,
|
||||
void **ppv) override;
|
||||
|
||||
ULONG STDMETHODCALLTYPE AddRef() override {
|
||||
return ++_ref;
|
||||
}
|
||||
ULONG STDMETHODCALLTYPE Release() override {
|
||||
if (--_ref == 0) {
|
||||
delete this;
|
||||
return 0;
|
||||
}
|
||||
return _ref;
|
||||
}
|
||||
|
||||
[[nodiscard]] rpl::producer<bool> interacting() const {
|
||||
return _interacting.value();
|
||||
}
|
||||
[[nodiscard]] rpl::producer<Event> events() const {
|
||||
return _events.events();
|
||||
}
|
||||
|
||||
[[nodiscard]] rpl::lifetime &lifetime() {
|
||||
return _lifetime;
|
||||
}
|
||||
|
||||
private:
|
||||
~Handler();
|
||||
|
||||
enum class State {
|
||||
None,
|
||||
Scroll,
|
||||
Fling,
|
||||
Pinch,
|
||||
};
|
||||
|
||||
void transitionToState(State state);
|
||||
|
||||
HRESULT STDMETHODCALLTYPE OnViewportStatusChanged(
|
||||
_In_ IDirectManipulationViewport* viewport,
|
||||
_In_ DIRECTMANIPULATION_STATUS current,
|
||||
_In_ DIRECTMANIPULATION_STATUS previous) override;
|
||||
|
||||
HRESULT STDMETHODCALLTYPE OnViewportUpdated(
|
||||
_In_ IDirectManipulationViewport *viewport) override;
|
||||
|
||||
HRESULT STDMETHODCALLTYPE OnContentUpdated(
|
||||
_In_ IDirectManipulationViewport *viewport,
|
||||
_In_ IDirectManipulationContent *content) override;
|
||||
|
||||
HRESULT STDMETHODCALLTYPE OnInteraction(
|
||||
_In_ IDirectManipulationViewport2 *viewport,
|
||||
_In_ DIRECTMANIPULATION_INTERACTION_TYPE interaction) override;
|
||||
|
||||
State _state = State::None;
|
||||
int _width = 0;
|
||||
int _height = 0;
|
||||
rpl::variable<bool> _interacting = false;
|
||||
rpl::event_stream<Event> _events;
|
||||
rpl::lifetime _lifetime;
|
||||
|
||||
float _scale = 1.0f;
|
||||
float _xOffset = 0.f;
|
||||
float _yOffset = 0.f;
|
||||
bool _pendingScrollBegin = false;
|
||||
|
||||
std::atomic<ULONG> _ref = 1;
|
||||
|
||||
};
|
||||
|
||||
DirectManipulation::Handler::Handler() {
|
||||
}
|
||||
|
||||
DirectManipulation::Handler::~Handler() {
|
||||
}
|
||||
|
||||
void DirectManipulation::Handler::setViewportSize(QSize size) {
|
||||
_width = size.width();
|
||||
_height = size.height();
|
||||
}
|
||||
|
||||
STDMETHODIMP DirectManipulation::Handler::QueryInterface(
|
||||
REFIID iid,
|
||||
void **ppv) {
|
||||
if ((IID_IUnknown == iid) ||
|
||||
(IID_IDirectManipulationViewportEventHandler == iid)) {
|
||||
*ppv = static_cast<IDirectManipulationViewportEventHandler*>(this);
|
||||
AddRef();
|
||||
return S_OK;
|
||||
}
|
||||
if (IID_IDirectManipulationInteractionEventHandler == iid) {
|
||||
*ppv = static_cast<IDirectManipulationInteractionEventHandler*>(
|
||||
this);
|
||||
AddRef();
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
return E_NOINTERFACE;
|
||||
}
|
||||
|
||||
void DirectManipulation::Handler::transitionToState(State state) {
|
||||
if (_state == state) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto was = _state;
|
||||
_state = state;
|
||||
|
||||
switch (was) {
|
||||
case State::Scroll: {
|
||||
if (state != State::Fling) {
|
||||
_events.fire({ .type = Event::Type::ScrollStop });
|
||||
}
|
||||
} break;
|
||||
case State::Fling: {
|
||||
_events.fire({ .type = Event::Type::FlingStop });
|
||||
} break;
|
||||
case State::Pinch: {
|
||||
// _events.fire({ .type = Event::Type::PinchStop });
|
||||
} break;
|
||||
}
|
||||
|
||||
switch (state) {
|
||||
case State::Scroll: {
|
||||
_pendingScrollBegin = true;
|
||||
} break;
|
||||
case State::Fling: {
|
||||
Assert(was == State::Scroll);
|
||||
_events.fire({ .type = Event::Type::FlingStart });
|
||||
} break;
|
||||
case State::Pinch: {
|
||||
//_events.fire({ .type = Event::Type::PinchStart });
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
HRESULT DirectManipulation::Handler::OnViewportStatusChanged(
|
||||
IDirectManipulationViewport *viewport,
|
||||
DIRECTMANIPULATION_STATUS current,
|
||||
DIRECTMANIPULATION_STATUS previous) {
|
||||
Expects(viewport != nullptr);
|
||||
|
||||
if (current == previous) {
|
||||
return S_OK;
|
||||
} else if (current == DIRECTMANIPULATION_INERTIA) {
|
||||
if (previous != DIRECTMANIPULATION_RUNNING
|
||||
|| _state != State::Scroll) {
|
||||
return S_OK;
|
||||
}
|
||||
transitionToState(State::Fling);
|
||||
}
|
||||
|
||||
if (current == DIRECTMANIPULATION_RUNNING) {
|
||||
if (previous == DIRECTMANIPULATION_INERTIA) {
|
||||
transitionToState(State::None);
|
||||
}
|
||||
}
|
||||
|
||||
if (current != DIRECTMANIPULATION_READY) {
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
if (_scale != 1.0f || _xOffset != 0. || _yOffset != 0.) {
|
||||
const auto hr = viewport->ZoomToRect(0, 0, _width, _height, FALSE);
|
||||
if (!SUCCEEDED(hr)) {
|
||||
return hr;
|
||||
}
|
||||
}
|
||||
|
||||
_scale = 1.0f;
|
||||
_xOffset = 0.0f;
|
||||
_yOffset = 0.0f;
|
||||
|
||||
transitionToState(State::None);
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
HRESULT DirectManipulation::Handler::OnViewportUpdated(
|
||||
IDirectManipulationViewport *viewport) {
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
HRESULT DirectManipulation::Handler::OnContentUpdated(
|
||||
IDirectManipulationViewport *viewport,
|
||||
IDirectManipulationContent *content) {
|
||||
Expects(viewport != nullptr);
|
||||
Expects(content != nullptr);
|
||||
|
||||
float xform[6];
|
||||
const auto hr = content->GetContentTransform(xform, ARRAYSIZE(xform));
|
||||
if (!SUCCEEDED(hr)) {
|
||||
return hr;
|
||||
}
|
||||
|
||||
float scale = xform[0];
|
||||
float xOffset = xform[4];
|
||||
float yOffset = xform[5];
|
||||
|
||||
if (scale == 0.0f) {
|
||||
return hr;
|
||||
} else if (qFuzzyCompare(scale, _scale)
|
||||
&& xOffset == _xOffset
|
||||
&& yOffset == _yOffset) {
|
||||
return hr;
|
||||
}
|
||||
if (qFuzzyCompare(scale, 1.0f)) {
|
||||
if (_state == State::None) {
|
||||
transitionToState(State::Scroll);
|
||||
}
|
||||
} else {
|
||||
transitionToState(State::Pinch);
|
||||
}
|
||||
|
||||
auto getIntDeltaPart = [](float &was, float now) {
|
||||
if (was < now) {
|
||||
const auto result = std::floor(now - was);
|
||||
was += result;
|
||||
return int(result);
|
||||
} else {
|
||||
const auto result = std::floor(was - now);
|
||||
was -= result;
|
||||
return -int(result);
|
||||
}
|
||||
};
|
||||
const auto d = QPoint(
|
||||
getIntDeltaPart(_xOffset, xOffset),
|
||||
getIntDeltaPart(_yOffset, yOffset));
|
||||
if ((_state == State::Scroll || _state == State::Fling) && d.isNull()) {
|
||||
return S_OK;
|
||||
}
|
||||
if (_state == State::Scroll) {
|
||||
if (_pendingScrollBegin) {
|
||||
_events.fire({ .type = Event::Type::ScrollStart, .delta = d });
|
||||
_pendingScrollBegin = false;
|
||||
} else {
|
||||
_events.fire({ .type = Event::Type::Scroll, .delta = d });
|
||||
}
|
||||
} else if (_state == State::Fling) {
|
||||
_events.fire({ .type = Event::Type::Fling, .delta = d });
|
||||
} else {
|
||||
//_events.fire({ .type = Event::Type::Pinch, .delta = ... });
|
||||
}
|
||||
_scale = scale;
|
||||
|
||||
return hr;
|
||||
}
|
||||
|
||||
HRESULT DirectManipulation::Handler::OnInteraction(
|
||||
IDirectManipulationViewport2 *viewport,
|
||||
DIRECTMANIPULATION_INTERACTION_TYPE interaction) {
|
||||
if (interaction == DIRECTMANIPULATION_INTERACTION_BEGIN) {
|
||||
_interacting = true;
|
||||
} else if (interaction == DIRECTMANIPULATION_INTERACTION_END) {
|
||||
_interacting = false;
|
||||
}
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
DirectManipulation::DirectManipulation(not_null<RpWidget*> widget)
|
||||
: NativeEventFilter(widget)
|
||||
, _interacting([=] { _updateManager->Update(nullptr); }) {
|
||||
widget->sizeValue() | rpl::on_next([=](QSize size) {
|
||||
sizeUpdated(size * widget->devicePixelRatio());
|
||||
}, _lifetime);
|
||||
|
||||
widget->winIdValue() | rpl::on_next([=](WId winId) {
|
||||
destroy();
|
||||
|
||||
if (const auto hwnd = reinterpret_cast<HWND>(winId)) {
|
||||
if (init(hwnd)) {
|
||||
sizeUpdated(widget->size() * widget->devicePixelRatio());
|
||||
} else {
|
||||
destroy();
|
||||
}
|
||||
}
|
||||
}, _lifetime);
|
||||
}
|
||||
|
||||
DirectManipulation::~DirectManipulation() {
|
||||
destroy();
|
||||
}
|
||||
|
||||
void DirectManipulation::sizeUpdated(QSize nativeSize) {
|
||||
if (const auto handler = _handler.get()) {
|
||||
const auto r = QRect(QPoint(), nativeSize);
|
||||
handler->setViewportSize(r.size());
|
||||
if (const auto viewport = _viewport.get()) {
|
||||
const auto rect = RECT{ r.x(), r.y(), r.right(), r.bottom() };
|
||||
viewport->SetViewportRect(&rect);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool DirectManipulation::init(HWND hwnd) {
|
||||
if (!hwnd || !::Platform::IsWindows10OrGreater()) {
|
||||
return false;
|
||||
}
|
||||
_manager = base::WinRT::TryCreateInstance<IDirectManipulationManager>(
|
||||
CLSID_DirectManipulationManager);
|
||||
if (!_manager) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto hr = S_OK;
|
||||
hr = _manager->GetUpdateManager(IID_PPV_ARGS(_updateManager.put()));
|
||||
if (!SUCCEEDED(hr) || !_updateManager) {
|
||||
return false;
|
||||
}
|
||||
|
||||
hr = _manager->CreateViewport(
|
||||
nullptr,
|
||||
hwnd,
|
||||
IID_PPV_ARGS(_viewport.put()));
|
||||
if (!SUCCEEDED(hr) || !_viewport) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto configuration = DIRECTMANIPULATION_CONFIGURATION_INTERACTION
|
||||
| DIRECTMANIPULATION_CONFIGURATION_TRANSLATION_X
|
||||
| DIRECTMANIPULATION_CONFIGURATION_TRANSLATION_Y
|
||||
| DIRECTMANIPULATION_CONFIGURATION_TRANSLATION_INERTIA
|
||||
| DIRECTMANIPULATION_CONFIGURATION_RAILS_X
|
||||
| DIRECTMANIPULATION_CONFIGURATION_RAILS_Y;
|
||||
|
||||
hr = _viewport->ActivateConfiguration(configuration);
|
||||
if (!SUCCEEDED(hr)) {
|
||||
return false;
|
||||
}
|
||||
hr = _viewport->SetViewportOptions(
|
||||
DIRECTMANIPULATION_VIEWPORT_OPTIONS_MANUALUPDATE);
|
||||
if (!SUCCEEDED(hr)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
_handler.attach(new Handler());
|
||||
_handler->interacting(
|
||||
) | rpl::on_next([=](bool interacting) {
|
||||
base::Integration::Instance().enterFromEventLoop([&] {
|
||||
if (interacting) {
|
||||
_interacting.start();
|
||||
} else {
|
||||
_interacting.stop();
|
||||
}
|
||||
});
|
||||
}, _handler->lifetime());
|
||||
_handler->events() | rpl::on_next([=](Event &&event) {
|
||||
base::Integration::Instance().enterFromEventLoop([&] {
|
||||
_events.fire(std::move(event));
|
||||
});
|
||||
}, _handler->lifetime());
|
||||
|
||||
hr = _viewport->AddEventHandler(hwnd, _handler.get(), &_cookie);
|
||||
if (!SUCCEEDED(hr)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
RECT rect = { 0, 0, 1024, 1024 };
|
||||
hr = _viewport->SetViewportRect(&rect);
|
||||
if (!SUCCEEDED(hr)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
hr = _manager->Activate(hwnd);
|
||||
if (!SUCCEEDED(hr)) {
|
||||
return false;
|
||||
}
|
||||
_managerHandle = hwnd;
|
||||
|
||||
hr = _viewport->Enable();
|
||||
if (!SUCCEEDED(hr)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
hr = _updateManager->Update(nullptr);
|
||||
if (!SUCCEEDED(hr)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DirectManipulation::filterNativeEvent(
|
||||
UINT msg,
|
||||
WPARAM wParam,
|
||||
LPARAM lParam,
|
||||
LRESULT *result) {
|
||||
switch (msg) {
|
||||
|
||||
case DM_POINTERHITTEST:
|
||||
if (_viewport && GetPointerTypeSupported()) {
|
||||
const auto id = UINT32(GET_POINTERID_WPARAM(wParam));
|
||||
auto type = POINTER_INPUT_TYPE();
|
||||
if (GetPointerType(id, &type) && type == PT_TOUCHPAD) {
|
||||
_viewport->SetContact(id);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
auto DirectManipulation::events() const -> rpl::producer<Event> {
|
||||
return _events.events();
|
||||
}
|
||||
|
||||
void DirectManipulation::destroy() {
|
||||
_interacting.stop();
|
||||
|
||||
if (_handler) {
|
||||
_handler = nullptr;
|
||||
}
|
||||
|
||||
if (_viewport) {
|
||||
_viewport->Stop();
|
||||
if (_cookie) {
|
||||
_viewport->RemoveEventHandler(_cookie);
|
||||
_cookie = 0;
|
||||
}
|
||||
_viewport->Abandon();
|
||||
_viewport = nullptr;
|
||||
}
|
||||
|
||||
if (_updateManager) {
|
||||
_updateManager = nullptr;
|
||||
}
|
||||
|
||||
if (_manager) {
|
||||
if (_managerHandle) {
|
||||
_manager->Deactivate(_managerHandle);
|
||||
}
|
||||
_manager = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void ActivateDirectManipulation(not_null<RpWidget*> window) {
|
||||
auto dm = std::make_unique<DirectManipulation>(window);
|
||||
|
||||
dm->events(
|
||||
) | rpl::on_next([=](const DirectManipulationEvent &event) {
|
||||
using Type = DirectManipulationEventType;
|
||||
const auto send = [&](Qt::ScrollPhase phase) {
|
||||
const auto windowHandle = window->windowHandle();
|
||||
const auto hwnd = reinterpret_cast<HWND>(window->winId());
|
||||
if (!windowHandle || !hwnd) {
|
||||
return;
|
||||
}
|
||||
auto global = POINT();
|
||||
::GetCursorPos(&global);
|
||||
auto local = global;
|
||||
::ScreenToClient(hwnd, &local);
|
||||
const auto dpi = GetDpiForWindowSupported()
|
||||
? GetDpiForWindow(hwnd)
|
||||
: 0;
|
||||
const auto scale = dpi ? (96. / dpi) : 1.;
|
||||
const auto delta = QPointF(event.delta) * scale;
|
||||
const auto inverted = true;
|
||||
QWindowSystemInterface::handleWheelEvent(
|
||||
windowHandle,
|
||||
QWindowSystemInterfacePrivate::eventTime.elapsed(),
|
||||
QPointF(local.x, local.y),
|
||||
QPointF(global.x, global.y),
|
||||
delta.toPoint(),
|
||||
(delta * kPixelToAngleDelta).toPoint(),
|
||||
LookupModifiers(),
|
||||
phase,
|
||||
Qt::MouseEventSynthesizedBySystem,
|
||||
inverted);
|
||||
};
|
||||
switch (event.type) {
|
||||
case Type::ScrollStart: send(Qt::ScrollBegin); break;
|
||||
case Type::Scroll: send(Qt::ScrollUpdate); break;
|
||||
case Type::FlingStart:
|
||||
case Type::Fling: send(Qt::ScrollMomentum); break;
|
||||
case Type::ScrollStop: send(Qt::ScrollEnd); break;
|
||||
case Type::FlingStop: send(Qt::ScrollEnd); break;
|
||||
}
|
||||
}, window->lifetime());
|
||||
|
||||
window->lifetime().add([owned = std::move(dm)] {});
|
||||
}
|
||||
|
||||
} // namespace Ui::Platform
|
||||
@@ -0,0 +1,76 @@
|
||||
// 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/platform/win/base_windows_winrt.h"
|
||||
#include "ui/platform/win/ui_windows_native_event_filter.h"
|
||||
#include "ui/effects/animations.h"
|
||||
|
||||
#include <QtCore/QPoint>
|
||||
|
||||
#include <windows.h>
|
||||
#include <directmanipulation.h>
|
||||
|
||||
namespace Ui {
|
||||
class RpWidget;
|
||||
} // namespace Ui
|
||||
|
||||
namespace Ui::Platform {
|
||||
|
||||
enum class DirectManipulationEventType {
|
||||
ScrollStart,
|
||||
Scroll,
|
||||
ScrollStop,
|
||||
FlingStart,
|
||||
Fling,
|
||||
FlingStop,
|
||||
};
|
||||
|
||||
struct DirectManipulationEvent {
|
||||
using Type = DirectManipulationEventType;
|
||||
Type type = Type ();
|
||||
QPoint delta;
|
||||
};
|
||||
|
||||
class DirectManipulation final : public NativeEventFilter {
|
||||
public:
|
||||
explicit DirectManipulation(not_null<RpWidget*> widget);
|
||||
~DirectManipulation();
|
||||
|
||||
using Event = DirectManipulationEvent;
|
||||
[[nodiscard]] rpl::producer<Event> events() const;
|
||||
|
||||
private:
|
||||
class Handler;
|
||||
|
||||
bool init(HWND hwnd);
|
||||
void sizeUpdated(QSize nativeSize);
|
||||
void destroy();
|
||||
|
||||
bool filterNativeEvent(
|
||||
UINT msg,
|
||||
WPARAM wParam,
|
||||
LPARAM lParam,
|
||||
LRESULT *result) override;
|
||||
|
||||
winrt::com_ptr<IDirectManipulationManager> _manager;
|
||||
winrt::com_ptr<IDirectManipulationUpdateManager> _updateManager;
|
||||
winrt::com_ptr<IDirectManipulationViewport> _viewport;
|
||||
winrt::com_ptr<Handler> _handler;
|
||||
HWND _managerHandle = nullptr;
|
||||
DWORD _cookie = 0;
|
||||
//bool has_animation_observer_ = false;
|
||||
|
||||
Ui::Animations::Basic _interacting;
|
||||
rpl::event_stream<Event> _events;
|
||||
rpl::lifetime _lifetime;
|
||||
|
||||
};
|
||||
|
||||
void ActivateDirectManipulation(not_null<RpWidget*> window);
|
||||
|
||||
} // namespace Ui::Platform
|
||||
@@ -0,0 +1,172 @@
|
||||
// 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_windows_native_event_filter.h"
|
||||
|
||||
#include "base/integration.h"
|
||||
#include "ui/qt_object_factory.h"
|
||||
#include "ui/rp_widget.h"
|
||||
|
||||
#include <QtCore/QAbstractNativeEventFilter>
|
||||
#include <QtCore/QCoreApplication>
|
||||
|
||||
namespace Ui::Platform {
|
||||
|
||||
class NativeEventFilter::FilterSingleton final
|
||||
: public QAbstractNativeEventFilter {
|
||||
public:
|
||||
void registerFilter(HWND handle, not_null<NativeEventFilter*> filter);
|
||||
void unregisterFilter(HWND handle, not_null<NativeEventFilter*> filter);
|
||||
|
||||
bool nativeEventFilter(
|
||||
const QByteArray &eventType,
|
||||
void *message,
|
||||
native_event_filter_result *result) override;
|
||||
|
||||
private:
|
||||
using Change = std::pair<HWND, not_null<NativeEventFilter*>>;
|
||||
struct Filters {
|
||||
not_null<NativeEventFilter*> first;
|
||||
std::vector<not_null<NativeEventFilter*>> other;
|
||||
};
|
||||
|
||||
base::flat_map<HWND, Filters> _filtersByHandle;
|
||||
base::flat_set<Change> _adding;
|
||||
base::flat_set<Change> _removing;
|
||||
bool _processing = false;
|
||||
|
||||
};
|
||||
|
||||
void NativeEventFilter::FilterSingleton::registerFilter(
|
||||
HWND handle,
|
||||
not_null<NativeEventFilter*> filter) {
|
||||
if (_processing) {
|
||||
const auto change = std::make_pair(handle, filter);
|
||||
_removing.remove(change);
|
||||
_adding.emplace(change);
|
||||
return;
|
||||
}
|
||||
const auto i = _filtersByHandle.find(handle);
|
||||
if (i == end(_filtersByHandle)) {
|
||||
_filtersByHandle.emplace(handle, Filters{ .first = filter });
|
||||
} else {
|
||||
i->second.other.push_back(filter);
|
||||
}
|
||||
}
|
||||
|
||||
void NativeEventFilter::FilterSingleton::unregisterFilter(
|
||||
HWND handle,
|
||||
not_null<NativeEventFilter*> filter) {
|
||||
if (_processing) {
|
||||
const auto change = std::make_pair(handle, filter);
|
||||
_adding.remove(change);
|
||||
_removing.emplace(change);
|
||||
return;
|
||||
}
|
||||
const auto i = _filtersByHandle.find(handle);
|
||||
if (i != end(_filtersByHandle)) {
|
||||
if (i->second.first == filter) {
|
||||
if (i->second.other.empty()) {
|
||||
_filtersByHandle.erase(i);
|
||||
} else {
|
||||
i->second.first = i->second.other.back();
|
||||
i->second.other.pop_back();
|
||||
}
|
||||
} else {
|
||||
const auto j = ranges::find(i->second.other, filter);
|
||||
if (j != end(i->second.other)) {
|
||||
i->second.other.erase(j);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool NativeEventFilter::FilterSingleton::nativeEventFilter(
|
||||
const QByteArray &eventType,
|
||||
void *message,
|
||||
native_event_filter_result *result) {
|
||||
const auto msg = static_cast<MSG*>(message);
|
||||
const auto i = _filtersByHandle.find(msg->hwnd);
|
||||
if (i == end(_filtersByHandle)) {
|
||||
return false;
|
||||
}
|
||||
auto filtered = false;
|
||||
_processing = true;
|
||||
base::Integration::Instance().enterFromEventLoop([&] {
|
||||
const auto first = i->second.first;
|
||||
if (!_removing.contains(std::make_pair(msg->hwnd, first))) {
|
||||
filtered = first->filterNativeEvent(
|
||||
msg->message,
|
||||
msg->wParam,
|
||||
msg->lParam,
|
||||
reinterpret_cast<LRESULT*>(result));
|
||||
if (filtered) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
for (const auto other : i->second.other) {
|
||||
if (!_removing.contains(std::make_pair(msg->hwnd, other))) {
|
||||
filtered = other->filterNativeEvent(
|
||||
msg->message,
|
||||
msg->wParam,
|
||||
msg->lParam,
|
||||
reinterpret_cast<LRESULT*>(result));
|
||||
if (filtered) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
_processing = false;
|
||||
|
||||
const auto destroyed = (msg->message == WM_DESTROY);
|
||||
if (destroyed) {
|
||||
_filtersByHandle.erase(i);
|
||||
filtered = false;
|
||||
}
|
||||
for (const auto &change : _adding) {
|
||||
if (!destroyed || change.first != msg->hwnd) {
|
||||
registerFilter(change.first, change.second);
|
||||
}
|
||||
}
|
||||
for (const auto &change : _removing) {
|
||||
if (!destroyed || change.first != msg->hwnd) {
|
||||
unregisterFilter(change.first, change.second);
|
||||
}
|
||||
}
|
||||
|
||||
return filtered;
|
||||
}
|
||||
|
||||
NativeEventFilter::NativeEventFilter(not_null<RpWidget*> that) {
|
||||
that->winIdValue() | rpl::on_next([=](WId winId) {
|
||||
if (_hwnd) {
|
||||
Singleton()->unregisterFilter(_hwnd, this);
|
||||
}
|
||||
_hwnd = reinterpret_cast<HWND>(winId);
|
||||
if (_hwnd) {
|
||||
Singleton()->registerFilter(_hwnd, this);
|
||||
}
|
||||
}, that->lifetime());
|
||||
}
|
||||
|
||||
NativeEventFilter::~NativeEventFilter() {
|
||||
if (_hwnd) {
|
||||
Singleton()->unregisterFilter(_hwnd, this);
|
||||
}
|
||||
}
|
||||
|
||||
auto NativeEventFilter::Singleton() -> not_null<FilterSingleton*> {
|
||||
static const auto Instance = [&] {
|
||||
const auto application = QCoreApplication::instance();
|
||||
const auto filter = Ui::CreateChild<FilterSingleton>(application);
|
||||
application->installNativeEventFilter(filter);
|
||||
return filter;
|
||||
}();
|
||||
return Instance;
|
||||
}
|
||||
|
||||
} // namespace Ui::Platform
|
||||
@@ -0,0 +1,37 @@
|
||||
// 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 <Windows.h>
|
||||
|
||||
namespace Ui {
|
||||
class RpWidget;
|
||||
} // namespace Ui
|
||||
|
||||
namespace Ui::Platform {
|
||||
|
||||
class NativeEventFilter {
|
||||
public:
|
||||
explicit NativeEventFilter(not_null<RpWidget*> that);
|
||||
virtual ~NativeEventFilter();
|
||||
|
||||
virtual bool filterNativeEvent(
|
||||
UINT msg,
|
||||
WPARAM wParam,
|
||||
LPARAM lParam,
|
||||
LRESULT *result) = 0;
|
||||
|
||||
private:
|
||||
class FilterSingleton;
|
||||
friend class FilterSingleton;
|
||||
|
||||
[[nodiscard]] static not_null<FilterSingleton*> Singleton();
|
||||
HWND _hwnd = nullptr;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Ui::Platform
|
||||
Reference in New Issue
Block a user