Updated lib_ui sources to TDesktop version 2.3.2+d34eabd

This commit is contained in:
Eric Kotato 2020-08-28 19:29:01 +03:00
commit 58c35e9dcc
17 changed files with 602 additions and 169 deletions

View file

@ -4,7 +4,7 @@
# For license and copyright information please follow this link:
# https://github.com/desktop-app/legal/blob/master/LEGAL
add_library(lib_ui OBJECT)
add_library(lib_ui STATIC)
add_library(desktop-app::lib_ui ALIAS lib_ui)
init_target(lib_ui)
@ -84,6 +84,7 @@ PRIVATE
ui/platform/win/ui_window_win.h
ui/platform/win/ui_utility_win.cpp
ui/platform/win/ui_utility_win.h
ui/platform/ui_platform_window.cpp
ui/platform/ui_platform_window.h
ui/platform/ui_platform_utility.h
ui/style/style_core.cpp

View file

@ -536,20 +536,23 @@ mediaviewTransparentFg: #cccccc; // another transparent filling part
notificationBg: windowBg; // custom notification window background
// calls
callBg: #26282cf2; // phone call popup background
callBg: #26282cf2; // old phone call popup background
callBgOpaque: #1b1f23; // phone call popup background
callBgButton: #1b1f237f; // phone call window control buttons bg
callNameFg: #ffffff; // phone call popup name text
callFingerprintBg: #00000066; // phone call popup emoji fingerprint background
callStatusFg: #aaabac; // phone call popup status text
callIconFg: #ffffff; // phone call popup answer, hangup and mute mic icon
callIconBg: #ffffff1f; // phone call mute mic and camera button background
callIconFg: #ffffff; // phone call popup answer, hangup, mute mic and camera icon
callIconBgActive: #ffffffe5; // phone call line busy cancel, muted mic and camera button background
callIconFgActive: #222222; // phone call line busy cancel, muted mic and camera icon
callIconActiveRipple: #f1f1f1; // phone call line busy cancel, muted mic and camera ripple effect
callAnswerBg: #64c15b; // phone call popup answer button background
callAnswerRipple: #52b149; // phone call popup answer button ripple effect
callAnswerBgOuter: #50eb4126; // phone call popup answer button outer ripple effect
callHangupBg: #d75a5a; // phone call popup hangup button background
callHangupRipple: #c04646; // phone call popup hangup button ripple effect
callCancelBg: #ffffff; // phone call popup line busy cancel button background
callCancelFg: #777777; // phone call popup line busy cancel button icon
callCancelRipple: #f1f1f1; // phone call popup line busy cancel button ripple effect
callMuteRipple: #ffffff12; // phone call popup mute mic ripple effect
callMuteRipple: #ffffff12; // phone call popup mute mic and camera ripple effect
callBarBg: dialogsBgActive; // active phone call bar background
callBarMuteRipple: dialogsRippleBgActive; // active phone call bar mute and hangup button ripple effect

View file

@ -458,9 +458,9 @@ void prepareRound(
}
auto cornerWidth = cornerMasks[0].width();
auto cornerHeight = cornerMasks[0].height();
auto imageWidth = image.width();
auto imageHeight = image.height();
if (imageWidth < 2 * cornerWidth || imageHeight < 2 * cornerHeight) {
auto targetWidth = target.width();
auto targetHeight = target.height();
if (targetWidth < 2 * cornerWidth || targetHeight < 2 * cornerHeight) {
return;
}
@ -475,9 +475,9 @@ void prepareRound(
Assert(image.depth() == static_cast<int>((imageIntsPerPixel * sizeof(uint32)) << 3));
Assert(image.bytesPerLine() == (imageIntsPerLine << 2));
auto intsTopLeft = ints + target.x() + target.y() * imageIntsPerLine;
auto intsTopRight = ints + target.x() + target.width() - cornerWidth + target.y() * imageIntsPerLine;
auto intsBottomLeft = ints + target.x() + (target.y() + target.height() - cornerHeight) * imageIntsPerLine;
auto intsBottomRight = ints + target.x() + target.width() - cornerWidth + (target.y() + target.height() - cornerHeight) * imageIntsPerLine;
auto intsTopRight = ints + target.x() + targetWidth - cornerWidth + target.y() * imageIntsPerLine;
auto intsBottomLeft = ints + target.x() + (target.y() + targetHeight - cornerHeight) * imageIntsPerLine;
auto intsBottomRight = ints + target.x() + targetWidth - cornerWidth + (target.y() + targetHeight - cornerHeight) * imageIntsPerLine;
auto maskCorner = [&](uint32 *imageInts, const QImage &mask) {
auto maskWidth = mask.width();
auto maskHeight = mask.height();

View file

@ -9,7 +9,7 @@
namespace Ui {
namespace Platform {
std::unique_ptr<BasicWindowHelper> CreateWindowHelper(
std::unique_ptr<BasicWindowHelper> CreateSpecialWindowHelper(
not_null<RpWidget*> window) {
return nullptr;
}

View file

@ -24,19 +24,29 @@ public:
void setMinimumSize(QSize size) override;
void setFixedSize(QSize size) override;
void setGeometry(QRect rect) override;
void close() override;
private:
class Private;
friend class Private;
void setupBodyTitleAreaEvents() override;
void init();
void toggleCustomTitle(bool visible);
const not_null<RpWidget*> _window;
const std::unique_ptr<Private> _private;
const not_null<TitleWidget*> _title;
const not_null<RpWidget*> _body;
#ifdef OS_OSX
struct WindowDrag {
QPoint windowStartPosition;
QPoint dragStartPosition;
};
std::optional<WindowDrag> _drag;
#endif // OS_OSX
};
} // namespace Platform

View file

@ -7,10 +7,14 @@
#include "ui/platform/mac/ui_window_mac.h"
#include "ui/platform/mac/ui_window_title_mac.h"
#include "ui/widgets/window.h"
#include "base/platform/base_platform_info.h"
#include "styles/palette.h"
#include <QtCore/QCoreApplication>
#include <QtCore/QAbstractNativeEventFilter>
#include <QtGui/QWindow>
#include <QtGui/QtEvents>
#include <QtWidgets/QOpenGLWidget>
#include <Cocoa/Cocoa.h>
@ -78,13 +82,42 @@ private:
};
class EventFilter : public QObject, public QAbstractNativeEventFilter {
public:
EventFilter(not_null<QObject*> parent, Fn<bool(void*)> checkPerformDrag)
: QObject(parent)
, _checkPerformDrag(std::move(checkPerformDrag)) {
Expects(_checkPerformDrag != nullptr);
}
bool nativeEventFilter(
const QByteArray &eventType,
void *message,
long *result) {
NSEvent *e = static_cast<NSEvent*>(message);
return (e && [e type] == NSEventTypeLeftMouseDown)
? _checkPerformDrag([e window])
: false;
return false;
}
private:
Fn<bool(void*)> _checkPerformDrag;
};
} // namespace
class WindowHelper::Private final {
public:
explicit Private(not_null<WindowHelper*> owner);
~Private();
[[nodiscard]] int customTitleHeight() const;
[[nodiscard]] QRect controlsRect() const;
[[nodiscard]] bool checkNativeMove(void *nswindow) const;
void activateBeforeNativeMove();
void close();
private:
void init();
@ -113,32 +146,87 @@ WindowHelper::Private::Private(not_null<WindowHelper*> owner)
init();
}
WindowHelper::Private::~Private() {
[_observer release];
}
int WindowHelper::Private::customTitleHeight() const {
return _customTitleHeight;
}
Fn<void(bool)> WindowHelper::Private::toggleCustomTitleCallback() {
return [=](bool visible) {
_owner->toggleCustomTitle(visible);
QRect WindowHelper::Private::controlsRect() const {
const auto button = [&](NSWindowButton type) {
auto view = [_nativeWindow standardWindowButton:type];
if (!view) {
return QRect();
}
auto result = [view frame];
for (auto parent = [view superview]; parent != nil; parent = [parent superview]) {
const auto origin = [parent frame].origin;
result.origin.x += origin.x;
result.origin.y += origin.y;
}
return QRect(result.origin.x, result.origin.y, result.size.width, result.size.height);
};
auto result = QRect();
const auto buttons = {
NSWindowCloseButton,
NSWindowMiniaturizeButton,
NSWindowZoomButton,
};
for (const auto type : buttons) {
result = result.united(button(type));
}
return QRect(
result.x(),
[_nativeWindow frame].size.height - result.y() - result.height(),
result.width(),
result.height());
}
bool WindowHelper::Private::checkNativeMove(void *nswindow) const {
if (_nativeWindow != nswindow
|| ([_nativeWindow styleMask] & NSFullScreenWindowMask) == NSFullScreenWindowMask) {
return false;
}
const auto cgReal = [NSEvent mouseLocation];
const auto real = QPointF(cgReal.x, cgReal.y);
const auto cgFrame = [_nativeWindow frame];
const auto frame = QRectF(cgFrame.origin.x, cgFrame.origin.y, cgFrame.size.width, cgFrame.size.height);
const auto border = QMarginsF{ 3., 3., 3., 3. };
return frame.marginsRemoved(border).contains(real);
}
void WindowHelper::Private::activateBeforeNativeMove() {
[_nativeWindow makeKeyAndOrderFront:_nativeWindow];
}
void WindowHelper::Private::close() {
[_nativeWindow close];
}
Fn<void(bool)> WindowHelper::Private::toggleCustomTitleCallback() {
return crl::guard(_owner->window(), [=](bool visible) {
_owner->toggleCustomTitle(visible);
});
}
Fn<void()> WindowHelper::Private::enforceStyleCallback() {
return [=] {
return crl::guard(_owner->window(), [=] {
if (_nativeWindow && _customTitleHeight > 0) {
[_nativeWindow setStyleMask:[_nativeWindow styleMask] | NSFullSizeContentViewWindowMask];
}
};
});
}
void WindowHelper::Private::initOpenGL() {
auto forceOpenGL = std::make_unique<QOpenGLWidget>(_owner->_window);
auto forceOpenGL = std::make_unique<QOpenGLWidget>(_owner->window());
}
void WindowHelper::Private::resolveWeakPointers() {
_owner->_window->createWinId();
_owner->window()->createWinId();
_nativeView = reinterpret_cast<NSView*>(_owner->_window->winId());
_nativeView = reinterpret_cast<NSView*>(_owner->window()->winId());
_nativeWindow = _nativeView ? [_nativeView window] : nullptr;
Ensures(_nativeWindow != nullptr);
@ -189,14 +277,17 @@ void WindowHelper::Private::init() {
}
WindowHelper::WindowHelper(not_null<RpWidget*> window)
: _window(window)
: BasicWindowHelper(window)
, _private(std::make_unique<Private>(this))
, _title(_private->customTitleHeight()
? Ui::CreateChild<TitleWidget>(
_window.get(),
window.get(),
_private->customTitleHeight())
: nullptr)
, _body(Ui::CreateChild<RpWidget>(_window.get())) {
, _body(Ui::CreateChild<RpWidget>(window.get())) {
if (_title->shouldBeHidden()) {
toggleCustomTitle(false);
}
init();
}
@ -211,44 +302,92 @@ void WindowHelper::setTitle(const QString &title) {
if (_title) {
_title->setText(title);
}
_window->setWindowTitle(
window()->setWindowTitle(
(!_title || _title->isHidden()) ? title : QString());
}
void WindowHelper::setTitleStyle(const style::WindowTitle &st) {
if (_title) {
_title->setStyle(st);
if (_title->shouldBeHidden()) {
toggleCustomTitle(false);
}
}
}
void WindowHelper::toggleCustomTitle(bool visible) {
if (_title->shouldBeHidden()) {
visible = false;
}
if (!_title || _title->isHidden() != visible) {
return;
}
_title->setVisible(visible);
_window->setWindowTitle(visible ? QString() : _title->text());
window()->setWindowTitle(visible ? QString() : _title->text());
}
void WindowHelper::setMinimumSize(QSize size) {
_window->setMinimumSize(
window()->setMinimumSize(
size.width(),
(_title ? _title->height() : 0) + size.height());
}
void WindowHelper::setFixedSize(QSize size) {
_window->setFixedSize(
window()->setFixedSize(
size.width(),
(_title ? _title->height() : 0) + size.height());
}
void WindowHelper::setGeometry(QRect rect) {
_window->setGeometry(
window()->setGeometry(
rect.marginsAdded({ 0, (_title ? _title->height() : 0), 0, 0 }));
}
void WindowHelper::setupBodyTitleAreaEvents() {
#ifndef OS_OSX
const auto controls = _private->controlsRect();
qApp->installNativeEventFilter(new EventFilter(window(), [=](void *nswindow) {
const auto point = body()->mapFromGlobal(QCursor::pos());
if (_private->checkNativeMove(nswindow)
&& !controls.contains(point)
&& (bodyTitleAreaHit(point) & WindowTitleHitTestFlag::Move)) {
_private->activateBeforeNativeMove();
window()->windowHandle()->startSystemMove();
return true;
}
return false;
}));
#else // OS_OSX
// OS X 10.10 doesn't have performWindowDragWithEvent yet.
body()->events() | rpl::start_with_next([=](not_null<QEvent*> e) {
const auto hitTest = [&] {
return bodyTitleAreaHit(
static_cast<QMouseEvent*>(e.get())->pos());
};
if (e->type() == QEvent::MouseButtonRelease
&& (static_cast<QMouseEvent*>(e.get())->button()
== Qt::LeftButton)) {
_drag = std::nullopt;
} else if (e->type() == QEvent::MouseButtonPress
&& hitTest()
&& (static_cast<QMouseEvent*>(e.get())->button()
== Qt::LeftButton)) {
_drag = { window()->pos(), static_cast<QMouseEvent*>(e.get())->globalPos() };
} else if (e->type() == QEvent::MouseMove && _drag && !window()->isFullScreen()) {
const auto delta = static_cast<QMouseEvent*>(e.get())->globalPos() - _drag->dragStartPosition;
window()->move(_drag->windowStartPosition + delta);
}
}, body()->lifetime());
#endif // OS_OSX
}
void WindowHelper::close() {
_private->close();
}
void WindowHelper::init() {
rpl::combine(
_window->sizeValue(),
window()->sizeValue(),
_title->heightValue(),
_title->shownValue()
) | rpl::start_with_next([=](QSize size, int titleHeight, bool shown) {
@ -263,7 +402,7 @@ void WindowHelper::init() {
}, _body->lifetime());
}
std::unique_ptr<BasicWindowHelper> CreateWindowHelper(
std::unique_ptr<BasicWindowHelper> CreateSpecialWindowHelper(
not_null<RpWidget*> window) {
return std::make_unique<WindowHelper>(window);
}

View file

@ -29,6 +29,7 @@ public:
void setText(const QString &text);
void setStyle(const style::WindowTitle &st);
[[nodiscard]] QString text() const;
[[nodiscard]] bool shouldBeHidden() const;
protected:
void paintEvent(QPaintEvent *e) override;

View file

@ -38,6 +38,10 @@ void TitleWidget::setStyle(const style::WindowTitle &st) {
update();
}
bool TitleWidget::shouldBeHidden() const {
return !_st->height;
}
QString TitleWidget::text() const {
return _text;
}

View file

@ -0,0 +1,125 @@
// 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/ui_platform_window.h"
#include "ui/widgets/window.h"
#include <QtGui/QWindow>
#include <QtGui/QtEvents>
namespace Ui {
namespace Platform {
BasicWindowHelper::BasicWindowHelper(not_null<RpWidget*> window)
: _window(window) {
}
not_null<RpWidget*> BasicWindowHelper::body() {
return _window;
}
void BasicWindowHelper::setTitle(const QString &title) {
_window->setWindowTitle(title);
}
void BasicWindowHelper::setTitleStyle(const style::WindowTitle &st) {
}
void BasicWindowHelper::setMinimumSize(QSize size) {
_window->setMinimumSize(size);
}
void BasicWindowHelper::setFixedSize(QSize size) {
_window->setFixedSize(size);
}
void BasicWindowHelper::setGeometry(QRect rect) {
_window->setGeometry(rect);
}
void BasicWindowHelper::showFullScreen() {
_window->showFullScreen();
}
void BasicWindowHelper::showNormal() {
_window->showNormal();
}
void BasicWindowHelper::close() {
_window->close();
}
void BasicWindowHelper::setBodyTitleArea(
Fn<WindowTitleHitTestFlags(QPoint)> testMethod) {
Expects(!_bodyTitleAreaTestMethod);
if (!testMethod) {
return;
}
_bodyTitleAreaTestMethod = std::move(testMethod);
setupBodyTitleAreaEvents();
}
void BasicWindowHelper::setupBodyTitleAreaEvents() {
// This is not done on macOS, because startSystemMove
// doesn't work from event handler there.
body()->events() | rpl::start_with_next([=](not_null<QEvent*> e) {
const auto hitTest = [&] {
return bodyTitleAreaHit(
static_cast<QMouseEvent*>(e.get())->pos());
};
if (e->type() == QEvent::MouseButtonDblClick) {
_mousePressed = false;
const auto hit = hitTest();
if (hit & WindowTitleHitTestFlag::Maximize) {
const auto state = _window->windowState();
if (state & Qt::WindowMaximized) {
_window->setWindowState(state & ~Qt::WindowMaximized);
} else {
_window->setWindowState(state | Qt::WindowMaximized);
}
} else if (hit & WindowTitleHitTestFlag::FullScreen) {
if (_window->isFullScreen()) {
showNormal();
} else {
showFullScreen();
}
}
} else if (e->type() == QEvent::MouseButtonRelease) {
_mousePressed = false;
} else if (e->type() == QEvent::MouseButtonPress
&& (static_cast<QMouseEvent*>(e.get())->button()
== Qt::LeftButton)) {
_mousePressed = true;
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) || defined DESKTOP_APP_QT_PATCHED
} else if (e->type() == QEvent::MouseMove) {
const auto mouseEvent = static_cast<QMouseEvent*>(e.get());
if (_mousePressed
#ifndef Q_OS_WIN // We handle fullscreen startSystemMove() only on Windows.
&& !_window->isFullScreen()
#endif // !Q_OS_WIN
&& (hitTest() & WindowTitleHitTestFlag::Move)) {
#ifdef Q_OS_WIN
if (_window->isFullScreen()) {
// On Windows we just jump out of fullscreen
// like we do automatically for dragging a window
// by title bar in a maximized state.
showNormal();
}
#endif // Q_OS_WIN
_mousePressed = false;
_window->windowHandle()->startSystemMove();
}
#endif // Qt >= 5.15 || DESKTOP_APP_QT_PATCHED
}
}, body()->lifetime());
}
} // namespace Platform
} // namespace Ui

View file

@ -6,6 +6,8 @@
//
#pragma once
#include "base/flags.h"
namespace style {
struct WindowTitle;
} // namespace style
@ -13,23 +15,58 @@ struct WindowTitle;
namespace Ui {
class RpWidget;
enum class WindowTitleHitTestFlag;
using WindowTitleHitTestFlags = base::flags<WindowTitleHitTestFlag>;
namespace Platform {
class BasicWindowHelper {
public:
[[nodiscard]] virtual not_null<RpWidget*> body() = 0;
virtual void setTitle(const QString &title) = 0;
virtual void setTitleStyle(const style::WindowTitle &st) = 0;
virtual void setMinimumSize(QSize size) = 0;
virtual void setFixedSize(QSize size) = 0;
virtual void setGeometry(QRect rect) = 0;
explicit BasicWindowHelper(not_null<RpWidget*> window);
virtual ~BasicWindowHelper() = default;
[[nodiscard]] virtual not_null<RpWidget*> body();
virtual void setTitle(const QString &title);
virtual void setTitleStyle(const style::WindowTitle &st);
virtual void setMinimumSize(QSize size);
virtual void setFixedSize(QSize size);
virtual void setGeometry(QRect rect);
virtual void showFullScreen();
virtual void showNormal();
virtual void close();
void setBodyTitleArea(Fn<WindowTitleHitTestFlags(QPoint)> testMethod);
protected:
[[nodiscard]] not_null<RpWidget*> window() const {
return _window;
}
[[nodiscard]] WindowTitleHitTestFlags bodyTitleAreaHit(
QPoint point) const {
return _bodyTitleAreaTestMethod
? _bodyTitleAreaTestMethod(point)
: WindowTitleHitTestFlag();
}
private:
virtual void setupBodyTitleAreaEvents();
const not_null<RpWidget*> _window;
Fn<WindowTitleHitTestFlags(QPoint)> _bodyTitleAreaTestMethod;
bool _mousePressed = false;
};
[[nodiscard]] std::unique_ptr<BasicWindowHelper> CreateWindowHelper(
[[nodiscard]] std::unique_ptr<BasicWindowHelper> CreateSpecialWindowHelper(
not_null<RpWidget*> window);
[[nodiscard]] inline std::unique_ptr<BasicWindowHelper> CreateWindowHelper(
not_null<RpWidget*> window) {
if (auto special = CreateSpecialWindowHelper(window)) {
return special;
}
return std::make_unique<BasicWindowHelper>(window);
}
} // namespace Platform
} // namespace Ui

View file

@ -19,39 +19,60 @@
namespace Ui {
namespace Platform {
TitleWidget::TitleWidget(not_null<RpWidget*> parent)
: RpWidget(parent)
, _st(&st::defaultWindowTitle)
, _minimize(this, _st->minimize)
, _maximizeRestore(this, _st->maximize)
, _close(this, _st->close)
, _shadow(this, st::titleShadow)
, _maximizedState(parent->windowState() & Qt::WindowMaximized)
TitleControls::TitleControls(
not_null<RpWidget*> parent,
const style::WindowTitle &st,
Fn<void(bool maximized)> maximize)
: _st(&st)
, _minimize(parent, _st->minimize)
, _maximizeRestore(parent, _st->maximize)
, _close(parent, _st->close)
, _maximizedState(parent->windowState()
& (Qt::WindowMaximized | Qt::WindowFullScreen))
, _activeState(parent->isActiveWindow()) {
init();
}
init(std::move(maximize));
void TitleWidget::setText(const QString &text) {
window()->setWindowTitle(text);
}
void TitleWidget::setStyle(const style::WindowTitle &st) {
_st = &st;
setGeometry(0, 0, window()->width(), _st->height);
_close->paintRequest(
) | rpl::start_with_next([=] {
const auto active = window()->isActiveWindow();
if (_activeState != active) {
_activeState = active;
updateButtonsState();
update();
}
}, _close->lifetime());
}
not_null<RpWidget*> TitleWidget::window() const {
return static_cast<RpWidget*>(parentWidget());
void TitleControls::setStyle(const style::WindowTitle &st) {
_st = &st;
updateButtonsState();
}
void TitleWidget::setResizeEnabled(bool enabled) {
_resizeEnabled = enabled;
updateControlsVisibility();
not_null<const style::WindowTitle*> TitleControls::st() const {
return _st;
}
void TitleWidget::init() {
QRect TitleControls::geometry() const {
auto result = QRect();
const auto add = [&](auto &&control) {
if (!control->isHidden()) {
result = result.united(control->geometry());
}
};
add(_minimize);
add(_maximizeRestore);
add(_close);
return result;
}
not_null<RpWidget*> TitleControls::parent() const {
return static_cast<RpWidget*>(_close->parentWidget());
}
not_null<QWidget*> TitleControls::window() const {
return _close->window();
}
void TitleControls::init(Fn<void(bool maximized)> maximize) {
_minimize->setClickedCallback([=] {
window()->setWindowState(
window()->windowState() | Qt::WindowMinimized);
@ -59,9 +80,13 @@ void TitleWidget::init() {
});
_minimize->setPointerCursor(false);
_maximizeRestore->setClickedCallback([=] {
if (maximize) {
maximize(!_maximizedState);
} else {
window()->setWindowState(_maximizedState
? Qt::WindowNoState
: Qt::WindowMaximized);
}
_maximizeRestore->clearState();
});
_maximizeRestore->setPointerCursor(false);
@ -71,32 +96,32 @@ void TitleWidget::init() {
});
_close->setPointerCursor(false);
setAttribute(Qt::WA_OpaquePaintEvent);
window()->widthValue(
parent()->widthValue(
) | rpl::start_with_next([=](int width) {
setGeometry(0, 0, width, _st->height);
}, lifetime());
updateControlsPosition();
}, _close->lifetime());
window()->createWinId();
connect(
QObject::connect(
window()->windowHandle(),
&QWindow::windowStateChanged,
[=](Qt::WindowState state) { handleWindowStateChanged(state); });
_activeState = isActiveWindow();
_activeState = parent()->isActiveWindow();
updateButtonsState();
}
void TitleWidget::paintEvent(QPaintEvent *e) {
const auto active = isActiveWindow();
if (_activeState != active) {
_activeState = active;
updateButtonsState();
}
QPainter(this).fillRect(e->rect(), active ? _st->bgActive : _st->bg);
void TitleControls::setResizeEnabled(bool enabled) {
_resizeEnabled = enabled;
updateControlsVisibility();
}
void TitleWidget::updateControlsPosition() {
void TitleControls::raise() {
_minimize->raise();
_maximizeRestore->raise();
_close->raise();
}
void TitleControls::updateControlsPosition() {
auto right = 0;
_close->moveToRight(right, 0); right += _close->width();
_maximizeRestore->moveToRight(right, 0);
@ -106,28 +131,25 @@ void TitleWidget::updateControlsPosition() {
_minimize->moveToRight(right, 0);
}
void TitleWidget::resizeEvent(QResizeEvent *e) {
updateControlsPosition();
_shadow->setGeometry(0, height() - st::lineWidth, width(), st::lineWidth);
}
void TitleWidget::updateControlsVisibility() {
void TitleControls::updateControlsVisibility() {
_maximizeRestore->setVisible(_resizeEnabled);
updateControlsPosition();
update();
}
void TitleWidget::handleWindowStateChanged(Qt::WindowState state) {
if (state == Qt::WindowMinimized) return;
void TitleControls::handleWindowStateChanged(Qt::WindowState state) {
if (state == Qt::WindowMinimized) {
return;
}
auto maximized = (state == Qt::WindowMaximized);
auto maximized = (state == Qt::WindowMaximized)
|| (state == Qt::WindowFullScreen);
if (_maximizedState != maximized) {
_maximizedState = maximized;
updateButtonsState();
}
}
void TitleWidget::updateButtonsState() {
void TitleControls::updateButtonsState() {
const auto minimize = _activeState
? &_st->minimizeIconActive
: &_st->minimize.icon;
@ -161,12 +183,45 @@ void TitleWidget::updateButtonsState() {
_close->setIconOverride(close, closeOver);
}
TitleWidget::TitleWidget(not_null<RpWidget*> parent)
: RpWidget(parent)
, _controls(this, st::defaultWindowTitle)
, _shadow(this, st::titleShadow) {
setAttribute(Qt::WA_OpaquePaintEvent);
parent->widthValue(
) | rpl::start_with_next([=](int width) {
setGeometry(0, 0, width, _controls.st()->height);
}, lifetime());
}
void TitleWidget::setText(const QString &text) {
window()->setWindowTitle(text);
}
void TitleWidget::setStyle(const style::WindowTitle &st) {
_controls.setStyle(st);
setGeometry(0, 0, window()->width(), _controls.st()->height);
update();
}
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) {
_shadow->setGeometry(0, height() - st::lineWidth, width(), st::lineWidth);
}
HitTestResult TitleWidget::hitTest(QPoint point) const {
if (false
|| (_minimize->geometry().contains(point))
|| (_maximizeRestore->geometry().contains(point))
|| (_close->geometry().contains(point))
) {
if (_controls.geometry().contains(point)) {
return HitTestResult::SysButton;
} else if (rect().contains(point)) {
return HitTestResult::Caption;

View file

@ -38,6 +38,41 @@ enum class HitTestResult {
TopLeft,
};
class TitleControls final {
public:
TitleControls(
not_null<RpWidget*> parent,
const style::WindowTitle &st,
Fn<void(bool maximized)> maximize = nullptr);
void setStyle(const style::WindowTitle &st);
[[nodiscard]] not_null<const style::WindowTitle*> st() const;
[[nodiscard]] QRect geometry() const;
void setResizeEnabled(bool enabled);
void raise();
private:
[[nodiscard]] not_null<RpWidget*> parent() const;
[[nodiscard]] not_null<QWidget*> window() const;
void init(Fn<void(bool maximized)> maximize);
void updateControlsVisibility();
void updateButtonsState();
void updateControlsPosition();
void handleWindowStateChanged(Qt::WindowState state = Qt::WindowNoState);
not_null<const style::WindowTitle*> _st;
object_ptr<Ui::IconButton> _minimize;
object_ptr<Ui::IconButton> _maximizeRestore;
object_ptr<Ui::IconButton> _close;
bool _maximizedState = false;
bool _activeState = false;
bool _resizeEnabled = true;
};
class TitleWidget : public RpWidget {
public:
explicit TitleWidget(not_null<RpWidget*> parent);
@ -52,25 +87,9 @@ protected:
void resizeEvent(QResizeEvent *e) override;
private:
not_null<RpWidget*> window() const;
void init();
void handleWindowStateChanged(Qt::WindowState state = Qt::WindowNoState);
void updateControlsVisibility();
void updateButtonsState();
void updateControlsPosition();
not_null<const style::WindowTitle*> _st;
object_ptr<Ui::IconButton> _minimize;
object_ptr<Ui::IconButton> _maximizeRestore;
object_ptr<Ui::IconButton> _close;
TitleControls _controls;
object_ptr<Ui::PlainShadow> _shadow;
bool _maximizedState = false;
bool _activeState = false;
bool _resizeEnabled = true;
};
} // namespace Platform

View file

@ -93,11 +93,11 @@ bool WindowHelper::NativeFilter::nativeEventFilter(
}
WindowHelper::WindowHelper(not_null<RpWidget*> window)
: _window(window)
, _handle(GetWindowHandle(_window))
, _title(Ui::CreateChild<TitleWidget>(_window.get()))
, _body(Ui::CreateChild<RpWidget>(_window.get()))
, _shadow(_window, st::windowShadowFg->c) {
: BasicWindowHelper(window)
, _handle(GetWindowHandle(window))
, _title(Ui::CreateChild<TitleWidget>(window.get()))
, _body(Ui::CreateChild<RpWidget>(window.get()))
, _shadow(window, st::windowShadowFg->c) {
Expects(_handle != nullptr);
GetNativeFilter()->registerWindow(_handle, this);
@ -114,7 +114,7 @@ not_null<RpWidget*> WindowHelper::body() {
void WindowHelper::setTitle(const QString &title) {
_title->setText(title);
_window->setWindowTitle(title);
window()->setWindowTitle(title);
}
void WindowHelper::setTitleStyle(const style::WindowTitle &st) {
@ -122,27 +122,43 @@ void WindowHelper::setTitleStyle(const style::WindowTitle &st) {
}
void WindowHelper::setMinimumSize(QSize size) {
_window->setMinimumSize(size.width(), _title->height() + size.height());
window()->setMinimumSize(size.width(), _title->height() + size.height());
}
void WindowHelper::setFixedSize(QSize size) {
_window->setFixedSize(size.width(), _title->height() + size.height());
window()->setFixedSize(size.width(), _title->height() + size.height());
_title->setResizeEnabled(false);
_shadow.setResizeEnabled(false);
}
void WindowHelper::setGeometry(QRect rect) {
_window->setGeometry(rect.marginsAdded({ 0, _title->height(), 0, 0 }));
window()->setGeometry(rect.marginsAdded({ 0, _title->height(), 0, 0 }));
}
void WindowHelper::showFullScreen() {
if (!_isFullScreen) {
_isFullScreen = true;
updateMargins();
}
window()->showFullScreen();
}
void WindowHelper::showNormal() {
window()->showNormal();
if (_isFullScreen) {
_isFullScreen = false;
updateMargins();
}
}
void WindowHelper::init() {
style::PaletteChanged(
) | rpl::start_with_next([=] {
_shadow.setColor(st::windowShadowFg->c);
}, _window->lifetime());
}, window()->lifetime());
rpl::combine(
_window->sizeValue(),
window()->sizeValue(),
_title->heightValue()
) | rpl::start_with_next([=](QSize size, int titleHeight) {
_body->setGeometry(
@ -165,14 +181,14 @@ void WindowHelper::init() {
const auto handleStateChanged = [=](Qt::WindowState state) {
updateSystemMenu(state);
if (fixedSize() && (state & Qt::WindowMaximized)) {
crl::on_main(_window.get(), [=] {
_window->setWindowState(
_window->windowState() & ~Qt::WindowMaximized);
crl::on_main(window().get(), [=] {
window()->setWindowState(
window()->windowState() & ~Qt::WindowMaximized);
});
}
};
Ui::Connect(
_window->windowHandle(),
window()->windowHandle(),
&QWindow::windowStateChanged,
handleStateChanged);
}
@ -186,14 +202,14 @@ bool WindowHelper::handleNativeEvent(
case WM_ACTIVATE: {
if (LOWORD(wParam) == WA_CLICKACTIVE) {
Ui::MarkInactivePress(_window, true);
Ui::MarkInactivePress(window(), true);
}
if (LOWORD(wParam) != WA_INACTIVE) {
_shadow.update(WindowShadow::Change::Activate);
} else {
_shadow.update(WindowShadow::Change::Deactivate);
}
_window->update();
window()->update();
} return false;
case WM_NCPAINT: {
@ -267,14 +283,14 @@ bool WindowHelper::handleNativeEvent(
|| wParam == SIZE_RESTORED
|| wParam == SIZE_MINIMIZED) {
if (wParam != SIZE_RESTORED
|| _window->windowState() != Qt::WindowNoState) {
|| window()->windowState() != Qt::WindowNoState) {
Qt::WindowState state = Qt::WindowNoState;
if (wParam == SIZE_MAXIMIZED) {
state = Qt::WindowMaximized;
} else if (wParam == SIZE_MINIMIZED) {
state = Qt::WindowMinimized;
}
emit _window->windowHandle()->windowStateChanged(state);
emit window()->windowHandle()->windowStateChanged(state);
}
updateMargins();
const auto changes = (wParam == SIZE_MINIMIZED
@ -310,7 +326,7 @@ bool WindowHelper::handleNativeEvent(
const auto mapped = QPoint(
p.x - r.left + _marginsDelta.left(),
p.y - r.top + _marginsDelta.top());
if (!_window->rect().contains(mapped)) {
if (!window()->rect().contains(mapped)) {
*result = HTTRANSPARENT;
} else if (!_title->geometry().contains(mapped)) {
*result = HTCLIENT;
@ -338,7 +354,7 @@ bool WindowHelper::handleNativeEvent(
case WM_SYSCOMMAND: {
if (wParam == SC_MOUSEMENU && !fixedSize()) {
POINTS p = MAKEPOINTS(lParam);
updateSystemMenu(_window->windowHandle()->windowState());
updateSystemMenu(window()->windowHandle()->windowState());
TrackPopupMenu(
_menu,
TPM_LEFTALIGN | TPM_TOPALIGN | TPM_LEFTBUTTON,
@ -357,19 +373,19 @@ bool WindowHelper::handleNativeEvent(
const auto command = LOWORD(wParam);
switch (command) {
case SC_CLOSE:
_window->close();
window()->close();
return true;
case SC_MINIMIZE:
_window->setWindowState(
_window->windowState() | Qt::WindowMinimized);
window()->setWindowState(
window()->windowState() | Qt::WindowMinimized);
return true;
case SC_MAXIMIZE:
if (!fixedSize()) {
_window->setWindowState(Qt::WindowMaximized);
window()->setWindowState(Qt::WindowMaximized);
}
return true;
case SC_RESTORE:
_window->setWindowState(Qt::WindowNoState);
window()->setWindowState(Qt::WindowNoState);
return true;
}
} return true;
@ -379,7 +395,7 @@ bool WindowHelper::handleNativeEvent(
}
bool WindowHelper::fixedSize() const {
return _window->minimumSize() == _window->maximumSize();
return window()->minimumSize() == window()->maximumSize();
}
void WindowHelper::updateMargins() {
@ -442,16 +458,19 @@ void WindowHelper::updateMargins() {
_marginsDelta = QMargins();
}
if (_isFullScreen) {
margins = QMargins();
}
if (const auto native = QGuiApplication::platformNativeInterface()) {
native->setWindowProperty(
_window->windowHandle()->handle(),
window()->windowHandle()->handle(),
"WindowsCustomMargins",
QVariant::fromValue<QMargins>(margins));
}
}
void WindowHelper::updateSystemMenu() {
updateSystemMenu(_window->windowHandle()->windowState());
updateSystemMenu(window()->windowHandle()->windowState());
}
void WindowHelper::updateSystemMenu(Qt::WindowState state) {
@ -518,7 +537,7 @@ HWND GetWindowHandle(not_null<RpWidget*> widget) {
window));
}
std::unique_ptr<BasicWindowHelper> CreateWindowHelper(
std::unique_ptr<BasicWindowHelper> CreateSpecialWindowHelper(
not_null<RpWidget*> window) {
return std::make_unique<WindowHelper>(window);
}

View file

@ -27,6 +27,8 @@ public:
void setMinimumSize(QSize size) override;
void setFixedSize(QSize size) override;
void setGeometry(QRect rect) override;
void showFullScreen() override;
void showNormal() override;
private:
class NativeFilter;
@ -45,7 +47,6 @@ private:
static not_null<NativeFilter*> GetNativeFilter();
const not_null<RpWidget*> _window;
const HWND _handle = nullptr;
const not_null<TitleWidget*> _title;
const not_null<RpWidget*> _body;
@ -53,6 +54,7 @@ private:
bool _updatingMargins = false;
QMargins _marginsDelta;
HMENU _menu = nullptr;
bool _isFullScreen = false;
};

View file

@ -382,6 +382,7 @@ CallButton {
angle: double;
outerRadius: pixels;
outerBg: color;
label: FlatLabel;
}
Menu {

View file

@ -13,55 +13,56 @@ namespace Ui {
Window::Window(QWidget *parent)
: RpWidget(parent)
, _helper(Platform::CreateWindowHelper(this)) {
Expects(_helper != nullptr);
hide();
}
Window::~Window() = default;
not_null<RpWidget*> Window::body() {
return _helper ? _helper->body() : this;
return _helper->body();
}
not_null<const RpWidget*> Window::body() const {
return _helper ? _helper->body().get() : this;
return _helper->body().get();
}
void Window::setTitle(const QString &title) {
if (_helper) {
_helper->setTitle(title);
} else {
setWindowTitle(title);
}
}
void Window::setTitleStyle(const style::WindowTitle &st) {
if (_helper) {
_helper->setTitleStyle(st);
}
}
void Window::setMinimumSize(QSize size) {
if (_helper) {
_helper->setMinimumSize(size);
} else {
RpWidget::setMinimumSize(size);
}
}
void Window::setFixedSize(QSize size) {
if (_helper) {
_helper->setFixedSize(size);
} else {
RpWidget::setFixedSize(size);
}
}
void Window::setGeometry(QRect rect) {
if (_helper) {
_helper->setGeometry(rect);
} else {
RpWidget::setGeometry(rect);
}
void Window::showFullScreen() {
_helper->showFullScreen();
}
void Window::showNormal() {
_helper->showNormal();
}
void Window::close() {
_helper->close();
}
void Window::setBodyTitleArea(
Fn<WindowTitleHitTestFlags(QPoint)> testMethod) {
_helper->setBodyTitleArea(std::move(testMethod));
}
} // namespace Ui

View file

@ -7,6 +7,7 @@
#pragma once
#include "ui/rp_widget.h"
#include "base/flags.h"
namespace style {
struct WindowTitle;
@ -17,6 +18,17 @@ namespace Platform {
class BasicWindowHelper;
} // namespace Platform
enum class WindowTitleHitTestFlag {
None = 0x00,
Move = 0x01,
Maximize = 0x02,
FullScreen = 0x04,
};
inline constexpr bool is_flag_type(WindowTitleHitTestFlag) {
return true;
}
using WindowTitleHitTestFlags = base::flags<WindowTitleHitTestFlag>;
class Window : public RpWidget {
public:
explicit Window(QWidget *parent = nullptr);
@ -30,6 +42,10 @@ public:
void setMinimumSize(QSize size);
void setFixedSize(QSize size);
void setGeometry(QRect rect);
void showFullScreen();
void showNormal();
void close();
void setBodyTitleArea(Fn<WindowTitleHitTestFlags(QPoint)> testMethod);
private:
const std::unique_ptr<Platform::BasicWindowHelper> _helper;