Allow arbitrary paddings around PopupMenu.

This commit is contained in:
John Preston 2022-08-17 21:03:06 +03:00
parent 1cc74a41c4
commit a76cdf7edf
16 changed files with 209 additions and 79 deletions

View file

@ -438,7 +438,7 @@ bool IsApplicationActive() {
return QApplication::activeWindow() != nullptr;
}
bool TranslucentWindowsSupported(QPoint globalPosition) {
bool TranslucentWindowsSupported() {
if (::Platform::IsWayland()) {
return true;
}

View file

@ -13,7 +13,7 @@
namespace Ui {
namespace Platform {
inline bool TranslucentWindowsSupported(QPoint globalPosition) {
inline bool TranslucentWindowsSupported() {
return true;
}

View file

@ -17,7 +17,7 @@ namespace Platform {
[[nodiscard]] bool IsApplicationActive();
[[nodiscard]] bool TranslucentWindowsSupported(QPoint globalPosition);
[[nodiscard]] bool TranslucentWindowsSupported();
void InitOnTopPanel(not_null<QWidget*> panel);
void DeInitOnTopPanel(not_null<QWidget*> panel);

View file

@ -339,8 +339,7 @@ QMargins DefaultWindowHelper::frameMargins() {
}
bool DefaultWindowHelper::hasShadow() const {
const auto center = window()->geometry().center();
return WindowExtentsSupported() && TranslucentWindowsSupported(center);
return WindowExtentsSupported() && TranslucentWindowsSupported();
}
QMargins DefaultWindowHelper::resizeArea() const {

View file

@ -16,7 +16,7 @@ class QPaintEvent;
namespace Ui {
namespace Platform {
inline bool TranslucentWindowsSupported(QPoint globalPosition) {
inline bool TranslucentWindowsSupported() {
return true;
}

View file

@ -62,8 +62,9 @@ not_null<QAction*> DropdownMenu::addAction(const QString &text, Fn<void()> callb
return _menu->addAction(text, std::move(callback), icon, iconOver);
}
not_null<QAction*> DropdownMenu::addSeparator() {
return _menu->addSeparator();
not_null<QAction*> DropdownMenu::addSeparator(
const style::MenuSeparator *st) {
return _menu->addSeparator(st);
}
void DropdownMenu::clearActions() {

View file

@ -18,7 +18,8 @@ public:
not_null<QAction*> addAction(base::unique_qptr<Menu::ItemBase> widget);
not_null<QAction*> addAction(const QString &text, Fn<void()> callback, const style::icon *icon = nullptr, const style::icon *iconOver = nullptr);
not_null<QAction*> addSeparator();
not_null<QAction*> addSeparator(
const style::MenuSeparator *st = nullptr);
void clearActions();
void setHiddenCallback(Fn<void()> callback) {

View file

@ -10,6 +10,7 @@
#include "ui/widgets/menu/menu_item_base.h"
#include "ui/widgets/menu/menu_separator.h"
#include "ui/widgets/scroll_area.h"
#include "styles/style_widgets.h"
#include <QtGui/QtEvents>
@ -191,10 +192,14 @@ not_null<QAction*> Menu::addAction(base::unique_qptr<ItemBase> widget) {
return action;
}
not_null<QAction*> Menu::addSeparator() {
not_null<QAction*> Menu::addSeparator(const style::MenuSeparator *st) {
const auto separator = new QAction(this);
separator->setSeparator(true);
auto item = base::make_unique_q<Separator>(this, _st, separator);
auto item = base::make_unique_q<Separator>(
this,
_st,
st ? *st : _st.separator,
separator);
return addAction(std::move(item));
}

View file

@ -9,7 +9,6 @@
#include "base/unique_qptr.h"
#include "ui/rp_widget.h"
#include "ui/widgets/menu/menu_common.h"
#include "styles/style_widgets.h"
#include <QtWidgets/QMenu>
@ -17,6 +16,15 @@ namespace Ui {
struct ScrollToRequest;
} // namespace Ui
namespace style {
struct Menu;
struct MenuSeparator;
} // namespace style
namespace st {
extern const style::Menu &defaultMenu;
} // namespace st
namespace Ui::Menu {
class ItemBase;
@ -43,7 +51,8 @@ public:
std::unique_ptr<QMenu> submenu,
const style::icon *icon = nullptr,
const style::icon *iconOver = nullptr);
not_null<QAction*> addSeparator();
not_null<QAction*> addSeparator(
const style::MenuSeparator *st = nullptr);
void clearActions();
void finishAnimating();

View file

@ -7,17 +7,19 @@
#include "ui/widgets/menu/menu_separator.h"
#include "ui/painter.h"
#include "styles/style_widgets.h"
namespace Ui::Menu {
Separator::Separator(
not_null<RpWidget*> parent,
const style::Menu &st,
const style::MenuSeparator &separator,
not_null<QAction*> action)
: ItemBase(parent, st)
, _lineWidth(st.separatorWidth)
, _padding(st.separatorPadding)
, _fg(st.separatorFg)
, _lineWidth(separator.width)
, _padding(separator.padding)
, _fg(separator.fg)
, _bg(st.itemBg)
, _height(_padding.top() + _lineWidth + _padding.bottom())
, _action(action) {

View file

@ -7,10 +7,14 @@
#pragma once
#include "ui/widgets/menu/menu_item_base.h"
#include "styles/style_widgets.h"
class Painter;
namespace style {
struct Menu;
struct MenuSeparator;
} // namespace style
namespace Ui::Menu {
class Separator : public ItemBase {
@ -18,6 +22,7 @@ public:
Separator(
not_null<RpWidget*> parent,
const style::Menu &st,
const style::MenuSeparator &separator,
not_null<QAction*> action);
not_null<QAction*> action() const override;

View file

@ -248,8 +248,6 @@ void PopupMenu::init() {
_menu->setMousePressDelegate([this](QPoint globalPosition) { handleMousePress(globalPosition); });
_menu->setMouseReleaseDelegate([this](QPoint globalPosition) { handleMouseRelease(globalPosition); });
handleCompositingUpdate();
setWindowFlags(Qt::WindowFlags(Qt::FramelessWindowHint) | Qt::BypassWindowManagerHint | Qt::Popup | Qt::NoDropShadowWindowHint);
setMouseTracking(true);
@ -297,16 +295,26 @@ void PopupMenu::checkSubmenuShow() {
}
}
void PopupMenu::handleCompositingUpdate() {
void PopupMenu::validateCompositingSupport() {
const auto line = st::lineWidth;
_padding = _useTransparency
? _st.shadow.extend
: style::margins(line, line, line, line);
_useTransparency = Platform::TranslucentWindowsSupported();
if (!_useTransparency) {
_padding = QMargins(line, line, line, line);
_extents = QMargins();
} else {
const auto &additional = _additionalMenuPadding;
_padding = QMargins(
std::max(_st.shadow.extend.left(), additional.left()),
std::max(_st.shadow.extend.top(), additional.top()),
std::max(_st.shadow.extend.right(), additional.right()),
std::max(_st.shadow.extend.bottom(), additional.bottom()));
_extents = _padding - (additional - _additionalMenuExtents);
}
if (windowHandle()) {
if (_useTransparency) {
Platform::SetWindowExtents(this, _padding);
} else {
if (_extents.isNull()) {
Platform::UnsetWindowExtents(this);
} else {
Platform::SetWindowExtents(this, _extents);
}
}
_scroll->moveToLeft(_padding.left(), _padding.top());
@ -395,8 +403,8 @@ not_null<QAction*> PopupMenu::addAction(
return action;
}
not_null<QAction*> PopupMenu::addSeparator() {
return _menu->addSeparator();
not_null<QAction*> PopupMenu::addSeparator(const style::MenuSeparator *st) {
return _menu->addSeparator(st);
}
void PopupMenu::clearActions() {
@ -404,6 +412,10 @@ void PopupMenu::clearActions() {
return _menu->clearActions();
}
void PopupMenu::setForceWidth(int forceWidth) {
_menu->setForceWidth(forceWidth);
}
const std::vector<not_null<QAction*>> &PopupMenu::actions() const {
return _menu->actions();
}
@ -488,10 +500,14 @@ void PopupMenu::popupSubmenu(
currentSubmenu->hideMenu(true);
}
if (submenu) {
QPoint p(_inner.x() + (style::RightToLeft() ? _padding.right() : _inner.width() - _padding.left()), _inner.y() + actionTop);
QPoint p(_inner.x() + (style::RightToLeft() ? _padding.right() : (_inner.width() - _padding.left())), _inner.y() + actionTop);
_activeSubmenu = submenu;
_activeSubmenu->showMenu(geometry().topLeft() + p, this, source);
_menu->setChildShownAction(action);
if (_activeSubmenu->prepareGeometryFor(geometry().topLeft() + p, this)) {
_activeSubmenu->showPrepared(source);
_menu->setChildShownAction(action);
} else {
_activeSubmenu = nullptr;
}
}
}
@ -616,6 +632,22 @@ void PopupMenu::setForcedVerticalOrigin(VerticalOrigin origin) {
_forcedVerticalOrigin = origin;
}
void PopupMenu::setAdditionalMenuPadding(
QMargins padding,
QMargins extents) {
Expects(padding.left() >= extents.left()
&& padding.right() >= extents.right()
&& padding.top() >= extents.top()
&& padding.bottom() >= extents.bottom());
if (_additionalMenuPadding != padding
|| _additionalMenuExtents != extents) {
_additionalMenuPadding = padding;
_additionalMenuExtents = extents;
_roundingOverlay = nullptr;
}
}
void PopupMenu::showAnimated(PanelAnimation::Origin origin) {
setOrigin(origin);
showStarted();
@ -759,23 +791,54 @@ void PopupMenu::deleteOnHide(bool del) {
}
void PopupMenu::popup(const QPoint &p) {
showMenu(p, nullptr, TriggeredSource::Mouse);
if (prepareGeometryFor(p)) {
popupPrepared();
return;
}
_hiding = false;
_a_opacity.stop();
_a_show.stop();
_cache = QPixmap();
hide();
if (_deleteOnHide) {
deleteLater();
}
}
void PopupMenu::showMenu(const QPoint &p, PopupMenu *parent, TriggeredSource source) {
void PopupMenu::popupPrepared() {
showPrepared(TriggeredSource::Mouse);
}
PanelAnimation::Origin PopupMenu::preparedOrigin() const {
return _origin;
}
QMargins PopupMenu::preparedPadding() const {
return _padding;
}
QMargins PopupMenu::preparedExtents() const {
return _extents;
}
bool PopupMenu::useTransparency() const {
return _useTransparency;
}
bool PopupMenu::prepareGeometryFor(const QPoint &p) {
return prepareGeometryFor(p, nullptr);
}
bool PopupMenu::prepareGeometryFor(const QPoint &p, PopupMenu *parent) {
const auto usingScreenGeometry = !::Platform::IsWayland();
const auto screen = usingScreenGeometry ? QGuiApplication::screenAt(p) : nullptr;
const auto screen = usingScreenGeometry
? QGuiApplication::screenAt(p)
: nullptr;
if ((usingScreenGeometry && !screen)
|| (!parent && ::Platform::IsMac() && !Platform::IsApplicationActive())) {
_hiding = false;
_a_opacity.stop();
_a_show.stop();
_cache = QPixmap();
hide();
if (_deleteOnHide) {
deleteLater();
}
return;
|| (!parent
&& ::Platform::IsMac()
&& !Platform::IsApplicationActive())) {
return false;
}
_parent = parent;
@ -785,6 +848,7 @@ void PopupMenu::showMenu(const QPoint &p, PopupMenu *parent, TriggeredSource sou
} else if (screen) {
windowHandle()->setScreen(screen);
}
validateCompositingSupport();
using Origin = PanelAnimation::Origin;
auto origin = Origin::TopLeft;
@ -792,7 +856,7 @@ void PopupMenu::showMenu(const QPoint &p, PopupMenu *parent, TriggeredSource sou
&& (*_forcedOrigin == Origin::TopLeft
|| *_forcedOrigin == Origin::BottomLeft);
const auto forceTop = (_forcedVerticalOrigin
&& (*_forcedVerticalOrigin == VerticalOrigin::Top))
&& (*_forcedVerticalOrigin == VerticalOrigin::Top))
|| (_forcedOrigin
&& (*_forcedOrigin == Origin::TopLeft
|| *_forcedOrigin == Origin::TopRight));
@ -800,55 +864,74 @@ void PopupMenu::showMenu(const QPoint &p, PopupMenu *parent, TriggeredSource sou
&& (*_forcedOrigin == Origin::TopRight
|| *_forcedOrigin == Origin::BottomRight);
const auto forceBottom = (_forcedVerticalOrigin
&& (*_forcedVerticalOrigin == VerticalOrigin::Bottom))
&& (*_forcedVerticalOrigin == VerticalOrigin::Bottom))
|| (_forcedOrigin
&& (*_forcedOrigin == Origin::BottomLeft
|| *_forcedOrigin == Origin::BottomRight));
auto w = p - QPoint(0, _padding.top());
auto w = p - QPoint(
std::max(
_additionalMenuPadding.left() - _st.shadow.extend.left(),
0),
_padding.top());
auto r = screen ? screen->availableGeometry() : QRect();
_useTransparency = Platform::TranslucentWindowsSupported(p);
setAttribute(Qt::WA_OpaquePaintEvent, !_useTransparency);
handleCompositingUpdate();
if (style::RightToLeft()) {
const auto badLeft = !r.isNull() && w.x() - width() < r.x() - _padding.left();
const auto badLeft = !r.isNull() && w.x() - width() < r.x() - _extents.left();
if (forceRight || (badLeft && !forceLeft)) {
if (_parent && (r.isNull() || w.x() + _parent->width() - _padding.left() - _padding.right() + width() - _padding.right() <= r.x() + r.width())) {
w.setX(w.x() + _parent->width() - _padding.left() - _padding.right());
if (_parent && (r.isNull() || w.x() + _parent->width() - _extents.left() - _extents.right() + width() - _extents.right() <= r.x() + r.width())) {
w.setX(w.x() + _parent->width() - _extents.left() - _extents.right());
} else {
w.setX(r.x() - _padding.left());
w.setX(r.x() - _extents.left());
}
} else {
w.setX(w.x() - width());
}
} else {
const auto badLeft = !r.isNull() && w.x() + width() - _padding.right() > r.x() + r.width();
const auto badLeft = !r.isNull() && w.x() + width() - _extents.right() > r.x() + r.width();
if (forceRight || (badLeft && !forceLeft)) {
if (_parent && (r.isNull() || w.x() - _parent->width() + _padding.left() + _padding.right() - width() + _padding.right() >= r.x() - _padding.left())) {
w.setX(w.x() + _padding.left() + _padding.right() - _parent->width() - width() + _padding.left() + _padding.right());
if (_parent && (r.isNull() || w.x() - _parent->width() + _extents.left() + _extents.right() - width() + _extents.right() >= r.x() - _extents.left())) {
w.setX(w.x() + _extents.left() + _extents.right() - _parent->width() - width() + _extents.left() + _extents.right());
} else {
w.setX(p.x() - width() + _padding.right());
w.setX(p.x() - width() + std::max(
_additionalMenuPadding.right() - _st.shadow.extend.right(),
0));
}
origin = PanelAnimation::Origin::TopRight;
}
}
const auto badTop = !r.isNull() && w.y() + height() - _padding.bottom() > r.y() + r.height();
const auto badTop = !r.isNull() && w.y() + height() - _extents.bottom() > r.y() + r.height();
if (forceBottom || (badTop && !forceTop)) {
if (_parent) {
w.setY(r.y() + r.height() - height() + _padding.bottom());
w.setY(r.y() + r.height() - height() + _extents.bottom());
} else {
w.setY(p.y() - height() + _padding.bottom());
origin = (origin == PanelAnimation::Origin::TopRight) ? PanelAnimation::Origin::BottomRight : PanelAnimation::Origin::BottomLeft;
w.setY(p.y() - height() + _extents.bottom());
origin = (origin == PanelAnimation::Origin::TopRight)
? PanelAnimation::Origin::BottomRight
: PanelAnimation::Origin::BottomLeft;
}
}
if (!r.isNull() && w.x() < r.x()) {
w.setX(r.x());
}
if (!r.isNull() && w.y() < r.y()) {
w.setY(r.y());
if (!r.isNull()) {
if (w.x() + width() - _extents.right() > r.x() + r.width()) {
w.setX(r.x() + r.width() + _extents.right() - width());
}
if (w.x() + _extents.left() < r.x()) {
w.setX(r.x() - _extents.left());
}
if (w.y() + height() - _extents.bottom() > r.y() + r.height()) {
w.setY(r.y() + r.height() + _extents.bottom() - height());
}
if (w.y() + _extents.top() < r.y()) {
w.setY(r.y() - _extents.top());
}
}
move(w);
setOrigin(origin);
return true;
}
void PopupMenu::showPrepared(TriggeredSource source) {
Expects(windowHandle() != nullptr);
_menu->setShowSource(source);
startShowAnimation();

View file

@ -15,6 +15,10 @@
#include "base/object_ptr.h"
#include "base/unique_qptr.h"
namespace style {
struct MenuSeparator;
} // namespace style
namespace Ui {
class ScrollArea;
@ -44,7 +48,8 @@ public:
std::unique_ptr<PopupMenu> submenu,
const style::icon *icon = nullptr,
const style::icon *iconOver = nullptr);
not_null<QAction*> addSeparator();
not_null<QAction*> addSeparator(
const style::MenuSeparator *st = nullptr);
void clearActions();
[[nodiscard]] const std::vector<not_null<QAction*>> &actions() const;
@ -57,9 +62,18 @@ public:
void deleteOnHide(bool del);
void popup(const QPoint &p);
bool prepareGeometryFor(const QPoint &p);
void popupPrepared();
void hideMenu(bool fast = false);
void setForceWidth(int forceWidth);
void setForcedOrigin(PanelAnimation::Origin origin);
void setForcedVerticalOrigin(VerticalOrigin origin);
void setAdditionalMenuPadding(QMargins padding, QMargins extents);
[[nodiscard]] PanelAnimation::Origin preparedOrigin() const;
[[nodiscard]] QMargins preparedPadding() const;
[[nodiscard]] QMargins preparedExtents() const;
[[nodiscard]] bool useTransparency() const;
void setDestroyedCallback(Fn<void()> callback) {
_destroyedCallback = std::move(callback);
@ -104,7 +118,7 @@ private:
void showStarted();
using TriggeredSource = Menu::TriggeredSource;
void handleCompositingUpdate();
void validateCompositingSupport();
void handleMenuResize();
void handleActivated(const Menu::CallbackData &data);
void handleTriggered(const Menu::CallbackData &data);
@ -129,7 +143,8 @@ private:
not_null<PopupMenu*> submenu,
int actionTop,
TriggeredSource source);
void showMenu(const QPoint &p, PopupMenu *parent, TriggeredSource source);
bool prepareGeometryFor(const QPoint &p, PopupMenu *parent);
void showPrepared(TriggeredSource source);
void updateRoundingOverlay();
const style::PopupMenu &_st;
@ -146,7 +161,10 @@ private:
PopupMenu *_parent = nullptr;
QRect _inner;
style::margins _padding;
QMargins _padding;
QMargins _extents;
QMargins _additionalMenuPadding;
QMargins _additionalMenuExtents;
QPointer<PopupMenu> _activeSubmenu;

View file

@ -526,7 +526,7 @@ void SeparatePanel::initGeometry(QSize size) {
if (center.y() - size.height() / 2 < available.y()) {
center.setY(available.y() + size.height() / 2);
}
_useTransparency = Ui::Platform::TranslucentWindowsSupported(center);
_useTransparency = Ui::Platform::TranslucentWindowsSupported();
_padding = _useTransparency
? st::callShadow.extend
: style::margins(

View file

@ -90,7 +90,7 @@ void Tooltip::popup(const QPoint &m, const QString &text, const style::Tooltip *
_st = st;
_text = Text::String(_st->textStyle, text, kPlainTextOptions, _st->widthMax);
_useTransparency = Platform::TranslucentWindowsSupported(_point);
_useTransparency = Platform::TranslucentWindowsSupported();
setAttribute(Qt::WA_OpaquePaintEvent, !_useTransparency);
int32 addw = 2 * st::lineWidth + _st->textPadding.left() + _st->textPadding.right();

View file

@ -212,6 +212,12 @@ PanelAnimation {
shadow: Shadow;
}
MenuSeparator {
padding: margins;
width: pixels;
fg: color;
}
Menu {
skip: pixels;
@ -230,9 +236,7 @@ Menu {
itemToggleOver: Toggle;
itemToggleShift: pixels;
separatorPadding: margins;
separatorWidth: pixels;
separatorFg: color;
separator: MenuSeparator;
arrow: icon;
@ -846,6 +850,11 @@ defaultMenuToggle: Toggle(defaultToggle) {
defaultMenuToggleOver: Toggle(defaultToggle) {
untoggledFg: menuIconFgOver;
}
defaultMenuSeparator: MenuSeparator {
padding: margins(0px, 5px, 0px, 5px);
width: 1px;
fg: menuSeparatorFg;
}
defaultMenu: Menu {
skip: 0px;
@ -864,9 +873,7 @@ defaultMenu: Menu {
itemToggleOver: defaultMenuToggleOver;
itemToggleShift: 0px;
separatorPadding: margins(0px, 5px, 0px, 5px);
separatorWidth: 1px;
separatorFg: menuSeparatorFg;
separator: defaultMenuSeparator;
arrow: defaultMenuArrow;