Partially replaced code for using widgets as items in Ui::Menu.
This commit is contained in:
parent
6f097fc571
commit
8d5bdd6b7e
8 changed files with 561 additions and 300 deletions
|
|
@ -148,6 +148,9 @@ PRIVATE
|
||||||
ui/widgets/labels.h
|
ui/widgets/labels.h
|
||||||
ui/widgets/menu.cpp
|
ui/widgets/menu.cpp
|
||||||
ui/widgets/menu.h
|
ui/widgets/menu.h
|
||||||
|
ui/widgets/menu/menu_common.h
|
||||||
|
ui/widgets/menu/menu_item_base.cpp
|
||||||
|
ui/widgets/menu/menu_item_base.h
|
||||||
ui/widgets/popup_menu.cpp
|
ui/widgets/popup_menu.cpp
|
||||||
ui/widgets/popup_menu.h
|
ui/widgets/popup_menu.h
|
||||||
ui/widgets/scroll_area.cpp
|
ui/widgets/scroll_area.cpp
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,10 @@ DropdownMenu::DropdownMenu(QWidget *parent, const style::DropdownMenu &st) : Inn
|
||||||
void DropdownMenu::init() {
|
void DropdownMenu::init() {
|
||||||
InnerDropdown::setHiddenCallback([this] { hideFinish(); });
|
InnerDropdown::setHiddenCallback([this] { hideFinish(); });
|
||||||
|
|
||||||
_menu->setResizedCallback([this] { resizeToContent(); });
|
_menu->sizeValue(
|
||||||
|
) | rpl::start_with_next([=] {
|
||||||
|
resizeToContent();
|
||||||
|
}, _menu->lifetime());
|
||||||
_menu->setActivatedCallback([this](QAction *action, int actionTop, TriggeredSource source) {
|
_menu->setActivatedCallback([this](QAction *action, int actionTop, TriggeredSource source) {
|
||||||
handleActivated(action, actionTop, source);
|
handleActivated(action, actionTop, source);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,9 @@
|
||||||
#include "ui/widgets/menu.h"
|
#include "ui/widgets/menu.h"
|
||||||
|
|
||||||
#include "ui/effects/ripple_animation.h"
|
#include "ui/effects/ripple_animation.h"
|
||||||
|
#include "ui/widgets/buttons.h"
|
||||||
#include "ui/widgets/checkbox.h"
|
#include "ui/widgets/checkbox.h"
|
||||||
|
#include "ui/widgets/menu/menu_item_base.h"
|
||||||
#include "ui/text/text.h"
|
#include "ui/text/text.h"
|
||||||
|
|
||||||
#include <QtGui/QtEvents>
|
#include <QtGui/QtEvents>
|
||||||
|
|
@ -49,31 +51,237 @@ TextParseOptions MenuTextOptions = {
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
struct Menu::ActionData {
|
class Menu::Separator : public ItemBase {
|
||||||
Text::String text;
|
public:
|
||||||
QString shortcut;
|
Separator(not_null<RpWidget*> parent, const style::Menu &st, int index)
|
||||||
const style::icon *icon = nullptr;
|
: ItemBase(parent, st, index)
|
||||||
const style::icon *iconOver = nullptr;
|
, _lineWidth(st.separatorWidth)
|
||||||
std::unique_ptr<RippleAnimation> ripple;
|
, _padding(st.separatorPadding)
|
||||||
std::unique_ptr<ToggleView> toggle;
|
, _fg(st.separatorFg)
|
||||||
int textWidth = 0;
|
, _bg(st.itemBg)
|
||||||
bool hasSubmenu = false;
|
, _height(_padding.top() + _lineWidth + _padding.bottom()) {
|
||||||
|
|
||||||
|
initResizeHook(parent->sizeValue());
|
||||||
|
// setAttribute(Qt::WA_TransparentForMouseEvents, true);
|
||||||
|
paintRequest(
|
||||||
|
) | rpl::start_with_next([=] {
|
||||||
|
Painter p(this);
|
||||||
|
|
||||||
|
p.fillRect(0, 0, width(), _height, _bg);
|
||||||
|
p.fillRect(
|
||||||
|
_padding.left(),
|
||||||
|
_padding.top(),
|
||||||
|
width() - _padding.left() - _padding.right(),
|
||||||
|
_lineWidth,
|
||||||
|
_fg);
|
||||||
|
}, lifetime());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
QAction *action() const override {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isEnabled() const override {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
int contentHeight() const override {
|
||||||
|
return _height;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
const int _lineWidth;
|
||||||
|
const style::margins &_padding;
|
||||||
|
const style::color &_fg;
|
||||||
|
const style::color &_bg;
|
||||||
|
const int _height;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
class Menu::Action : public ItemBase {
|
||||||
|
public:
|
||||||
|
Action(
|
||||||
|
not_null<RpWidget*> parent,
|
||||||
|
const style::Menu &st,
|
||||||
|
int index,
|
||||||
|
not_null<QAction*> action,
|
||||||
|
const style::icon *icon,
|
||||||
|
const style::icon *iconOver,
|
||||||
|
bool hasSubmenu)
|
||||||
|
: ItemBase(parent, st, index)
|
||||||
|
, _action(action)
|
||||||
|
, _st(st)
|
||||||
|
, _icon(icon)
|
||||||
|
, _iconOver(iconOver)
|
||||||
|
, _height(_st.itemPadding.top()
|
||||||
|
+ _st.itemStyle.font->height
|
||||||
|
+ _st.itemPadding.bottom()) {
|
||||||
|
|
||||||
|
initResizeHook(parent->sizeValue());
|
||||||
|
processAction();
|
||||||
|
setHasSubmenu(hasSubmenu);
|
||||||
|
|
||||||
|
paintRequest(
|
||||||
|
) | rpl::start_with_next([=] {
|
||||||
|
Painter p(this);
|
||||||
|
paint(p);
|
||||||
|
}, lifetime());
|
||||||
|
|
||||||
|
events(
|
||||||
|
) | rpl::filter([=](not_null<QEvent*> e) {
|
||||||
|
return _action->isEnabled()
|
||||||
|
&& ((e->type() == QEvent::Leave)
|
||||||
|
|| (e->type() == QEvent::Enter));
|
||||||
|
}) | rpl::map([=](not_null<QEvent*> e) {
|
||||||
|
return (e->type() == QEvent::Enter);
|
||||||
|
}) | rpl::start_with_next([=](bool selected) {
|
||||||
|
setSelected(selected);
|
||||||
|
}, lifetime());
|
||||||
|
|
||||||
|
events(
|
||||||
|
) | rpl::filter([=](not_null<QEvent*> e) {
|
||||||
|
return _action->isEnabled() && (e->type() == QEvent::MouseMove);
|
||||||
|
}) | rpl::start_with_next([=](not_null<QEvent*> e) {
|
||||||
|
setSelected(true);
|
||||||
|
}, lifetime());
|
||||||
|
|
||||||
|
connect(_action, &QAction::changed, [=] { processAction(); });
|
||||||
|
}
|
||||||
|
|
||||||
|
void paint(Painter &p) {
|
||||||
|
|
||||||
|
const auto enabled = _action->isEnabled();
|
||||||
|
const auto selected = isSelected();
|
||||||
|
if (selected && _st.itemBgOver->c.alpha() < 255) {
|
||||||
|
p.fillRect(0, 0, width(), _height, _st.itemBg);
|
||||||
|
}
|
||||||
|
p.fillRect(0, 0, width(), _height, selected ? _st.itemBgOver : _st.itemBg);
|
||||||
|
if (isEnabled()) {
|
||||||
|
paintRipple(p, 0, 0);
|
||||||
|
}
|
||||||
|
if (const auto icon = (selected ? _iconOver : _icon)) {
|
||||||
|
icon->paint(p, _st.itemIconPosition, width());
|
||||||
|
}
|
||||||
|
p.setPen(selected ? _st.itemFgOver : (enabled ? _st.itemFg : _st.itemFgDisabled));
|
||||||
|
_text.drawLeftElided(p, _st.itemPadding.left(), _st.itemPadding.top(), _textWidth, width());
|
||||||
|
if (hasSubmenu()) {
|
||||||
|
const auto left = width() - _st.itemPadding.right() - _st.arrow.width();
|
||||||
|
const auto top = (_height - _st.arrow.height()) / 2;
|
||||||
|
if (enabled) {
|
||||||
|
_st.arrow.paint(p, left, top, width());
|
||||||
|
} else {
|
||||||
|
_st.arrow.paint(
|
||||||
|
p,
|
||||||
|
left,
|
||||||
|
top,
|
||||||
|
width(),
|
||||||
|
_st.itemFgDisabled->c);
|
||||||
|
}
|
||||||
|
} else if (!_shortcut.isEmpty()) {
|
||||||
|
p.setPen(selected ? _st.itemFgShortcutOver : (enabled ? _st.itemFgShortcut : _st.itemFgShortcutDisabled));
|
||||||
|
p.drawTextRight(_st.itemPadding.right(), _st.itemPadding.top(), width(), _shortcut);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void processAction() {
|
||||||
|
if (_action->text().isEmpty()) {
|
||||||
|
_shortcut = QString();
|
||||||
|
_text.clear();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto actionTextParts = _action->text().split('\t');
|
||||||
|
const auto actionText = actionTextParts.empty()
|
||||||
|
? QString()
|
||||||
|
: actionTextParts[0];
|
||||||
|
const auto actionShortcut = (actionTextParts.size() > 1)
|
||||||
|
? actionTextParts[1]
|
||||||
|
: QString();
|
||||||
|
_text.setMarkedText(
|
||||||
|
_st.itemStyle,
|
||||||
|
ParseMenuItem(actionText),
|
||||||
|
MenuTextOptions);
|
||||||
|
const auto textWidth = _text.maxWidth();
|
||||||
|
const auto &padding = _st.itemPadding;
|
||||||
|
|
||||||
|
const auto additionalWidth = hasSubmenu()
|
||||||
|
? padding.right() + _st.arrow.width()
|
||||||
|
: (!actionShortcut.isEmpty())
|
||||||
|
? (padding.right() + _st.itemStyle.font->width(actionShortcut))
|
||||||
|
: 0;
|
||||||
|
const auto goodWidth = padding.left()
|
||||||
|
+ textWidth
|
||||||
|
+ padding.right()
|
||||||
|
+ additionalWidth;
|
||||||
|
// if (action->isCheckable()) {
|
||||||
|
// auto updateCallback = [this, index] { updateItem(index); };
|
||||||
|
// if (_toggle) {
|
||||||
|
// _toggle->setUpdateCallback(updateCallback);
|
||||||
|
// _toggle->setChecked(action->isChecked(), anim::type::normal);
|
||||||
|
// } else {
|
||||||
|
// _toggle = std::make_unique<ToggleView>(_st.itemToggle, action->isChecked(), updateCallback);
|
||||||
|
// }
|
||||||
|
// goodWidth += _st.itemPadding.right() + _toggle->getSize().width() - _st.itemToggleShift;
|
||||||
|
// } else {
|
||||||
|
// _toggle.reset();
|
||||||
|
// }
|
||||||
|
const auto w = std::clamp(goodWidth, _st.widthMin, _st.widthMax);
|
||||||
|
_textWidth = w - (goodWidth - textWidth);
|
||||||
|
_shortcut = actionShortcut;
|
||||||
|
setContentWidth(w);
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isEnabled() const override {
|
||||||
|
return _action->isEnabled();
|
||||||
|
}
|
||||||
|
|
||||||
|
QAction *action() const override {
|
||||||
|
return _action;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
QPoint prepareRippleStartPosition() const override {
|
||||||
|
return mapFromGlobal(QCursor::pos());
|
||||||
|
}
|
||||||
|
|
||||||
|
QImage prepareRippleMask() const override {
|
||||||
|
return RippleAnimation::rectMask(size());
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
int contentHeight() const override {
|
||||||
|
return _height;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
Text::String _text;
|
||||||
|
QString _shortcut;
|
||||||
|
const not_null<QAction*> _action;
|
||||||
|
const style::Menu &_st;
|
||||||
|
const style::icon *_icon;
|
||||||
|
const style::icon *_iconOver;
|
||||||
|
// std::unique_ptr<RippleAnimation> _ripple;
|
||||||
|
std::unique_ptr<ToggleView> _toggle;
|
||||||
|
int _textWidth = 0;
|
||||||
|
const int _height;
|
||||||
|
|
||||||
|
// rpl::variable<bool> _selected = false;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
Menu::Menu(QWidget *parent, const style::Menu &st)
|
Menu::Menu(QWidget *parent, const style::Menu &st)
|
||||||
: RpWidget(parent)
|
: RpWidget(parent)
|
||||||
, _st(st)
|
, _st(st) {
|
||||||
, _itemHeight(_st.itemPadding.top() + _st.itemStyle.font->height + _st.itemPadding.bottom())
|
|
||||||
, _separatorHeight(_st.separatorPadding.top() + _st.separatorWidth + _st.separatorPadding.bottom()) {
|
|
||||||
init();
|
init();
|
||||||
}
|
}
|
||||||
|
|
||||||
Menu::Menu(QWidget *parent, QMenu *menu, const style::Menu &st)
|
Menu::Menu(QWidget *parent, QMenu *menu, const style::Menu &st)
|
||||||
: RpWidget(parent)
|
: RpWidget(parent)
|
||||||
, _st(st)
|
, _st(st)
|
||||||
, _wappedMenu(menu)
|
, _wappedMenu(menu) {
|
||||||
, _itemHeight(_st.itemPadding.top() + _st.itemStyle.font->height + _st.itemPadding.bottom())
|
|
||||||
, _separatorHeight(_st.separatorPadding.top() + _st.separatorWidth + _st.separatorPadding.bottom()) {
|
|
||||||
init();
|
init();
|
||||||
|
|
||||||
_wappedMenu->setParent(this);
|
_wappedMenu->setParent(this);
|
||||||
|
|
@ -93,6 +301,12 @@ void Menu::init() {
|
||||||
if (_st.itemBg->c.alpha() == 255) {
|
if (_st.itemBg->c.alpha() == 255) {
|
||||||
setAttribute(Qt::WA_OpaquePaintEvent);
|
setAttribute(Qt::WA_OpaquePaintEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
paintRequest(
|
||||||
|
) | rpl::start_with_next([=](const QRect &clip) {
|
||||||
|
Painter p(this);
|
||||||
|
p.fillRect(clip, _st.itemBg);
|
||||||
|
}, lifetime());
|
||||||
}
|
}
|
||||||
|
|
||||||
not_null<QAction*> Menu::addAction(const QString &text, Fn<void()> callback, const style::icon *icon, const style::icon *iconOver) {
|
not_null<QAction*> Menu::addAction(const QString &text, Fn<void()> callback, const style::icon *icon, const style::icon *iconOver) {
|
||||||
|
|
@ -108,27 +322,78 @@ not_null<QAction*> Menu::addAction(const QString &text, std::unique_ptr<QMenu> s
|
||||||
}
|
}
|
||||||
|
|
||||||
not_null<QAction*> Menu::addAction(not_null<QAction*> action, const style::icon *icon, const style::icon *iconOver) {
|
not_null<QAction*> Menu::addAction(not_null<QAction*> action, const style::icon *icon, const style::icon *iconOver) {
|
||||||
connect(action, &QAction::changed, this, [=] {
|
|
||||||
actionChanged();
|
|
||||||
});
|
|
||||||
_actions.emplace_back(action);
|
_actions.emplace_back(action);
|
||||||
_actionsData.push_back([&] {
|
|
||||||
auto data = ActionData();
|
|
||||||
data.icon = icon;
|
|
||||||
data.iconOver = iconOver ? iconOver : icon;
|
|
||||||
data.hasSubmenu = (action->menu() != nullptr);
|
|
||||||
return data;
|
|
||||||
}());
|
|
||||||
|
|
||||||
auto newWidth = qMax(width(), _st.widthMin);
|
const auto top = _actionWidgets.empty()
|
||||||
newWidth = processAction(action, _actions.size() - 1, newWidth);
|
? 0
|
||||||
auto newHeight = height() + (action->isSeparator() ? _separatorHeight : _itemHeight);
|
: _actionWidgets.back()->y() + _actionWidgets.back()->height();
|
||||||
resize(_forceWidth ? _forceWidth : newWidth, newHeight);
|
const auto index = _actionWidgets.size();
|
||||||
if (_resizedCallback) {
|
if (action->isSeparator()) {
|
||||||
_resizedCallback();
|
auto widget = base::make_unique_q<Separator>(this, _st, index);
|
||||||
|
widget->moveToLeft(0, top);
|
||||||
|
widget->show();
|
||||||
|
_actionWidgets.push_back(std::move(widget));
|
||||||
|
} else {
|
||||||
|
auto widget = base::make_unique_q<Action>(
|
||||||
|
this,
|
||||||
|
_st,
|
||||||
|
index,
|
||||||
|
action,
|
||||||
|
icon,
|
||||||
|
iconOver ? iconOver : icon,
|
||||||
|
(action->menu() != nullptr));
|
||||||
|
widget->moveToLeft(0, top);
|
||||||
|
widget->show();
|
||||||
|
|
||||||
|
widget->selects(
|
||||||
|
) | rpl::start_with_next([=, w = widget.get()](bool selected) {
|
||||||
|
if (!selected) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
for (auto i = 0; i < _actionWidgets.size(); i++) {
|
||||||
|
if (_actionWidgets[i].get() != w) {
|
||||||
|
_actionWidgets[i]->setSelected(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (_activatedCallback) {
|
||||||
|
_activatedCallback(
|
||||||
|
w->action(),
|
||||||
|
w->y(),
|
||||||
|
w->lastTriggeredSource());
|
||||||
|
}
|
||||||
|
}, widget->lifetime());
|
||||||
|
|
||||||
|
widget->clicks(
|
||||||
|
) | rpl::start_with_next([=, w = widget.get()]() {
|
||||||
|
if (_triggeredCallback) {
|
||||||
|
_triggeredCallback(w->action(), w->y(), w->lastTriggeredSource());
|
||||||
|
}
|
||||||
|
}, widget->lifetime());
|
||||||
|
|
||||||
|
widget->contentWidthValue(
|
||||||
|
) | rpl::start_with_next([=] {
|
||||||
|
const auto newWidth = _forceWidth
|
||||||
|
? _forceWidth
|
||||||
|
: _actionWidgets.empty()
|
||||||
|
? _st.widthMin
|
||||||
|
: (*ranges::max_element(
|
||||||
|
_actionWidgets,
|
||||||
|
std::greater<>(),
|
||||||
|
&ItemBase::width))->contentWidth();
|
||||||
|
resize(newWidth, height());
|
||||||
|
}, widget->lifetime());
|
||||||
|
|
||||||
|
_actionWidgets.push_back(std::move(widget));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const auto newHeight = ranges::accumulate(
|
||||||
|
_actionWidgets,
|
||||||
|
0,
|
||||||
|
ranges::plus(),
|
||||||
|
&ItemBase::height);
|
||||||
|
resize(width(), newHeight);
|
||||||
updateSelected(QCursor::pos());
|
updateSelected(QCursor::pos());
|
||||||
update();
|
|
||||||
|
|
||||||
return action;
|
return action;
|
||||||
}
|
}
|
||||||
|
|
@ -141,64 +406,16 @@ not_null<QAction*> Menu::addSeparator() {
|
||||||
|
|
||||||
void Menu::clearActions() {
|
void Menu::clearActions() {
|
||||||
setSelected(-1);
|
setSelected(-1);
|
||||||
setPressed(-1);
|
_actionWidgets.clear();
|
||||||
_actionsData.clear();
|
|
||||||
for (auto action : base::take(_actions)) {
|
for (auto action : base::take(_actions)) {
|
||||||
if (action->parent() == this) {
|
if (action->parent() == this) {
|
||||||
delete action;
|
delete action;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
resize(_forceWidth ? _forceWidth : _st.widthMin, _st.skip * 2);
|
resize(_forceWidth ? _forceWidth : _st.widthMin, _st.skip * 2);
|
||||||
if (_resizedCallback) {
|
|
||||||
_resizedCallback();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Menu::finishAnimating() {
|
void Menu::finishAnimating() {
|
||||||
for (auto &data : _actionsData) {
|
|
||||||
if (data.ripple) {
|
|
||||||
data.ripple.reset();
|
|
||||||
}
|
|
||||||
if (data.toggle) {
|
|
||||||
data.toggle->finishAnimating();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int Menu::processAction(not_null<QAction*> action, int index, int width) {
|
|
||||||
auto &data = _actionsData[index];
|
|
||||||
if (action->isSeparator() || action->text().isEmpty()) {
|
|
||||||
data.shortcut = QString();
|
|
||||||
data.text.clear();
|
|
||||||
} else {
|
|
||||||
auto actionTextParts = action->text().split('\t');
|
|
||||||
auto actionText = actionTextParts.empty() ? QString() : actionTextParts[0];
|
|
||||||
auto actionShortcut = (actionTextParts.size() > 1) ? actionTextParts[1] : QString();
|
|
||||||
data.text.setMarkedText(_st.itemStyle, ParseMenuItem(actionText), MenuTextOptions);
|
|
||||||
const auto textw = data.text.maxWidth();
|
|
||||||
int goodw = _st.itemPadding.left() + textw + _st.itemPadding.right();
|
|
||||||
if (data.hasSubmenu) {
|
|
||||||
goodw += _st.itemPadding.right() + _st.arrow.width();
|
|
||||||
} else if (!actionShortcut.isEmpty()) {
|
|
||||||
goodw += _st.itemPadding.right() + _st.itemStyle.font->width(actionShortcut);
|
|
||||||
}
|
|
||||||
if (action->isCheckable()) {
|
|
||||||
auto updateCallback = [this, index] { updateItem(index); };
|
|
||||||
if (data.toggle) {
|
|
||||||
data.toggle->setUpdateCallback(updateCallback);
|
|
||||||
data.toggle->setChecked(action->isChecked(), anim::type::normal);
|
|
||||||
} else {
|
|
||||||
data.toggle = std::make_unique<ToggleView>(_st.itemToggle, action->isChecked(), updateCallback);
|
|
||||||
}
|
|
||||||
goodw += _st.itemPadding.right() + data.toggle->getSize().width() - _st.itemToggleShift;
|
|
||||||
} else {
|
|
||||||
data.toggle.reset();
|
|
||||||
}
|
|
||||||
width = std::clamp(goodw, width, _st.widthMax);
|
|
||||||
data.textWidth = width - (goodw - textw);
|
|
||||||
data.shortcut = actionShortcut;
|
|
||||||
}
|
|
||||||
return width;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Menu::setShowSource(TriggeredSource source) {
|
void Menu::setShowSource(TriggeredSource source) {
|
||||||
|
|
@ -215,147 +432,48 @@ void Menu::setForceWidth(int forceWidth) {
|
||||||
resize(_forceWidth, height());
|
resize(_forceWidth, height());
|
||||||
}
|
}
|
||||||
|
|
||||||
void Menu::actionChanged() {
|
|
||||||
auto newWidth = _st.widthMin;
|
|
||||||
auto index = 0;
|
|
||||||
for (const auto action : _actions) {
|
|
||||||
newWidth = processAction(action, index++, newWidth);
|
|
||||||
}
|
|
||||||
if (newWidth != width() && !_forceWidth) {
|
|
||||||
resize(newWidth, height());
|
|
||||||
if (_resizedCallback) {
|
|
||||||
_resizedCallback();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
update();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Menu::paintEvent(QPaintEvent *e) {
|
|
||||||
Painter p(this);
|
|
||||||
|
|
||||||
auto clip = e->rect();
|
|
||||||
|
|
||||||
auto topskip = QRect(0, 0, width(), _st.skip);
|
|
||||||
auto bottomskip = QRect(0, height() - _st.skip, width(), _st.skip);
|
|
||||||
if (clip.intersects(topskip)) p.fillRect(clip.intersected(topskip), _st.itemBg);
|
|
||||||
if (clip.intersects(bottomskip)) p.fillRect(clip.intersected(bottomskip), _st.itemBg);
|
|
||||||
|
|
||||||
int top = _st.skip;
|
|
||||||
p.translate(0, top);
|
|
||||||
p.setFont(_st.itemStyle.font);
|
|
||||||
for (int i = 0, count = int(_actions.size()); i != count; ++i) {
|
|
||||||
if (clip.top() + clip.height() <= top) break;
|
|
||||||
|
|
||||||
const auto action = _actions[i];
|
|
||||||
auto &data = _actionsData[i];
|
|
||||||
auto actionHeight = action->isSeparator() ? _separatorHeight : _itemHeight;
|
|
||||||
top += actionHeight;
|
|
||||||
if (clip.top() < top) {
|
|
||||||
if (action->isSeparator()) {
|
|
||||||
p.fillRect(0, 0, width(), actionHeight, _st.itemBg);
|
|
||||||
p.fillRect(_st.separatorPadding.left(), _st.separatorPadding.top(), width() - _st.separatorPadding.left() - _st.separatorPadding.right(), _st.separatorWidth, _st.separatorFg);
|
|
||||||
} else {
|
|
||||||
auto enabled = action->isEnabled();
|
|
||||||
auto selected = ((i == _selected || i == _pressed) && enabled);
|
|
||||||
if (selected && _st.itemBgOver->c.alpha() < 255) {
|
|
||||||
p.fillRect(0, 0, width(), actionHeight, _st.itemBg);
|
|
||||||
}
|
|
||||||
p.fillRect(0, 0, width(), actionHeight, selected ? _st.itemBgOver : _st.itemBg);
|
|
||||||
if (data.ripple) {
|
|
||||||
data.ripple->paint(p, 0, 0, width());
|
|
||||||
if (data.ripple->empty()) {
|
|
||||||
data.ripple.reset();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (auto icon = (selected ? data.iconOver : data.icon)) {
|
|
||||||
icon->paint(p, _st.itemIconPosition, width());
|
|
||||||
}
|
|
||||||
p.setPen(selected ? _st.itemFgOver : (enabled ? _st.itemFg : _st.itemFgDisabled));
|
|
||||||
data.text.drawLeftElided(p, _st.itemPadding.left(), _st.itemPadding.top(), data.textWidth, width());
|
|
||||||
if (data.hasSubmenu) {
|
|
||||||
const auto left = width() - _st.itemPadding.right() - _st.arrow.width();
|
|
||||||
const auto top = (_itemHeight - _st.arrow.height()) / 2;
|
|
||||||
if (enabled) {
|
|
||||||
_st.arrow.paint(p, left, top, width());
|
|
||||||
} else {
|
|
||||||
_st.arrow.paint(
|
|
||||||
p,
|
|
||||||
left,
|
|
||||||
top,
|
|
||||||
width(),
|
|
||||||
_st.itemFgDisabled->c);
|
|
||||||
}
|
|
||||||
} else if (!data.shortcut.isEmpty()) {
|
|
||||||
p.setPen(selected ? _st.itemFgShortcutOver : (enabled ? _st.itemFgShortcut : _st.itemFgShortcutDisabled));
|
|
||||||
p.drawTextRight(_st.itemPadding.right(), _st.itemPadding.top(), width(), data.shortcut);
|
|
||||||
} else if (data.toggle) {
|
|
||||||
auto toggleSize = data.toggle->getSize();
|
|
||||||
data.toggle->paint(p, width() - _st.itemPadding.right() - toggleSize.width() + _st.itemToggleShift, (_itemHeight - toggleSize.height()) / 2, width());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
p.translate(0, actionHeight);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Menu::updateSelected(QPoint globalPosition) {
|
void Menu::updateSelected(QPoint globalPosition) {
|
||||||
if (!_mouseSelection) return;
|
if (!_mouseSelection) {
|
||||||
|
return;
|
||||||
auto p = mapFromGlobal(globalPosition) - QPoint(0, _st.skip);
|
}
|
||||||
auto selected = -1, top = 0;
|
|
||||||
while (top <= p.y() && ++selected < _actions.size()) {
|
const auto p = mapFromGlobal(globalPosition) - QPoint(0, _st.skip);
|
||||||
top += _actions[selected]->isSeparator() ? _separatorHeight : _itemHeight;
|
for (const auto &widget : _actionWidgets) {
|
||||||
|
const auto widgetRect = QRect(widget->pos(), widget->size());
|
||||||
|
if (widgetRect.contains(p)) {
|
||||||
|
widget->setSelected(true);
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
setSelected((selected >= 0 && selected < _actions.size() && _actions[selected]->isEnabled() && !_actions[selected]->isSeparator()) ? selected : -1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Menu::itemPressed(TriggeredSource source) {
|
void Menu::itemPressed(TriggeredSource source) {
|
||||||
if (source == TriggeredSource::Mouse && !_mouseSelection) {
|
if (const auto action = findSelectedAction()) {
|
||||||
return;
|
if (action->lastTriggeredSource() == source) {
|
||||||
}
|
action->setClicked(source);
|
||||||
if (_selected >= 0 && _selected < _actions.size() && _actions[_selected]->isEnabled()) {
|
|
||||||
setPressed(_selected);
|
|
||||||
if (source == TriggeredSource::Mouse) {
|
|
||||||
if (!_actionsData[_pressed].ripple) {
|
|
||||||
auto mask = RippleAnimation::rectMask(QSize(width(), _itemHeight));
|
|
||||||
_actionsData[_pressed].ripple = std::make_unique<RippleAnimation>(_st.ripple, std::move(mask), [this, selected = _pressed] {
|
|
||||||
updateItem(selected);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
_actionsData[_pressed].ripple->add(mapFromGlobal(QCursor::pos()) - QPoint(0, itemTop(_pressed)));
|
|
||||||
} else {
|
|
||||||
itemReleased(source);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Menu::itemReleased(TriggeredSource source) {
|
|
||||||
if (_pressed >= 0 && _pressed < _actions.size()) {
|
|
||||||
auto pressed = _pressed;
|
|
||||||
setPressed(-1);
|
|
||||||
if (source == TriggeredSource::Mouse && _actionsData[pressed].ripple) {
|
|
||||||
_actionsData[pressed].ripple->lastStop();
|
|
||||||
}
|
|
||||||
if (pressed == _selected && _triggeredCallback) {
|
|
||||||
_triggeredCallback(_actions[_selected], itemTop(_selected), source);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Menu::keyPressEvent(QKeyEvent *e) {
|
void Menu::keyPressEvent(QKeyEvent *e) {
|
||||||
auto key = e->key();
|
const auto key = e->key();
|
||||||
if (!_keyPressDelegate || !_keyPressDelegate(key)) {
|
if (!_keyPressDelegate || !_keyPressDelegate(key)) {
|
||||||
handleKeyPress(key);
|
handleKeyPress(key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ItemBase *Menu::findSelectedAction() const {
|
||||||
|
const auto it = ranges::find_if(_actionWidgets, &ItemBase::isSelected);
|
||||||
|
return (it == end(_actionWidgets)) ? nullptr : it->get();
|
||||||
|
}
|
||||||
|
|
||||||
void Menu::handleKeyPress(int key) {
|
void Menu::handleKeyPress(int key) {
|
||||||
if (key == Qt::Key_Enter || key == Qt::Key_Return) {
|
if (key == Qt::Key_Enter || key == Qt::Key_Return) {
|
||||||
itemPressed(TriggeredSource::Keyboard);
|
itemPressed(TriggeredSource::Keyboard);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (key == (style::RightToLeft() ? Qt::Key_Left : Qt::Key_Right)) {
|
if (key == (style::RightToLeft() ? Qt::Key_Left : Qt::Key_Right)) {
|
||||||
if (_selected >= 0 && _actionsData[_selected].hasSubmenu) {
|
if (_selected >= 0 && _actionWidgets[_selected]->hasSubmenu()) {
|
||||||
itemPressed(TriggeredSource::Keyboard);
|
itemPressed(TriggeredSource::Keyboard);
|
||||||
return;
|
return;
|
||||||
} else if (_selected < 0 && !_actions.empty()) {
|
} else if (_selected < 0 && !_actions.empty()) {
|
||||||
|
|
@ -367,7 +485,9 @@ void Menu::handleKeyPress(int key) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto delta = (key == Qt::Key_Down ? 1 : -1), start = _selected;
|
const auto delta = (key == Qt::Key_Down ? 1 : -1);
|
||||||
|
const auto selected = findSelectedAction();
|
||||||
|
auto start = selected ? selected->index() : -1;
|
||||||
if (start < 0 || start >= _actions.size()) {
|
if (start < 0 || start >= _actions.size()) {
|
||||||
start = (delta > 0) ? (_actions.size() - 1) : 0;
|
start = (delta > 0) ? (_actions.size() - 1) : 0;
|
||||||
}
|
}
|
||||||
|
|
@ -379,9 +499,9 @@ void Menu::handleKeyPress(int key) {
|
||||||
} else if (newSelected >= _actions.size()) {
|
} else if (newSelected >= _actions.size()) {
|
||||||
newSelected -= _actions.size();
|
newSelected -= _actions.size();
|
||||||
}
|
}
|
||||||
} while (newSelected != start && (!_actions[newSelected]->isEnabled() || _actions[newSelected]->isSeparator()));
|
} while (newSelected != start && (!_actionWidgets[newSelected]->isEnabled()));
|
||||||
|
|
||||||
if (_actions[newSelected]->isEnabled() && !_actions[newSelected]->isSeparator()) {
|
if (_actionWidgets[newSelected]->isEnabled()) {
|
||||||
_mouseSelection = false;
|
_mouseSelection = false;
|
||||||
setSelected(newSelected);
|
setSelected(newSelected);
|
||||||
}
|
}
|
||||||
|
|
@ -398,77 +518,37 @@ void Menu::clearMouseSelection() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Menu::enterEventHook(QEvent *e) {
|
|
||||||
QPoint mouse = QCursor::pos();
|
|
||||||
if (!rect().marginsRemoved(QMargins(0, _st.skip, 0, _st.skip)).contains(mapFromGlobal(mouse))) {
|
|
||||||
clearMouseSelection();
|
|
||||||
}
|
|
||||||
return TWidget::enterEventHook(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Menu::leaveEventHook(QEvent *e) {
|
|
||||||
clearMouseSelection();
|
|
||||||
return TWidget::leaveEventHook(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Menu::setSelected(int selected) {
|
void Menu::setSelected(int selected) {
|
||||||
if (selected >= _actions.size()) {
|
if (selected >= _actions.size()) {
|
||||||
selected = -1;
|
selected = -1;
|
||||||
}
|
}
|
||||||
if (_selected != selected) {
|
if (_selected != selected) {
|
||||||
updateSelectedItem();
|
const auto source = _mouseSelection
|
||||||
if (_selected >= 0 && _selected != _pressed && _actionsData[_selected].toggle) {
|
? TriggeredSource::Mouse
|
||||||
_actionsData[_selected].toggle->setStyle(_st.itemToggle);
|
: TriggeredSource::Keyboard;
|
||||||
|
// updateSelectedItem();
|
||||||
|
// if (_selected >= 0 && _selected != _pressed && _actionsData[_selected].toggle) {
|
||||||
|
// _actionsData[_selected].toggle->setStyle(_st.itemToggle);
|
||||||
|
// }
|
||||||
|
if (_selected >= 0) {
|
||||||
|
_actionWidgets[_selected]->setSelected(false, source);
|
||||||
}
|
}
|
||||||
_selected = selected;
|
_selected = selected;
|
||||||
if (_selected >= 0 && _actionsData[_selected].toggle && _actions[_selected]->isEnabled()) {
|
if (_selected >= 0) {
|
||||||
_actionsData[_selected].toggle->setStyle(_st.itemToggleOver);
|
_actionWidgets[_selected]->setSelected(true, source);
|
||||||
}
|
}
|
||||||
updateSelectedItem();
|
// if (_selected >= 0 && _actionsData[_selected].toggle && _actions[_selected]->isEnabled()) {
|
||||||
if (_activatedCallback) {
|
// _actionsData[_selected].toggle->setStyle(_st.itemToggleOver);
|
||||||
auto source = _mouseSelection ? TriggeredSource::Mouse : TriggeredSource::Keyboard;
|
// }
|
||||||
_activatedCallback(
|
// updateSelectedItem();
|
||||||
(_selected >= 0) ? _actions[_selected].get() : nullptr,
|
// if (_activatedCallback) {
|
||||||
itemTop(_selected),
|
// auto source = _mouseSelection ? TriggeredSource::Mouse : TriggeredSource::Keyboard;
|
||||||
source);
|
// _activatedCallback(
|
||||||
|
// (_selected >= 0) ? _actions[_selected].get() : nullptr,
|
||||||
|
// itemTop(_selected),
|
||||||
|
// source);
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Menu::setPressed(int pressed) {
|
|
||||||
if (pressed >= _actions.size()) {
|
|
||||||
pressed = -1;
|
|
||||||
}
|
|
||||||
if (_pressed != pressed) {
|
|
||||||
if (_pressed >= 0 && _pressed != _selected && _actionsData[_pressed].toggle) {
|
|
||||||
_actionsData[_pressed].toggle->setStyle(_st.itemToggle);
|
|
||||||
}
|
|
||||||
_pressed = pressed;
|
|
||||||
if (_pressed >= 0 && _actionsData[_pressed].toggle && _actions[_pressed]->isEnabled()) {
|
|
||||||
_actionsData[_pressed].toggle->setStyle(_st.itemToggleOver);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int Menu::itemTop(int index) {
|
|
||||||
if (index > _actions.size()) {
|
|
||||||
index = _actions.size();
|
|
||||||
}
|
|
||||||
int top = _st.skip;
|
|
||||||
for (int i = 0; i < index; ++i) {
|
|
||||||
top += _actions.at(i)->isSeparator() ? _separatorHeight : _itemHeight;
|
|
||||||
}
|
|
||||||
return top;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Menu::updateItem(int index) {
|
|
||||||
if (index >= 0 && index < _actions.size()) {
|
|
||||||
update(0, itemTop(index), width(), _actions[index]->isSeparator() ? _separatorHeight : _itemHeight);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Menu::updateSelectedItem() {
|
|
||||||
updateItem(_selected);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Menu::mouseMoveEvent(QMouseEvent *e) {
|
void Menu::mouseMoveEvent(QMouseEvent *e) {
|
||||||
|
|
@ -476,8 +556,9 @@ void Menu::mouseMoveEvent(QMouseEvent *e) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Menu::handleMouseMove(QPoint globalPosition) {
|
void Menu::handleMouseMove(QPoint globalPosition) {
|
||||||
auto inner = rect().marginsRemoved(QMargins(0, _st.skip, 0, _st.skip));
|
const auto margins = style::margins(0, _st.skip, 0, _st.skip);
|
||||||
auto localPosition = mapFromGlobal(globalPosition);
|
const auto inner = rect().marginsRemoved(margins);
|
||||||
|
const auto localPosition = mapFromGlobal(globalPosition);
|
||||||
if (inner.contains(localPosition)) {
|
if (inner.contains(localPosition)) {
|
||||||
_mouseSelection = true;
|
_mouseSelection = true;
|
||||||
updateSelected(globalPosition);
|
updateSelected(globalPosition);
|
||||||
|
|
@ -499,17 +580,14 @@ void Menu::mouseReleaseEvent(QMouseEvent *e) {
|
||||||
|
|
||||||
void Menu::handleMousePress(QPoint globalPosition) {
|
void Menu::handleMousePress(QPoint globalPosition) {
|
||||||
handleMouseMove(globalPosition);
|
handleMouseMove(globalPosition);
|
||||||
if (rect().contains(mapFromGlobal(globalPosition))) {
|
if (_mousePressDelegate) {
|
||||||
itemPressed(TriggeredSource::Mouse);
|
|
||||||
} else if (_mousePressDelegate) {
|
|
||||||
_mousePressDelegate(globalPosition);
|
_mousePressDelegate(globalPosition);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Menu::handleMouseRelease(QPoint globalPosition) {
|
void Menu::handleMouseRelease(QPoint globalPosition) {
|
||||||
handleMouseMove(globalPosition);
|
if (!rect().contains(mapFromGlobal(globalPosition))
|
||||||
itemReleased(TriggeredSource::Mouse);
|
&& _mouseReleaseDelegate) {
|
||||||
if (!rect().contains(mapFromGlobal(globalPosition)) && _mouseReleaseDelegate) {
|
|
||||||
_mouseReleaseDelegate(globalPosition);
|
_mouseReleaseDelegate(globalPosition);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,13 +6,16 @@
|
||||||
//
|
//
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "base/unique_qptr.h"
|
||||||
#include "ui/rp_widget.h"
|
#include "ui/rp_widget.h"
|
||||||
|
#include "ui/widgets/menu/menu_common.h"
|
||||||
#include "styles/style_widgets.h"
|
#include "styles/style_widgets.h"
|
||||||
|
|
||||||
#include <QtWidgets/QMenu>
|
#include <QtWidgets/QMenu>
|
||||||
|
|
||||||
namespace Ui {
|
namespace Ui {
|
||||||
|
|
||||||
|
class ItemBase;
|
||||||
class ToggleView;
|
class ToggleView;
|
||||||
class RippleAnimation;
|
class RippleAnimation;
|
||||||
|
|
||||||
|
|
@ -30,10 +33,7 @@ public:
|
||||||
|
|
||||||
void clearSelection();
|
void clearSelection();
|
||||||
|
|
||||||
enum class TriggeredSource {
|
using TriggeredSource = ContextMenu::TriggeredSource;
|
||||||
Mouse,
|
|
||||||
Keyboard,
|
|
||||||
};
|
|
||||||
void setChildShown(bool shown) {
|
void setChildShown(bool shown) {
|
||||||
_childShown = shown;
|
_childShown = shown;
|
||||||
}
|
}
|
||||||
|
|
@ -42,10 +42,6 @@ public:
|
||||||
|
|
||||||
const std::vector<not_null<QAction*>> &actions() const;
|
const std::vector<not_null<QAction*>> &actions() const;
|
||||||
|
|
||||||
void setResizedCallback(Fn<void()> callback) {
|
|
||||||
_resizedCallback = std::move(callback);
|
|
||||||
}
|
|
||||||
|
|
||||||
void setActivatedCallback(Fn<void(QAction *action, int actionTop, TriggeredSource source)> callback) {
|
void setActivatedCallback(Fn<void(QAction *action, int actionTop, TriggeredSource source)> callback) {
|
||||||
_activatedCallback = std::move(callback);
|
_activatedCallback = std::move(callback);
|
||||||
}
|
}
|
||||||
|
|
@ -74,38 +70,29 @@ public:
|
||||||
void handleMouseRelease(QPoint globalPosition);
|
void handleMouseRelease(QPoint globalPosition);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void paintEvent(QPaintEvent *e) override;
|
|
||||||
void keyPressEvent(QKeyEvent *e) override;
|
void keyPressEvent(QKeyEvent *e) override;
|
||||||
void mouseMoveEvent(QMouseEvent *e) override;
|
void mouseMoveEvent(QMouseEvent *e) override;
|
||||||
void mousePressEvent(QMouseEvent *e) override;
|
void mousePressEvent(QMouseEvent *e) override;
|
||||||
void mouseReleaseEvent(QMouseEvent *e) override;
|
void mouseReleaseEvent(QMouseEvent *e) override;
|
||||||
void enterEventHook(QEvent *e) override;
|
|
||||||
void leaveEventHook(QEvent *e) override;
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
struct ActionData;
|
class Separator;
|
||||||
|
class Action;
|
||||||
|
|
||||||
void updateSelected(QPoint globalPosition);
|
void updateSelected(QPoint globalPosition);
|
||||||
void actionChanged();
|
|
||||||
void init();
|
void init();
|
||||||
|
|
||||||
// Returns the new width.
|
|
||||||
int processAction(not_null<QAction*> action, int index, int width);
|
|
||||||
not_null<QAction*> addAction(not_null<QAction*> action, const style::icon *icon = nullptr, const style::icon *iconOver = nullptr);
|
not_null<QAction*> addAction(not_null<QAction*> action, const style::icon *icon = nullptr, const style::icon *iconOver = nullptr);
|
||||||
|
|
||||||
void setSelected(int selected);
|
void setSelected(int selected);
|
||||||
void setPressed(int pressed);
|
|
||||||
void clearMouseSelection();
|
void clearMouseSelection();
|
||||||
|
|
||||||
int itemTop(int index);
|
|
||||||
void updateItem(int index);
|
|
||||||
void updateSelectedItem();
|
|
||||||
void itemPressed(TriggeredSource source);
|
void itemPressed(TriggeredSource source);
|
||||||
void itemReleased(TriggeredSource source);
|
|
||||||
|
ItemBase *findSelectedAction() const;
|
||||||
|
|
||||||
const style::Menu &_st;
|
const style::Menu &_st;
|
||||||
|
|
||||||
Fn<void()> _resizedCallback;
|
|
||||||
Fn<void(QAction *action, int actionTop, TriggeredSource source)> _activatedCallback;
|
Fn<void(QAction *action, int actionTop, TriggeredSource source)> _activatedCallback;
|
||||||
Fn<void(QAction *action, int actionTop, TriggeredSource source)> _triggeredCallback;
|
Fn<void(QAction *action, int actionTop, TriggeredSource source)> _triggeredCallback;
|
||||||
Fn<bool(int key)> _keyPressDelegate;
|
Fn<bool(int key)> _keyPressDelegate;
|
||||||
|
|
@ -115,15 +102,13 @@ private:
|
||||||
|
|
||||||
QMenu *_wappedMenu = nullptr;
|
QMenu *_wappedMenu = nullptr;
|
||||||
std::vector<not_null<QAction*>> _actions;
|
std::vector<not_null<QAction*>> _actions;
|
||||||
std::vector<ActionData> _actionsData;
|
std::vector<base::unique_qptr<ItemBase>> _actionWidgets;
|
||||||
|
|
||||||
int _forceWidth = 0;
|
int _forceWidth = 0;
|
||||||
int _itemHeight, _separatorHeight;
|
|
||||||
|
|
||||||
bool _mouseSelection = false;
|
bool _mouseSelection = false;
|
||||||
|
|
||||||
int _selected = -1;
|
int _selected = -1;
|
||||||
int _pressed = -1;
|
|
||||||
bool _childShown = false;
|
bool _childShown = false;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
|
||||||
16
ui/widgets/menu/menu_common.h
Normal file
16
ui/widgets/menu/menu_common.h
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
// This file is part of Desktop App Toolkit,
|
||||||
|
// a set of libraries for developing nice desktop applications.
|
||||||
|
//
|
||||||
|
// For license and copyright information please follow this link:
|
||||||
|
// https://github.com/desktop-app/legal/blob/master/LEGAL
|
||||||
|
//
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
namespace Ui::ContextMenu {
|
||||||
|
|
||||||
|
enum class TriggeredSource {
|
||||||
|
Mouse,
|
||||||
|
Keyboard,
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Ui::ContextMenu
|
||||||
108
ui/widgets/menu/menu_item_base.cpp
Normal file
108
ui/widgets/menu/menu_item_base.cpp
Normal file
|
|
@ -0,0 +1,108 @@
|
||||||
|
// This file is part of Desktop App Toolkit,
|
||||||
|
// a set of libraries for developing nice desktop applications.
|
||||||
|
//
|
||||||
|
// For license and copyright information please follow this link:
|
||||||
|
// https://github.com/desktop-app/legal/blob/master/LEGAL
|
||||||
|
//
|
||||||
|
#include "ui/widgets/menu/menu_item_base.h"
|
||||||
|
|
||||||
|
namespace Ui {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
using TriggeredSource = Menu::TriggeredSource;
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
ItemBase::ItemBase(
|
||||||
|
not_null<RpWidget*> parent,
|
||||||
|
const style::Menu &st, int index)
|
||||||
|
: RippleButton(parent, st.ripple)
|
||||||
|
, _index(index) {
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ItemBase::setSelected(
|
||||||
|
bool selected,
|
||||||
|
TriggeredSource source) {
|
||||||
|
if (!isEnabled()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (_selected.current() != selected) {
|
||||||
|
_lastTriggeredSource = source;
|
||||||
|
_selected = selected;
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ItemBase::isSelected() const {
|
||||||
|
return _selected.current();
|
||||||
|
}
|
||||||
|
rpl::producer<bool> ItemBase::selects() const {
|
||||||
|
return _selected.changes();
|
||||||
|
}
|
||||||
|
|
||||||
|
TriggeredSource ItemBase::lastTriggeredSource() const {
|
||||||
|
return _lastTriggeredSource;
|
||||||
|
}
|
||||||
|
|
||||||
|
int ItemBase::index() const {
|
||||||
|
return _index;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ItemBase::setClicked(TriggeredSource source) {
|
||||||
|
if (isEnabled()) {
|
||||||
|
_lastTriggeredSource = source;
|
||||||
|
_clicks.fire({});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rpl::producer<> ItemBase::clicks() const {
|
||||||
|
return rpl::merge(
|
||||||
|
AbstractButton::clicks() | rpl::to_empty,
|
||||||
|
_clicks.events());
|
||||||
|
}
|
||||||
|
|
||||||
|
rpl::producer<int> ItemBase::contentWidthValue() const {
|
||||||
|
return _contentWidth.value();
|
||||||
|
}
|
||||||
|
|
||||||
|
int ItemBase::contentWidth() const {
|
||||||
|
return _contentWidth.current();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ItemBase::hasSubmenu() const {
|
||||||
|
return _hasSubmenu;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ItemBase::setHasSubmenu(bool value) {
|
||||||
|
_hasSubmenu = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ItemBase::init() {
|
||||||
|
events(
|
||||||
|
) | rpl::filter([=](not_null<QEvent*> e) {
|
||||||
|
return isEnabled()
|
||||||
|
&& isSelected()
|
||||||
|
&& (e->type() == QEvent::MouseButtonRelease);
|
||||||
|
}) | rpl::to_empty | rpl::start_with_next([=] {
|
||||||
|
const auto point = mapFromGlobal(QCursor::pos());
|
||||||
|
if (!rect().contains(point)) {
|
||||||
|
setSelected(false);
|
||||||
|
}
|
||||||
|
}, lifetime());
|
||||||
|
}
|
||||||
|
|
||||||
|
void ItemBase::initResizeHook(rpl::producer<QSize> &&size) {
|
||||||
|
std::move(
|
||||||
|
size
|
||||||
|
) | rpl::start_with_next([=](QSize s) {
|
||||||
|
resize(s.width(), contentHeight());
|
||||||
|
}, lifetime());
|
||||||
|
}
|
||||||
|
|
||||||
|
void ItemBase::setContentWidth(int w) {
|
||||||
|
_contentWidth = w;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Ui
|
||||||
65
ui/widgets/menu/menu_item_base.h
Normal file
65
ui/widgets/menu/menu_item_base.h
Normal file
|
|
@ -0,0 +1,65 @@
|
||||||
|
// 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/widgets/buttons.h"
|
||||||
|
#include "ui/widgets/menu.h"
|
||||||
|
#include "ui/widgets/menu/menu_common.h"
|
||||||
|
#include "styles/style_widgets.h"
|
||||||
|
|
||||||
|
namespace Ui {
|
||||||
|
|
||||||
|
class ItemBase : public RippleButton {
|
||||||
|
public:
|
||||||
|
ItemBase(not_null<RpWidget*> parent, const style::Menu &st, int index);
|
||||||
|
|
||||||
|
using TriggeredSource = ContextMenu::TriggeredSource;
|
||||||
|
TriggeredSource lastTriggeredSource() const;
|
||||||
|
|
||||||
|
rpl::producer<bool> selects() const;
|
||||||
|
void setSelected(
|
||||||
|
bool selected,
|
||||||
|
TriggeredSource source = TriggeredSource::Mouse);
|
||||||
|
bool isSelected() const;
|
||||||
|
|
||||||
|
int index() const;
|
||||||
|
|
||||||
|
void setClicked(TriggeredSource source = TriggeredSource::Mouse);
|
||||||
|
|
||||||
|
rpl::producer<> clicks() const;
|
||||||
|
|
||||||
|
rpl::producer<int> contentWidthValue() const;
|
||||||
|
int contentWidth() const;
|
||||||
|
void setContentWidth(int w);
|
||||||
|
|
||||||
|
bool hasSubmenu() const;
|
||||||
|
void setHasSubmenu(bool value);
|
||||||
|
|
||||||
|
virtual QAction *action() const = 0;
|
||||||
|
virtual bool isEnabled() const = 0;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void init();
|
||||||
|
void initResizeHook(rpl::producer<QSize> &&size);
|
||||||
|
|
||||||
|
virtual int contentHeight() const = 0;
|
||||||
|
|
||||||
|
private:
|
||||||
|
const int _index;
|
||||||
|
|
||||||
|
bool _hasSubmenu = false;
|
||||||
|
|
||||||
|
rpl::variable<bool> _selected = false;
|
||||||
|
rpl::event_stream<> _clicks;
|
||||||
|
|
||||||
|
rpl::variable<int> _contentWidth = 0;
|
||||||
|
|
||||||
|
TriggeredSource _lastTriggeredSource = TriggeredSource::Mouse;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Ui
|
||||||
|
|
@ -52,7 +52,10 @@ void PopupMenu::init() {
|
||||||
hideMenu(true);
|
hideMenu(true);
|
||||||
}, lifetime());
|
}, lifetime());
|
||||||
|
|
||||||
_menu->setResizedCallback([this] { handleMenuResize(); });
|
_menu->sizeValue(
|
||||||
|
) | rpl::start_with_next([=] {
|
||||||
|
handleMenuResize();
|
||||||
|
}, _menu->lifetime());
|
||||||
_menu->setActivatedCallback([this](QAction *action, int actionTop, TriggeredSource source) {
|
_menu->setActivatedCallback([this](QAction *action, int actionTop, TriggeredSource source) {
|
||||||
handleActivated(action, actionTop, source);
|
handleActivated(action, actionTop, source);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue