init
Some checks failed
Docker. / Ubuntu (push) Has been cancelled
User-agent updater. / User-agent (push) Failing after 15s
Lock Threads / lock (push) Failing after 10s
Waiting for answer. / waiting-for-answer (push) Failing after 22s
Needs user action. / needs-user-action (push) Failing after 8s
Can't reproduce. / cant-reproduce (push) Failing after 8s
Close stale issues and PRs / stale (push) Has been cancelled
Some checks failed
Docker. / Ubuntu (push) Has been cancelled
User-agent updater. / User-agent (push) Failing after 15s
Lock Threads / lock (push) Failing after 10s
Waiting for answer. / waiting-for-answer (push) Failing after 22s
Needs user action. / needs-user-action (push) Failing after 8s
Can't reproduce. / cant-reproduce (push) Failing after 8s
Close stale issues and PRs / stale (push) Has been cancelled
This commit is contained in:
460
Telegram/lib_ui/ui/widgets/menu/menu.cpp
Normal file
460
Telegram/lib_ui/ui/widgets/menu/menu.cpp
Normal file
@@ -0,0 +1,460 @@
|
||||
// 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.h"
|
||||
|
||||
#include "ui/widgets/menu/menu_action.h"
|
||||
#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>
|
||||
|
||||
namespace Ui::Menu {
|
||||
|
||||
Menu::Menu(QWidget *parent, const style::Menu &st)
|
||||
: RpWidget(parent)
|
||||
, _st(st) {
|
||||
init();
|
||||
}
|
||||
|
||||
Menu::Menu(QWidget *parent, QMenu *menu, const style::Menu &st)
|
||||
: RpWidget(parent)
|
||||
, _st(st)
|
||||
, _wappedMenu(menu) {
|
||||
init();
|
||||
|
||||
_wappedMenu->setParent(this);
|
||||
for (auto action : _wappedMenu->actions()) {
|
||||
addAction(action);
|
||||
}
|
||||
_wappedMenu->hide();
|
||||
}
|
||||
|
||||
Menu::~Menu() = default;
|
||||
|
||||
void Menu::init() {
|
||||
resize(_forceWidth ? _forceWidth : _st.widthMin, _st.skip * 2);
|
||||
|
||||
setMouseTracking(true);
|
||||
|
||||
if (_st.itemBg->c.alpha() == 255) {
|
||||
setAttribute(Qt::WA_OpaquePaintEvent);
|
||||
}
|
||||
|
||||
paintRequest(
|
||||
) | rpl::on_next([=](const QRect &clip) {
|
||||
QPainter(this).fillRect(clip, _st.itemBg);
|
||||
}, lifetime());
|
||||
|
||||
positionValue(
|
||||
) | rpl::on_next([=] {
|
||||
handleMouseMove(QCursor::pos());
|
||||
}, lifetime());
|
||||
}
|
||||
|
||||
not_null<QAction*> Menu::addAction(
|
||||
const QString &text,
|
||||
Fn<void()> callback,
|
||||
const style::icon *icon,
|
||||
const style::icon *iconOver) {
|
||||
auto action = CreateAction(this, text, std::move(callback));
|
||||
return addAction(std::move(action), icon, iconOver);
|
||||
}
|
||||
|
||||
not_null<QAction*> Menu::addAction(
|
||||
const QString &text,
|
||||
std::unique_ptr<QMenu> submenu,
|
||||
const style::icon *icon,
|
||||
const style::icon *iconOver) {
|
||||
const auto action = new QAction(text, this);
|
||||
action->setMenu(submenu.release());
|
||||
return addAction(action, icon, iconOver);
|
||||
}
|
||||
|
||||
not_null<QAction*> Menu::addAction(
|
||||
not_null<QAction*> action,
|
||||
const style::icon *icon,
|
||||
const style::icon *iconOver) {
|
||||
if (action->isSeparator()) {
|
||||
return addSeparator();
|
||||
}
|
||||
auto item = base::make_unique_q<Action>(
|
||||
this,
|
||||
_st,
|
||||
std::move(action),
|
||||
icon,
|
||||
iconOver ? iconOver : icon);
|
||||
return addAction(std::move(item));
|
||||
}
|
||||
|
||||
not_null<QAction*> Menu::addAction(base::unique_qptr<ItemBase> widget) {
|
||||
return insertAction(_actions.size(), std::move(widget));
|
||||
}
|
||||
|
||||
not_null<QAction*> Menu::insertAction(
|
||||
int position,
|
||||
base::unique_qptr<ItemBase> widget) {
|
||||
Expects(position >= 0 && position <= _actions.size());
|
||||
Expects(position >= 0 && position <= _actionWidgets.size());
|
||||
|
||||
const auto raw = widget.get();
|
||||
const auto action = raw->action();
|
||||
_actions.insert(begin(_actions) + position, action);
|
||||
|
||||
raw->setMenuAsParent(this);
|
||||
raw->show();
|
||||
raw->setIndex(position);
|
||||
for (auto i = position, to = int(_actionWidgets.size()); i != to; ++i) {
|
||||
_actionWidgets[i]->setIndex(i + 1);
|
||||
}
|
||||
_actionWidgets.insert(
|
||||
begin(_actionWidgets) + position,
|
||||
std::move(widget));
|
||||
|
||||
raw->selects(
|
||||
) | rpl::on_next([=](const CallbackData &data) {
|
||||
if (!data.selected) {
|
||||
if (!findSelectedAction()
|
||||
&& data.index < _actionWidgets.size()
|
||||
&& _childShownAction == data.action) {
|
||||
const auto widget = _actionWidgets[data.index].get();
|
||||
widget->setSelected(true, widget->lastTriggeredSource());
|
||||
}
|
||||
return;
|
||||
}
|
||||
_lastSelectedByMouse = (data.source == TriggeredSource::Mouse);
|
||||
for (auto i = 0; i < _actionWidgets.size(); i++) {
|
||||
if (i != data.index) {
|
||||
_actionWidgets[i]->setSelected(false);
|
||||
}
|
||||
}
|
||||
if (_activatedCallback) {
|
||||
_activatedCallback(data);
|
||||
}
|
||||
}, raw->lifetime());
|
||||
|
||||
raw->clicks(
|
||||
) | rpl::on_next([=](const CallbackData &data) {
|
||||
if (_triggeredCallback) {
|
||||
_triggeredCallback(data);
|
||||
}
|
||||
}, raw->lifetime());
|
||||
|
||||
QObject::connect(action.get(), &QAction::changed, raw, [=] {
|
||||
// Select an item under mouse that was disabled and became enabled.
|
||||
if (_lastSelectedByMouse
|
||||
&& !findSelectedAction()
|
||||
&& action->isEnabled()) {
|
||||
updateSelected(QCursor::pos());
|
||||
}
|
||||
});
|
||||
|
||||
raw->minWidthValue(
|
||||
) | rpl::skip(1) | rpl::filter([=] {
|
||||
return !_forceWidth;
|
||||
}) | rpl::on_next([=] {
|
||||
resizeFromInner(recountWidth(), height());
|
||||
}, raw->lifetime());
|
||||
|
||||
raw->heightValue(
|
||||
) | rpl::skip(1) | rpl::on_next([=] {
|
||||
resizeFromInner(width(), recountHeight());
|
||||
}, raw->lifetime());
|
||||
|
||||
resizeFromInner(recountWidth(), recountHeight());
|
||||
|
||||
updateSelected(QCursor::pos());
|
||||
|
||||
return action;
|
||||
}
|
||||
|
||||
int Menu::recountWidth() const {
|
||||
return _forceWidth
|
||||
? _forceWidth
|
||||
: std::clamp(
|
||||
(_actionWidgets.empty()
|
||||
? 0
|
||||
: (*ranges::max_element(
|
||||
_actionWidgets,
|
||||
std::less<>(),
|
||||
&ItemBase::minWidth))->minWidth()),
|
||||
_st.widthMin,
|
||||
_st.widthMax);
|
||||
}
|
||||
|
||||
int Menu::recountHeight() const {
|
||||
auto result = 0;
|
||||
for (const auto &widget : _actionWidgets) {
|
||||
if (widget->y() != result) {
|
||||
widget->move(0, result);
|
||||
}
|
||||
result += widget->height();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void Menu::removeAction(int position) {
|
||||
Expects(position >= 0 && position < actions().size());
|
||||
|
||||
_actionWidgets.erase(begin(_actionWidgets) + position);
|
||||
if (_actions[position]->parent() == this) {
|
||||
delete _actions[position];
|
||||
}
|
||||
_actions.erase(begin(_actions) + position);
|
||||
resizeFromInner(width(), recountHeight());
|
||||
}
|
||||
|
||||
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,
|
||||
st ? *st : _st.separator,
|
||||
separator);
|
||||
return addAction(std::move(item));
|
||||
}
|
||||
|
||||
void Menu::clearActions() {
|
||||
_actionWidgets.clear();
|
||||
for (auto action : base::take(_actions)) {
|
||||
if (action->parent() == this) {
|
||||
delete action;
|
||||
}
|
||||
}
|
||||
resizeFromInner(_forceWidth ? _forceWidth : _st.widthMin, _st.skip * 2);
|
||||
}
|
||||
|
||||
void Menu::clearLastSeparator() {
|
||||
if (_actionWidgets.empty() || _actions.empty()) {
|
||||
return;
|
||||
}
|
||||
if (_actionWidgets.back()->action() == _actions.back()) {
|
||||
if (_actions.back()->isSeparator()) {
|
||||
resizeFromInner(
|
||||
width(),
|
||||
height() - _actionWidgets.back()->height());
|
||||
_actionWidgets.pop_back();
|
||||
if (_actions.back()->parent() == this) {
|
||||
delete _actions.back();
|
||||
_actions.pop_back();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Menu::finishAnimating() {
|
||||
for (const auto &widget : _actionWidgets) {
|
||||
widget->finishAnimating();
|
||||
}
|
||||
}
|
||||
|
||||
bool Menu::empty() const {
|
||||
return _actionWidgets.empty();
|
||||
}
|
||||
|
||||
void Menu::resizeFromInner(int w, int h) {
|
||||
if (const auto s = QSize(w, h); s != size()) {
|
||||
resize(s);
|
||||
_resizesFromInner.fire({});
|
||||
}
|
||||
}
|
||||
|
||||
rpl::producer<> Menu::resizesFromInner() const {
|
||||
return _resizesFromInner.events();
|
||||
}
|
||||
|
||||
rpl::producer<ScrollToRequest> Menu::scrollToRequests() const {
|
||||
return _scrollToRequests.events();
|
||||
}
|
||||
|
||||
void Menu::setShowSource(TriggeredSource source) {
|
||||
const auto mouseSelection = (source == TriggeredSource::Mouse);
|
||||
setSelected(
|
||||
(mouseSelection || _actions.empty()) ? -1 : 0,
|
||||
mouseSelection);
|
||||
}
|
||||
|
||||
const std::vector<not_null<QAction*>> &Menu::actions() const {
|
||||
return _actions;
|
||||
}
|
||||
|
||||
void Menu::setForceWidth(int forceWidth) {
|
||||
_forceWidth = forceWidth;
|
||||
resizeFromInner(_forceWidth, height());
|
||||
}
|
||||
|
||||
void Menu::updateSelected(QPoint globalPosition) {
|
||||
const auto p = mapFromGlobal(globalPosition) - QPoint(0, _st.skip);
|
||||
for (const auto &widget : _actionWidgets) {
|
||||
const auto widgetRect = QRect(widget->pos(), widget->size());
|
||||
if (widgetRect.contains(p)) {
|
||||
_lastSelectedByMouse = true;
|
||||
|
||||
// It may actually fail to become selected (if it is disabled).
|
||||
widget->setSelected(true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Menu::itemPressed(TriggeredSource source) {
|
||||
if (const auto action = findSelectedAction()) {
|
||||
if (action->lastTriggeredSource() == source) {
|
||||
action->setClicked(source);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Menu::keyPressEvent(QKeyEvent *e) {
|
||||
const auto key = e->key();
|
||||
if (!_keyPressDelegate || !_keyPressDelegate(key)) {
|
||||
handleKeyPress(e);
|
||||
}
|
||||
}
|
||||
|
||||
ItemBase *Menu::findSelectedAction() const {
|
||||
const auto it = ranges::find_if(_actionWidgets, &ItemBase::isSelected);
|
||||
return (it == end(_actionWidgets)) ? nullptr : it->get();
|
||||
}
|
||||
|
||||
void Menu::handleKeyPress(not_null<QKeyEvent*> e) {
|
||||
const auto key = e->key();
|
||||
const auto selected = findSelectedAction();
|
||||
if ((key != Qt::Key_Up && key != Qt::Key_Down) || _actions.empty()) {
|
||||
if (selected) {
|
||||
selected->handleKeyPress(e);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const auto delta = (key == Qt::Key_Down ? 1 : -1);
|
||||
auto start = selected ? selected->index() : -1;
|
||||
if (start < 0 || start >= _actions.size()) {
|
||||
start = (delta > 0) ? (_actions.size() - 1) : 0;
|
||||
}
|
||||
auto newSelected = start;
|
||||
do {
|
||||
newSelected += delta;
|
||||
if (newSelected < 0) {
|
||||
newSelected += _actions.size();
|
||||
} else if (newSelected >= _actions.size()) {
|
||||
newSelected -= _actions.size();
|
||||
}
|
||||
} while (newSelected != start
|
||||
&& (!_actionWidgets[newSelected]->isEnabled()));
|
||||
|
||||
if (_actionWidgets[newSelected]->isEnabled()) {
|
||||
setSelected(newSelected, false);
|
||||
}
|
||||
}
|
||||
|
||||
void Menu::clearSelection() {
|
||||
setSelected(-1, false);
|
||||
}
|
||||
|
||||
void Menu::clearMouseSelection() {
|
||||
const auto selected = findSelectedAction();
|
||||
const auto mouseSelection = selected
|
||||
? (selected->lastTriggeredSource() == TriggeredSource::Mouse)
|
||||
: false;
|
||||
if (mouseSelection && !_childShownAction) {
|
||||
clearSelection();
|
||||
}
|
||||
}
|
||||
|
||||
void Menu::setSelected(int selected, bool isMouseSelection) {
|
||||
if (selected >= _actionWidgets.size()) {
|
||||
selected = -1;
|
||||
}
|
||||
const auto source = isMouseSelection
|
||||
? TriggeredSource::Mouse
|
||||
: TriggeredSource::Keyboard;
|
||||
if (selected >= 0 && source == TriggeredSource::Keyboard) {
|
||||
const auto widget = _actionWidgets[selected].get();
|
||||
_scrollToRequests.fire({
|
||||
widget->y(),
|
||||
widget->y() + widget->height(),
|
||||
});
|
||||
}
|
||||
if (const auto selectedItem = findSelectedAction()) {
|
||||
if (selectedItem->index() == selected) {
|
||||
return;
|
||||
}
|
||||
selectedItem->setSelected(false, source);
|
||||
}
|
||||
if (selected >= 0) {
|
||||
_actionWidgets[selected].get()->setSelected(true, source);
|
||||
}
|
||||
}
|
||||
|
||||
void Menu::mouseMoveEvent(QMouseEvent *e) {
|
||||
handleMouseMove(e->globalPos());
|
||||
}
|
||||
|
||||
void Menu::handleMouseMove(QPoint globalPosition) {
|
||||
const auto margins = style::margins(0, _st.skip, 0, _st.skip);
|
||||
const auto inner = rect().marginsRemoved(margins);
|
||||
const auto localPosition = mapFromGlobal(globalPosition);
|
||||
if (inner.contains(localPosition)) {
|
||||
updateSelected(globalPosition);
|
||||
} else {
|
||||
clearMouseSelection();
|
||||
if (_mouseMoveDelegate) {
|
||||
_mouseMoveDelegate(globalPosition);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Menu::mousePressEvent(QMouseEvent *e) {
|
||||
handleMousePress(e->globalPos());
|
||||
}
|
||||
|
||||
void Menu::mouseReleaseEvent(QMouseEvent *e) {
|
||||
handleMouseRelease(e->globalPos());
|
||||
}
|
||||
|
||||
void Menu::handleMousePress(QPoint globalPosition) {
|
||||
handleMouseMove(globalPosition);
|
||||
const auto margins = style::margins(0, _st.skip, 0, _st.skip);
|
||||
const auto inner = rect().marginsRemoved(margins);
|
||||
const auto localPosition = mapFromGlobal(globalPosition);
|
||||
const auto pressed = (inner.contains(localPosition)
|
||||
&& _lastSelectedByMouse)
|
||||
? findSelectedAction()
|
||||
: nullptr;
|
||||
if (pressed) {
|
||||
pressed->setClicked();
|
||||
} else {
|
||||
if (_mousePressDelegate) {
|
||||
_mousePressDelegate(globalPosition);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Menu::handleMouseRelease(QPoint globalPosition) {
|
||||
if (_pressedOutside) {
|
||||
_pressedOutside = false;
|
||||
updateSelected(globalPosition);
|
||||
if (const auto selected = findSelectedAction()) {
|
||||
selected->setClicked(TriggeredSource::Mouse);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (!rect().contains(mapFromGlobal(globalPosition))
|
||||
&& _mouseReleaseDelegate) {
|
||||
_mouseReleaseDelegate(globalPosition);
|
||||
}
|
||||
}
|
||||
|
||||
void Menu::handlePressedOutside(QPoint globalPosition) {
|
||||
_pressedOutside = true;
|
||||
updateSelected(globalPosition);
|
||||
}
|
||||
|
||||
} // namespace Ui::Menu
|
||||
159
Telegram/lib_ui/ui/widgets/menu/menu.h
Normal file
159
Telegram/lib_ui/ui/widgets/menu/menu.h
Normal file
@@ -0,0 +1,159 @@
|
||||
// This file is part of Desktop App Toolkit,
|
||||
// a set of libraries for developing nice desktop applications.
|
||||
//
|
||||
// For license and copyright information please follow this link:
|
||||
// https://github.com/desktop-app/legal/blob/master/LEGAL
|
||||
//
|
||||
#pragma once
|
||||
|
||||
#include "base/unique_qptr.h"
|
||||
#include "ui/rp_widget.h"
|
||||
#include "ui/widgets/menu/menu_common.h"
|
||||
|
||||
#include <QtWidgets/QMenu>
|
||||
|
||||
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;
|
||||
class RippleAnimation;
|
||||
|
||||
class Menu : public RpWidget {
|
||||
public:
|
||||
Menu(QWidget *parent, const style::Menu &st = st::defaultMenu);
|
||||
Menu(QWidget *parent, QMenu *menu, const style::Menu &st = st::defaultMenu);
|
||||
~Menu();
|
||||
|
||||
[[nodiscard]] const style::Menu &st() const {
|
||||
return _st;
|
||||
}
|
||||
|
||||
not_null<QAction*> addAction(base::unique_qptr<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*> addAction(
|
||||
const QString &text,
|
||||
std::unique_ptr<QMenu> submenu,
|
||||
const style::icon *icon = nullptr,
|
||||
const style::icon *iconOver = nullptr);
|
||||
not_null<QAction*> addSeparator(
|
||||
const style::MenuSeparator *st = nullptr);
|
||||
not_null<QAction*> insertAction(
|
||||
int position,
|
||||
base::unique_qptr<ItemBase> widget);
|
||||
void removeAction(int position);
|
||||
void clearActions();
|
||||
void clearLastSeparator();
|
||||
void finishAnimating();
|
||||
|
||||
bool empty() const;
|
||||
|
||||
void clearSelection();
|
||||
|
||||
void setChildShownAction(QAction *action) {
|
||||
_childShownAction = action;
|
||||
}
|
||||
void setShowSource(TriggeredSource source);
|
||||
void setForceWidth(int forceWidth);
|
||||
|
||||
const std::vector<not_null<QAction*>> &actions() const;
|
||||
|
||||
void setActivatedCallback(Fn<void(const CallbackData &data)> callback) {
|
||||
_activatedCallback = std::move(callback);
|
||||
}
|
||||
void setTriggeredCallback(Fn<void(const CallbackData &data)> callback) {
|
||||
_triggeredCallback = std::move(callback);
|
||||
}
|
||||
|
||||
[[nodiscard]] ItemBase *findSelectedAction() const;
|
||||
|
||||
void setKeyPressDelegate(Fn<bool(int key)> delegate) {
|
||||
_keyPressDelegate = std::move(delegate);
|
||||
}
|
||||
void handleKeyPress(not_null<QKeyEvent*> e);
|
||||
|
||||
void setMouseMoveDelegate(Fn<void(QPoint globalPosition)> delegate) {
|
||||
_mouseMoveDelegate = std::move(delegate);
|
||||
}
|
||||
void handleMouseMove(QPoint globalPosition);
|
||||
|
||||
void setMousePressDelegate(Fn<void(QPoint globalPosition)> delegate) {
|
||||
_mousePressDelegate = std::move(delegate);
|
||||
}
|
||||
void handleMousePress(QPoint globalPosition);
|
||||
|
||||
void setMouseReleaseDelegate(Fn<void(QPoint globalPosition)> delegate) {
|
||||
_mouseReleaseDelegate = std::move(delegate);
|
||||
}
|
||||
void handleMouseRelease(QPoint globalPosition);
|
||||
|
||||
void handlePressedOutside(QPoint globalPosition);
|
||||
|
||||
void setSelected(int selected, bool isMouseSelection);
|
||||
|
||||
[[nodiscard]] rpl::producer<> resizesFromInner() const;
|
||||
[[nodiscard]] rpl::producer<ScrollToRequest> scrollToRequests() const;
|
||||
|
||||
protected:
|
||||
void keyPressEvent(QKeyEvent *e) override;
|
||||
void mouseMoveEvent(QMouseEvent *e) override;
|
||||
void mousePressEvent(QMouseEvent *e) override;
|
||||
void mouseReleaseEvent(QMouseEvent *e) override;
|
||||
|
||||
private:
|
||||
void updateSelected(QPoint globalPosition);
|
||||
void init();
|
||||
|
||||
not_null<QAction*> addAction(
|
||||
not_null<QAction*> action,
|
||||
const style::icon *icon = nullptr,
|
||||
const style::icon *iconOver = nullptr);
|
||||
|
||||
void clearMouseSelection();
|
||||
|
||||
void itemPressed(TriggeredSource source);
|
||||
|
||||
[[nodiscard]] int recountWidth() const;
|
||||
[[nodiscard]] int recountHeight() const;
|
||||
void resizeFromInner(int w, int h);
|
||||
|
||||
const style::Menu &_st;
|
||||
|
||||
Fn<void(const CallbackData &data)> _activatedCallback;
|
||||
Fn<void(const CallbackData &data)> _triggeredCallback;
|
||||
Fn<bool(int key)> _keyPressDelegate;
|
||||
Fn<void(QPoint globalPosition)> _mouseMoveDelegate;
|
||||
Fn<void(QPoint globalPosition)> _mousePressDelegate;
|
||||
Fn<void(QPoint globalPosition)> _mouseReleaseDelegate;
|
||||
|
||||
QMenu *_wappedMenu = nullptr;
|
||||
std::vector<not_null<QAction*>> _actions;
|
||||
std::vector<base::unique_qptr<ItemBase>> _actionWidgets;
|
||||
|
||||
int _forceWidth = 0;
|
||||
bool _lastSelectedByMouse = false;
|
||||
bool _pressedOutside = false;
|
||||
|
||||
QPointer<QAction> _childShownAction;
|
||||
|
||||
rpl::event_stream<> _resizesFromInner;
|
||||
rpl::event_stream<ScrollToRequest> _scrollToRequests;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Ui::Menu
|
||||
227
Telegram/lib_ui/ui/widgets/menu/menu_action.cpp
Normal file
227
Telegram/lib_ui/ui/widgets/menu/menu_action.cpp
Normal file
@@ -0,0 +1,227 @@
|
||||
// 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_action.h"
|
||||
|
||||
#include "ui/effects/ripple_animation.h"
|
||||
#include "ui/painter.h"
|
||||
|
||||
#include <QtGui/QtEvents>
|
||||
|
||||
namespace Ui::Menu {
|
||||
namespace {
|
||||
|
||||
[[nodiscard]] TextWithEntities ParseMenuItem(const QString &text) {
|
||||
auto result = TextWithEntities();
|
||||
result.text.reserve(text.size());
|
||||
auto afterAmpersand = false;
|
||||
for (const auto &ch : text) {
|
||||
if (afterAmpersand) {
|
||||
afterAmpersand = false;
|
||||
if (ch == '&') {
|
||||
result.text.append(ch);
|
||||
} else {
|
||||
result.entities.append(EntityInText{
|
||||
EntityType::Underline,
|
||||
int(result.text.size()),
|
||||
1 });
|
||||
result.text.append(ch);
|
||||
}
|
||||
} else if (ch == '&') {
|
||||
afterAmpersand = true;
|
||||
} else {
|
||||
result.text.append(ch);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
TextParseOptions MenuTextOptions = {
|
||||
TextParseLinks | TextParseMarkdown, // flags
|
||||
0, // maxw
|
||||
0, // maxh
|
||||
Qt::LayoutDirectionAuto, // dir
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
Action::Action(
|
||||
not_null<RpWidget*> parent,
|
||||
const style::Menu &st,
|
||||
not_null<QAction*> action,
|
||||
const style::icon *icon,
|
||||
const style::icon *iconOver)
|
||||
: ItemBase(parent, st)
|
||||
, _action(action)
|
||||
, _st(st)
|
||||
, _icon(icon)
|
||||
, _iconOver(iconOver)
|
||||
, _height(_st.itemPadding.top()
|
||||
+ _st.itemStyle.font->height
|
||||
+ _st.itemPadding.bottom()) {
|
||||
setAcceptBoth(true);
|
||||
|
||||
initResizeHook(parent->sizeValue());
|
||||
processAction();
|
||||
|
||||
enableMouseSelecting();
|
||||
|
||||
connect(_action, &QAction::changed, [=] { processAction(); });
|
||||
}
|
||||
|
||||
bool Action::hasSubmenu() const {
|
||||
return _action->menu() != nullptr;
|
||||
}
|
||||
|
||||
void Action::paintEvent(QPaintEvent *e) {
|
||||
Painter p(this);
|
||||
paint(p);
|
||||
}
|
||||
|
||||
void Action::paintBackground(QPainter &p, bool selected) {
|
||||
if (selected && _st.itemBgOver->c.alpha() < 255) {
|
||||
p.fillRect(0, 0, width(), _height, _st.itemBg);
|
||||
}
|
||||
p.fillRect(
|
||||
QRect(0, 0, width(), _height),
|
||||
selected ? _st.itemBgOver : _st.itemBg);
|
||||
}
|
||||
|
||||
void Action::paintText(Painter &p) {
|
||||
_text.drawLeftElided(
|
||||
p,
|
||||
_st.itemPadding.left(),
|
||||
_st.itemPadding.top(),
|
||||
_textWidth,
|
||||
width());
|
||||
}
|
||||
|
||||
void Action::paint(Painter &p) {
|
||||
const auto enabled = isEnabled();
|
||||
const auto selected = isSelected();
|
||||
paintBackground(p, selected);
|
||||
if (enabled) {
|
||||
RippleButton::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));
|
||||
paintText(p);
|
||||
if (hasSubmenu()) {
|
||||
const auto skip = _st.itemRightSkip;
|
||||
const auto left = width() - skip - _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 Action::processAction() {
|
||||
accessibilityNameChanged();
|
||||
|
||||
setPointerCursor(isEnabled());
|
||||
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();
|
||||
setMarkedText(ParseMenuItem(actionText), actionShortcut);
|
||||
}
|
||||
|
||||
void Action::setMarkedText(
|
||||
TextWithEntities text,
|
||||
QString shortcut,
|
||||
const Text::MarkedContext &context) {
|
||||
_text.setMarkedText(_st.itemStyle, text, MenuTextOptions, context);
|
||||
const auto textWidth = _text.maxWidth();
|
||||
const auto &padding = _st.itemPadding;
|
||||
|
||||
const auto additionalWidth = hasSubmenu()
|
||||
? (_st.itemRightSkip + _st.arrow.width())
|
||||
: (!shortcut.isEmpty())
|
||||
? (_st.itemRightSkip + _st.itemStyle.font->width(shortcut))
|
||||
: 0;
|
||||
const auto goodWidth = padding.left()
|
||||
+ textWidth
|
||||
+ additionalWidth
|
||||
+ padding.right();
|
||||
|
||||
const auto w = std::clamp(goodWidth, _st.widthMin, _st.widthMax);
|
||||
_textWidth = w - (goodWidth - textWidth);
|
||||
_shortcut = shortcut;
|
||||
setMinWidth(w);
|
||||
update();
|
||||
}
|
||||
|
||||
const style::Menu &Action::st() const {
|
||||
return _st;
|
||||
}
|
||||
|
||||
bool Action::isEnabled() const {
|
||||
return _action->isEnabled();
|
||||
}
|
||||
|
||||
not_null<QAction*> Action::action() const {
|
||||
return _action;
|
||||
}
|
||||
|
||||
QPoint Action::prepareRippleStartPosition() const {
|
||||
return mapFromGlobal(QCursor::pos());
|
||||
}
|
||||
|
||||
QImage Action::prepareRippleMask() const {
|
||||
return Ui::RippleAnimation::RectMask(size());
|
||||
}
|
||||
|
||||
int Action::contentHeight() const {
|
||||
return _height;
|
||||
}
|
||||
|
||||
void Action::handleKeyPress(not_null<QKeyEvent*> e) {
|
||||
if (!isSelected()) {
|
||||
return;
|
||||
}
|
||||
const auto key = e->key();
|
||||
if (key == Qt::Key_Enter || key == Qt::Key_Return) {
|
||||
setClicked(TriggeredSource::Keyboard);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void Action::setIcon(
|
||||
const style::icon *icon,
|
||||
const style::icon *iconOver) {
|
||||
_icon = icon;
|
||||
_iconOver = iconOver ? iconOver : icon;
|
||||
update();
|
||||
}
|
||||
|
||||
} // namespace Ui::Menu
|
||||
77
Telegram/lib_ui/ui/widgets/menu/menu_action.h
Normal file
77
Telegram/lib_ui/ui/widgets/menu/menu_action.h
Normal file
@@ -0,0 +1,77 @@
|
||||
// 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/text/text.h"
|
||||
#include "ui/widgets/menu/menu_item_base.h"
|
||||
#include "styles/style_widgets.h"
|
||||
|
||||
class Painter;
|
||||
|
||||
namespace Ui::Menu {
|
||||
|
||||
class Action : public ItemBase {
|
||||
public:
|
||||
Action(
|
||||
not_null<RpWidget*> parent,
|
||||
const style::Menu &st,
|
||||
not_null<QAction*> action,
|
||||
const style::icon *icon,
|
||||
const style::icon *iconOver);
|
||||
|
||||
QAccessible::Role accessibilityRole() override {
|
||||
return QAccessible::MenuItem;
|
||||
}
|
||||
QString accessibilityName() override {
|
||||
return _action->text();
|
||||
}
|
||||
|
||||
[[nodiscard]] const style::Menu &st() const;
|
||||
|
||||
bool isEnabled() const override;
|
||||
not_null<QAction*> action() const override;
|
||||
|
||||
void handleKeyPress(not_null<QKeyEvent*> e) override;
|
||||
|
||||
void setIcon(
|
||||
const style::icon *icon,
|
||||
const style::icon *iconOver = nullptr);
|
||||
|
||||
void setMarkedText(
|
||||
TextWithEntities text,
|
||||
QString shortcut,
|
||||
const Text::MarkedContext &context = {});
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent *e) override;
|
||||
QPoint prepareRippleStartPosition() const override;
|
||||
QImage prepareRippleMask() const override;
|
||||
|
||||
int contentHeight() const override;
|
||||
|
||||
void paintBackground(QPainter &p, bool selected);
|
||||
void paintText(Painter &p);
|
||||
|
||||
private:
|
||||
void processAction();
|
||||
void paint(Painter &p);
|
||||
|
||||
bool hasSubmenu() const;
|
||||
|
||||
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<ToggleView> _toggle;
|
||||
int _textWidth = 0;
|
||||
const int _height;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Ui::Menu
|
||||
28
Telegram/lib_ui/ui/widgets/menu/menu_add_action_callback.cpp
Normal file
28
Telegram/lib_ui/ui/widgets/menu/menu_add_action_callback.cpp
Normal file
@@ -0,0 +1,28 @@
|
||||
// This file is part of Desktop App Toolkit,
|
||||
// a set of libraries for developing nice desktop applications.
|
||||
//
|
||||
// For license and copyright information please follow this link:
|
||||
// https://github.com/desktop-app/legal/blob/master/LEGAL
|
||||
//
|
||||
#include "ui/widgets/menu/menu_add_action_callback.h"
|
||||
|
||||
#include "ui/style/style_core.h"
|
||||
|
||||
namespace Ui::Menu {
|
||||
|
||||
MenuCallback::MenuCallback(MenuCallback::Callback callback)
|
||||
: _callback(std::move(callback)) {
|
||||
}
|
||||
|
||||
QAction *MenuCallback::operator()(Args &&args) const {
|
||||
return _callback(std::move(args));
|
||||
}
|
||||
|
||||
QAction *MenuCallback::operator()(
|
||||
const QString &text,
|
||||
Fn<void()> handler,
|
||||
const style::icon *icon) const {
|
||||
return _callback({ text, std::move(handler), icon, nullptr });
|
||||
}
|
||||
|
||||
} // namespace Ui::Menu
|
||||
63
Telegram/lib_ui/ui/widgets/menu/menu_add_action_callback.h
Normal file
63
Telegram/lib_ui/ui/widgets/menu/menu_add_action_callback.h
Normal file
@@ -0,0 +1,63 @@
|
||||
// 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/style/style_core.h"
|
||||
|
||||
namespace anim {
|
||||
enum class type : uchar;
|
||||
} // namespace anim
|
||||
|
||||
namespace base {
|
||||
template <typename T>
|
||||
class unique_qptr;
|
||||
} // namespace base
|
||||
|
||||
namespace style {
|
||||
struct PopupMenu;
|
||||
struct MenuSeparator;
|
||||
} // namespace style
|
||||
|
||||
namespace Ui {
|
||||
class RpWidget;
|
||||
class PopupMenu;
|
||||
} // namespace Ui
|
||||
|
||||
namespace Ui::Menu {
|
||||
|
||||
class ItemBase;
|
||||
|
||||
struct MenuCallback final {
|
||||
public:
|
||||
struct Args {
|
||||
QString text;
|
||||
Fn<void()> handler;
|
||||
const style::icon *icon;
|
||||
const style::MenuSeparator *separatorSt = nullptr;
|
||||
FnMut<void(not_null<Ui::PopupMenu*>)> fillSubmenu;
|
||||
FnMut<base::unique_qptr<ItemBase>(not_null<RpWidget*>)> make;
|
||||
const style::PopupMenu *submenuSt = nullptr;
|
||||
Fn<bool()> triggerFilter;
|
||||
rpl::producer<anim::type> hideRequests;
|
||||
int addTopShift = 0;
|
||||
bool isSeparator = false;
|
||||
bool isAttention = false;
|
||||
};
|
||||
using Callback = Fn<QAction*(Args&&)>;
|
||||
|
||||
explicit MenuCallback(Callback callback);
|
||||
|
||||
QAction *operator()(Args &&args) const;
|
||||
QAction *operator()(
|
||||
const QString &text,
|
||||
Fn<void()> handler,
|
||||
const style::icon *icon) const;
|
||||
private:
|
||||
Callback _callback;
|
||||
};
|
||||
|
||||
} // namespace Ui::Menu
|
||||
@@ -0,0 +1,162 @@
|
||||
// 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_add_action_callback_factory.h"
|
||||
|
||||
#include "ui/widgets/dropdown_menu.h"
|
||||
#include "ui/widgets/menu/menu_add_action_callback.h"
|
||||
#include "ui/widgets/menu/menu_action.h"
|
||||
#include "ui/widgets/popup_menu.h"
|
||||
#include "ui/qt_object_factory.h"
|
||||
#include "styles/style_widgets.h"
|
||||
|
||||
namespace Ui::Menu {
|
||||
|
||||
MenuCallback CreateAddActionCallback(not_null<Ui::PopupMenu*> menu) {
|
||||
return MenuCallback([=](MenuCallback::Args a) -> QAction* {
|
||||
const auto initFilter = [&](not_null<Ui::Menu::ItemBase*> action) {
|
||||
if (const auto copy = a.triggerFilter) {
|
||||
action->setClickedCallback([=] {
|
||||
const auto weak = base::make_weak(action);
|
||||
if (copy() && weak && !action->isDisabled()) {
|
||||
action->setDisabled(true);
|
||||
crl::on_main(
|
||||
weak,
|
||||
[=] { action->setDisabled(false); });
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
if (a.hideRequests) {
|
||||
std::move(
|
||||
a.hideRequests
|
||||
) | rpl::on_next([=](anim::type animated) {
|
||||
menu->hideMenu(animated == anim::type::instant);
|
||||
}, menu->lifetime());
|
||||
}
|
||||
if (a.addTopShift) {
|
||||
menu->setTopShift(a.addTopShift);
|
||||
return nullptr;
|
||||
} else if (a.fillSubmenu) {
|
||||
const auto action = menu->addAction(
|
||||
a.text,
|
||||
std::move(a.handler),
|
||||
a.icon);
|
||||
// Dummy menu.
|
||||
action->setMenu(Ui::CreateChild<QMenu>(menu->menu().get()));
|
||||
a.fillSubmenu(menu->ensureSubmenu(
|
||||
action,
|
||||
a.submenuSt ? *a.submenuSt : menu->st()));
|
||||
return action;
|
||||
} else if (a.separatorSt || a.isSeparator) {
|
||||
return menu->addSeparator(a.separatorSt);
|
||||
} else if (a.isAttention) {
|
||||
auto owned = base::make_unique_q<Ui::Menu::Action>(
|
||||
menu,
|
||||
a.icon ? st::menuWithIconsAttention : st::menuAttention,
|
||||
Ui::Menu::CreateAction(
|
||||
menu->menu().get(),
|
||||
a.text,
|
||||
std::move(a.handler)),
|
||||
a.icon,
|
||||
a.icon);
|
||||
initFilter(owned.get());
|
||||
return menu->addAction(std::move(owned));
|
||||
} else if (a.triggerFilter) {
|
||||
auto owned = base::make_unique_q<Ui::Menu::Action>(
|
||||
menu,
|
||||
menu->st().menu,
|
||||
Ui::Menu::CreateAction(
|
||||
menu->menu().get(),
|
||||
a.text,
|
||||
std::move(a.handler)),
|
||||
a.icon,
|
||||
a.icon);
|
||||
initFilter(owned.get());
|
||||
return menu->addAction(std::move(owned));
|
||||
} else if (auto owned = a.make ? a.make(menu) : nullptr) {
|
||||
initFilter(owned.get());
|
||||
return menu->addAction(std::move(owned));
|
||||
}
|
||||
return menu->addAction(a.text, std::move(a.handler), a.icon);
|
||||
});
|
||||
}
|
||||
|
||||
MenuCallback CreateAddActionCallback(not_null<Ui::DropdownMenu*> menu) {
|
||||
return MenuCallback([=](MenuCallback::Args a) -> QAction* {
|
||||
const auto initFilter = [&](not_null<Ui::Menu::Action*> action) {
|
||||
if (const auto copy = a.triggerFilter) {
|
||||
action->setClickedCallback([=] {
|
||||
const auto weak = base::make_weak(action);
|
||||
if (copy() && weak && !action->isDisabled()) {
|
||||
action->setDisabled(true);
|
||||
crl::on_main(
|
||||
weak,
|
||||
[=] { action->setDisabled(false); });
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
if (a.hideRequests) {
|
||||
Unexpected("Dropdown menu does not support hideRequests.");
|
||||
// std::move(
|
||||
// a.hideRequests
|
||||
// ) | rpl::on_next([=](anim::type animated) {
|
||||
// menu->hideMenu(animated == anim::type::instant);
|
||||
// }, menu->lifetime());
|
||||
}
|
||||
if (a.addTopShift) {
|
||||
Unexpected("Dropdown menu does not support addTopShift.");
|
||||
// menu->setTopShift(a.addTopShift);
|
||||
// return nullptr;
|
||||
} else if (a.fillSubmenu) {
|
||||
Unexpected("Dropdown menu does not support fillSubmenu.");
|
||||
// const auto action = menu->addAction(
|
||||
// a.text,
|
||||
// std::move(a.handler),
|
||||
// a.icon);
|
||||
// // Dummy menu.
|
||||
// action->setMenu(Ui::CreateChild<QMenu>(menu->menu().get()));
|
||||
// a.fillSubmenu(menu->ensureSubmenu(action, menu->st()));
|
||||
// return action;
|
||||
} else if (a.separatorSt || a.isSeparator) {
|
||||
return menu->addSeparator(a.separatorSt);
|
||||
} else if (a.isAttention) {
|
||||
auto owned = base::make_unique_q<Ui::Menu::Action>(
|
||||
menu,
|
||||
a.icon ? st::menuWithIconsAttention : st::menuAttention,
|
||||
Ui::Menu::CreateAction(
|
||||
menu->menu().get(),
|
||||
a.text,
|
||||
std::move(a.handler)),
|
||||
a.icon,
|
||||
a.icon);
|
||||
initFilter(owned.get());
|
||||
return menu->addAction(std::move(owned));
|
||||
} else if (a.triggerFilter) {
|
||||
Unexpected("Dropdown menu does not support triggerFilter.");
|
||||
// auto owned = base::make_unique_q<Ui::Menu::Action>(
|
||||
// menu,
|
||||
// menu->st().menu,
|
||||
// Ui::Menu::CreateAction(
|
||||
// menu->menu().get(),
|
||||
// a.text,
|
||||
// std::move(a.handler)),
|
||||
// a.icon,
|
||||
// a.icon);
|
||||
// initFilter(owned.get());
|
||||
// return menu->addAction(std::move(owned));
|
||||
}
|
||||
return menu->addAction(a.text, std::move(a.handler), a.icon);
|
||||
});
|
||||
}
|
||||
|
||||
MenuCallback CreateAddActionCallback(
|
||||
const base::unique_qptr<Ui::PopupMenu> &menu) {
|
||||
return CreateAddActionCallback(menu.get());
|
||||
}
|
||||
|
||||
} // namespace Ui::Menu
|
||||
@@ -0,0 +1,27 @@
|
||||
// This file is part of Desktop App Toolkit,
|
||||
// a set of libraries for developing nice desktop applications.
|
||||
//
|
||||
// For license and copyright information please follow this link:
|
||||
// https://github.com/desktop-app/legal/blob/master/LEGAL
|
||||
//
|
||||
#pragma once
|
||||
|
||||
#include "base/unique_qptr.h"
|
||||
|
||||
namespace Ui {
|
||||
class DropdownMenu;
|
||||
class PopupMenu;
|
||||
} // namespace Ui
|
||||
|
||||
namespace Ui::Menu {
|
||||
|
||||
struct MenuCallback;
|
||||
|
||||
[[nodiscard]] MenuCallback CreateAddActionCallback(
|
||||
not_null<Ui::PopupMenu*> menu);
|
||||
[[nodiscard]] MenuCallback CreateAddActionCallback(
|
||||
not_null<Ui::DropdownMenu*> menu);
|
||||
[[nodiscard]] MenuCallback CreateAddActionCallback(
|
||||
const base::unique_qptr<Ui::PopupMenu> &menu);
|
||||
|
||||
} // namespace Ui::Menu
|
||||
27
Telegram/lib_ui/ui/widgets/menu/menu_common.cpp
Normal file
27
Telegram/lib_ui/ui/widgets/menu/menu_common.cpp
Normal file
@@ -0,0 +1,27 @@
|
||||
// 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_common.h"
|
||||
|
||||
#include <QAction>
|
||||
|
||||
namespace Ui::Menu {
|
||||
|
||||
not_null<QAction*> CreateAction(
|
||||
QWidget *parent,
|
||||
const QString &text,
|
||||
Fn<void()> &&callback) {
|
||||
const auto action = new QAction(text, parent);
|
||||
parent->connect(
|
||||
action,
|
||||
&QAction::triggered,
|
||||
action,
|
||||
std::move(callback),
|
||||
Qt::QueuedConnection);
|
||||
return action;
|
||||
}
|
||||
|
||||
} // namespace Ui::Menu
|
||||
29
Telegram/lib_ui/ui/widgets/menu/menu_common.h
Normal file
29
Telegram/lib_ui/ui/widgets/menu/menu_common.h
Normal file
@@ -0,0 +1,29 @@
|
||||
// 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::Menu {
|
||||
|
||||
enum class TriggeredSource {
|
||||
Mouse,
|
||||
Keyboard,
|
||||
};
|
||||
|
||||
struct CallbackData {
|
||||
QAction *action;
|
||||
int actionTop = 0;
|
||||
TriggeredSource source;
|
||||
int index = 0;
|
||||
bool selected = false;
|
||||
};
|
||||
|
||||
not_null<QAction*> CreateAction(
|
||||
QWidget *parent,
|
||||
const QString &text,
|
||||
Fn<void()> &&callback);
|
||||
|
||||
} // namespace Ui::Menu
|
||||
168
Telegram/lib_ui/ui/widgets/menu/menu_item_base.cpp
Normal file
168
Telegram/lib_ui/ui/widgets/menu/menu_item_base.cpp
Normal file
@@ -0,0 +1,168 @@
|
||||
// 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"
|
||||
|
||||
#include "ui/widgets/menu/menu.h"
|
||||
|
||||
namespace Ui::Menu {
|
||||
|
||||
ItemBase::ItemBase(
|
||||
not_null<RpWidget*> parent,
|
||||
const style::Menu &st)
|
||||
: RippleButton(parent, st.ripple) {
|
||||
}
|
||||
|
||||
void ItemBase::setMenuAsParent(not_null<Menu*> menu) {
|
||||
QWidget::setParent(menu);
|
||||
_menu = menu;
|
||||
}
|
||||
|
||||
void ItemBase::setSelected(
|
||||
bool selected,
|
||||
TriggeredSource source) {
|
||||
if (selected && !isEnabled()) {
|
||||
return;
|
||||
}
|
||||
if (_selected.current() != selected) {
|
||||
setMouseTracking(!selected);
|
||||
_lastTriggeredSource = source;
|
||||
_selected = selected;
|
||||
update();
|
||||
}
|
||||
}
|
||||
|
||||
bool ItemBase::isSelected() const {
|
||||
return _selected.current();
|
||||
}
|
||||
|
||||
rpl::producer<CallbackData> ItemBase::selects() const {
|
||||
return _selected.changes(
|
||||
) | rpl::map([=](bool selected) -> CallbackData {
|
||||
return { action(), y(), _lastTriggeredSource, _index, selected };
|
||||
});
|
||||
}
|
||||
|
||||
TriggeredSource ItemBase::lastTriggeredSource() const {
|
||||
return _lastTriggeredSource;
|
||||
}
|
||||
|
||||
int ItemBase::index() const {
|
||||
return _index;
|
||||
}
|
||||
|
||||
void ItemBase::setIndex(int index) {
|
||||
_index = index;
|
||||
}
|
||||
|
||||
void ItemBase::setClicked(TriggeredSource source) {
|
||||
if (isEnabled()) {
|
||||
_lastTriggeredSource = source;
|
||||
_clicks.fire({});
|
||||
}
|
||||
}
|
||||
|
||||
rpl::producer<CallbackData> ItemBase::clicks() const {
|
||||
return rpl::merge(
|
||||
AbstractButton::clicks() | rpl::to_empty,
|
||||
_clicks.events()
|
||||
) | rpl::filter([=] {
|
||||
return isEnabled() && !AbstractButton::isDisabled();
|
||||
}) | rpl::map([=]() -> CallbackData {
|
||||
return { action(), y(), _lastTriggeredSource, _index, true };
|
||||
});
|
||||
}
|
||||
|
||||
rpl::producer<int> ItemBase::minWidthValue() const {
|
||||
return _minWidth.value();
|
||||
}
|
||||
|
||||
int ItemBase::minWidth() const {
|
||||
return _minWidth.current();
|
||||
}
|
||||
|
||||
void ItemBase::initResizeHook(rpl::producer<QSize> &&size) {
|
||||
std::move(
|
||||
size
|
||||
) | rpl::on_next([=](QSize s) {
|
||||
resize(s.width(), contentHeight());
|
||||
}, lifetime());
|
||||
}
|
||||
|
||||
void ItemBase::setMinWidth(int w) {
|
||||
_minWidth = w;
|
||||
}
|
||||
|
||||
void ItemBase::finishAnimating() {
|
||||
RippleButton::finishAnimating();
|
||||
}
|
||||
|
||||
void ItemBase::enableMouseSelecting() {
|
||||
enableMouseSelecting(this);
|
||||
}
|
||||
|
||||
void ItemBase::enableMouseSelecting(not_null<RpWidget*> widget) {
|
||||
widget->events(
|
||||
) | rpl::on_next([=](not_null<QEvent*> e) {
|
||||
const auto type = e->type();
|
||||
if (((type == QEvent::Leave)
|
||||
|| (type == QEvent::Enter)
|
||||
|| (type == QEvent::MouseMove)) && action()->isEnabled()) {
|
||||
setSelected(e->type() != QEvent::Leave);
|
||||
} else if ((type == QEvent::MouseButtonRelease)
|
||||
&& isEnabled()
|
||||
&& isSelected()) {
|
||||
const auto point = mapFromGlobal(QCursor::pos());
|
||||
if (!rect().contains(point)) {
|
||||
setSelected(false);
|
||||
}
|
||||
}
|
||||
}, lifetime());
|
||||
}
|
||||
|
||||
void ItemBase::setClickedCallback(Fn<void()> callback) {
|
||||
Ui::AbstractButton::setClickedCallback(callback);
|
||||
_connection = QObject::connect(
|
||||
action(),
|
||||
&QAction::triggered,
|
||||
std::move(callback));
|
||||
}
|
||||
|
||||
void ItemBase::mousePressEvent(QMouseEvent *e) {
|
||||
if (e->button() == Qt::LeftButton) {
|
||||
_mousePressed = true;
|
||||
}
|
||||
RippleButton::mousePressEvent(e);
|
||||
}
|
||||
|
||||
void ItemBase::mouseMoveEvent(QMouseEvent *e) {
|
||||
if (_mousePressed && _menu && !rect().contains(e->pos())) {
|
||||
_menu->handlePressedOutside(e->globalPos());
|
||||
}
|
||||
RippleButton::mouseMoveEvent(e);
|
||||
}
|
||||
|
||||
void ItemBase::mouseReleaseEvent(QMouseEvent *e) {
|
||||
const auto wasPressed = base::take(_mousePressed);
|
||||
#ifdef Q_OS_UNIX
|
||||
if (isEnabled() && e->button() == Qt::RightButton) {
|
||||
setClicked(TriggeredSource::Mouse);
|
||||
return;
|
||||
}
|
||||
#endif // Q_OS_UNIX
|
||||
const auto isInRect = rect().contains(e->pos());
|
||||
if (isInRect && isEnabled() && e->button() == Qt::LeftButton) {
|
||||
//
|
||||
setClicked(TriggeredSource::Mouse);
|
||||
return;
|
||||
}
|
||||
if (wasPressed && _menu && !isInRect) {
|
||||
_menu->handleMouseRelease(e->globalPos());
|
||||
}
|
||||
RippleButton::mouseReleaseEvent(e);
|
||||
}
|
||||
|
||||
} // namespace Ui::Menu
|
||||
83
Telegram/lib_ui/ui/widgets/menu/menu_item_base.h
Normal file
83
Telegram/lib_ui/ui/widgets/menu/menu_item_base.h
Normal file
@@ -0,0 +1,83 @@
|
||||
// This file is part of Desktop App Toolkit,
|
||||
// a set of libraries for developing nice desktop applications.
|
||||
//
|
||||
// For license and copyright information please follow this link:
|
||||
// https://github.com/desktop-app/legal/blob/master/LEGAL
|
||||
//
|
||||
#pragma once
|
||||
|
||||
#include "base/qt_connection.h"
|
||||
#include "ui/widgets/buttons.h"
|
||||
#include "ui/widgets/menu/menu.h"
|
||||
#include "ui/widgets/menu/menu_common.h"
|
||||
#include "styles/style_widgets.h"
|
||||
|
||||
namespace Ui::Menu {
|
||||
|
||||
class Menu;
|
||||
|
||||
class ItemBase : public RippleButton {
|
||||
public:
|
||||
ItemBase(not_null<RpWidget*> parent, const style::Menu &st);
|
||||
|
||||
TriggeredSource lastTriggeredSource() const;
|
||||
|
||||
rpl::producer<CallbackData> selects() const;
|
||||
void setSelected(
|
||||
bool selected,
|
||||
TriggeredSource source = TriggeredSource::Mouse);
|
||||
bool isSelected() const;
|
||||
|
||||
int index() const;
|
||||
void setIndex(int index);
|
||||
|
||||
void setClicked(TriggeredSource source = TriggeredSource::Mouse);
|
||||
|
||||
rpl::producer<CallbackData> clicks() const;
|
||||
|
||||
void setClickedCallback(Fn<void()> callback);
|
||||
|
||||
rpl::producer<int> minWidthValue() const;
|
||||
int minWidth() const;
|
||||
void setMinWidth(int w);
|
||||
|
||||
virtual void handleKeyPress(not_null<QKeyEvent*> e) {
|
||||
}
|
||||
|
||||
void setMenuAsParent(not_null<Menu*> menu);
|
||||
|
||||
virtual not_null<QAction*> action() const = 0;
|
||||
virtual bool isEnabled() const = 0;
|
||||
|
||||
virtual void finishAnimating();
|
||||
|
||||
protected:
|
||||
void initResizeHook(rpl::producer<QSize> &&size);
|
||||
|
||||
void enableMouseSelecting();
|
||||
void enableMouseSelecting(not_null<RpWidget*> widget);
|
||||
|
||||
virtual int contentHeight() const = 0;
|
||||
|
||||
void mousePressEvent(QMouseEvent *e) override;
|
||||
void mouseMoveEvent(QMouseEvent *e) override;
|
||||
void mouseReleaseEvent(QMouseEvent *e) override;
|
||||
|
||||
private:
|
||||
bool _mousePressed = false;
|
||||
int _index = -1;
|
||||
|
||||
rpl::variable<bool> _selected = false;
|
||||
rpl::event_stream<> _clicks;
|
||||
|
||||
rpl::variable<int> _minWidth = 0;
|
||||
|
||||
TriggeredSource _lastTriggeredSource = TriggeredSource::Mouse;
|
||||
|
||||
base::qt_connection _connection;
|
||||
|
||||
Menu *_menu = nullptr;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Ui::Menu
|
||||
95
Telegram/lib_ui/ui/widgets/menu/menu_multiline_action.cpp
Normal file
95
Telegram/lib_ui/ui/widgets/menu/menu_multiline_action.cpp
Normal file
@@ -0,0 +1,95 @@
|
||||
// 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_multiline_action.h"
|
||||
|
||||
#include "ui/widgets/labels.h"
|
||||
#include "ui/qt_object_factory.h"
|
||||
|
||||
namespace Ui::Menu {
|
||||
|
||||
MultilineAction::MultilineAction(
|
||||
not_null<Ui::RpWidget*> parent,
|
||||
const style::Menu &st,
|
||||
const style::FlatLabel &stLabel,
|
||||
QPoint labelPosition,
|
||||
TextWithEntities &&about,
|
||||
const style::icon *icon,
|
||||
const style::icon *iconOver)
|
||||
: ItemBase(parent, st)
|
||||
, _st(st)
|
||||
, _icon(icon)
|
||||
, _iconOver(iconOver ? iconOver : icon)
|
||||
, _labelPosition(labelPosition)
|
||||
, _text(base::make_unique_q<Ui::FlatLabel>(
|
||||
this,
|
||||
rpl::single(std::move(about)),
|
||||
stLabel))
|
||||
, _dummyAction(Ui::CreateChild<QAction>(parent.get())) {
|
||||
ItemBase::enableMouseSelecting();
|
||||
_text->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||
updateMinWidth();
|
||||
parent->widthValue() | rpl::on_next([=](int width) {
|
||||
const auto top = _labelPosition.y();
|
||||
const auto skip = _labelPosition.x();
|
||||
const auto rightSkip = _icon ? _st.itemIconPosition.x() : skip;
|
||||
_text->resizeToWidth(width - skip - rightSkip);
|
||||
_text->moveToLeft(skip, top);
|
||||
resize(width, contentHeight());
|
||||
}, lifetime());
|
||||
}
|
||||
|
||||
not_null<QAction*> MultilineAction::action() const {
|
||||
return _dummyAction;
|
||||
}
|
||||
|
||||
bool MultilineAction::isEnabled() const {
|
||||
return true;
|
||||
}
|
||||
|
||||
int MultilineAction::contentHeight() const {
|
||||
const auto skip = _labelPosition.y();
|
||||
return skip
|
||||
+ std::max(_text->height(), _icon ? _icon->height() : 0)
|
||||
+ skip;
|
||||
}
|
||||
|
||||
void MultilineAction::paintEvent(QPaintEvent *e) {
|
||||
auto p = QPainter(this);
|
||||
const auto selected = isSelected();
|
||||
p.fillRect(rect(), selected ? _st.itemBgOver : _st.itemBg);
|
||||
RippleButton::paintRipple(p, 0, 0);
|
||||
if (const auto icon = (selected ? _iconOver : _icon)) {
|
||||
icon->paint(p, _st.itemIconPosition, width());
|
||||
}
|
||||
}
|
||||
|
||||
void MultilineAction::updateMinWidth() {
|
||||
const auto skip = _labelPosition.x();
|
||||
const auto rightSkip = _icon ? _st.itemIconPosition.x() : skip;
|
||||
auto min = _text->textMaxWidth() / 4;
|
||||
auto max = _icon ? _st.widthMax : (_text->textMaxWidth() - skip);
|
||||
_text->resizeToWidth(max);
|
||||
const auto height = _icon
|
||||
? ((_st.itemIconPosition.y() * 2) + _icon->height())
|
||||
: _text->height();
|
||||
_text->resizeToWidth(min);
|
||||
const auto heightMax = _text->height();
|
||||
if (heightMax > height) {
|
||||
while (min + 1 < max) {
|
||||
const auto middle = (max + min) / 2;
|
||||
_text->resizeToWidth(middle);
|
||||
if (_text->height() > height) {
|
||||
min = middle;
|
||||
} else {
|
||||
max = middle;
|
||||
}
|
||||
}
|
||||
}
|
||||
ItemBase::setMinWidth(skip + rightSkip + max);
|
||||
}
|
||||
|
||||
} // namespace Ui::Menu
|
||||
51
Telegram/lib_ui/ui/widgets/menu/menu_multiline_action.h
Normal file
51
Telegram/lib_ui/ui/widgets/menu/menu_multiline_action.h
Normal file
@@ -0,0 +1,51 @@
|
||||
// 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/menu/menu_item_base.h"
|
||||
|
||||
namespace style {
|
||||
struct FlatLabel;
|
||||
struct Menu;
|
||||
} // namespace style
|
||||
|
||||
namespace Ui {
|
||||
class FlatLabel;
|
||||
class RpWidget;
|
||||
} // namespace Ui
|
||||
|
||||
namespace Ui::Menu {
|
||||
|
||||
class MultilineAction final : public ItemBase {
|
||||
public:
|
||||
MultilineAction(
|
||||
not_null<Ui::RpWidget*> parent,
|
||||
const style::Menu &st,
|
||||
const style::FlatLabel &stLabel,
|
||||
QPoint labelPosition,
|
||||
TextWithEntities &&about,
|
||||
const style::icon *icon = nullptr,
|
||||
const style::icon *iconOver = nullptr);
|
||||
|
||||
not_null<QAction*> action() const override;
|
||||
bool isEnabled() const override;
|
||||
|
||||
private:
|
||||
int contentHeight() const override;
|
||||
void paintEvent(QPaintEvent *e) override;
|
||||
void updateMinWidth();
|
||||
|
||||
const style::Menu &_st;
|
||||
const style::icon *_icon;
|
||||
const style::icon *_iconOver;
|
||||
const QPoint _labelPosition;
|
||||
const base::unique_qptr<Ui::FlatLabel> _text;
|
||||
const not_null<QAction*> _dummyAction;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Ui::Menu
|
||||
53
Telegram/lib_ui/ui/widgets/menu/menu_separator.cpp
Normal file
53
Telegram/lib_ui/ui/widgets/menu/menu_separator.cpp
Normal file
@@ -0,0 +1,53 @@
|
||||
// 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_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(separator.width)
|
||||
, _padding(separator.padding)
|
||||
, _fg(separator.fg)
|
||||
, _bg(st.itemBg)
|
||||
, _height(_padding.top() + _lineWidth + _padding.bottom())
|
||||
, _action(action) {
|
||||
initResizeHook(parent->sizeValue());
|
||||
paintRequest(
|
||||
) | rpl::on_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());
|
||||
}
|
||||
|
||||
not_null<QAction*> Separator::action() const {
|
||||
return _action;
|
||||
}
|
||||
|
||||
bool Separator::isEnabled() const {
|
||||
return false;
|
||||
}
|
||||
|
||||
int Separator::contentHeight() const {
|
||||
return _height;
|
||||
}
|
||||
|
||||
} // namespace Ui::Menu
|
||||
48
Telegram/lib_ui/ui/widgets/menu/menu_separator.h
Normal file
48
Telegram/lib_ui/ui/widgets/menu/menu_separator.h
Normal file
@@ -0,0 +1,48 @@
|
||||
// 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/menu/menu_item_base.h"
|
||||
|
||||
class Painter;
|
||||
|
||||
namespace style {
|
||||
struct Menu;
|
||||
struct MenuSeparator;
|
||||
} // namespace style
|
||||
|
||||
namespace Ui::Menu {
|
||||
|
||||
class Separator : public ItemBase {
|
||||
public:
|
||||
Separator(
|
||||
not_null<RpWidget*> parent,
|
||||
const style::Menu &st,
|
||||
const style::MenuSeparator &separator,
|
||||
not_null<QAction*> action);
|
||||
|
||||
QAccessible::Role accessibilityRole() override {
|
||||
return QAccessible::Role::Separator;
|
||||
}
|
||||
|
||||
not_null<QAction*> action() const override;
|
||||
bool isEnabled() const override;
|
||||
|
||||
protected:
|
||||
int contentHeight() const override;
|
||||
|
||||
private:
|
||||
const int _lineWidth;
|
||||
const style::margins &_padding;
|
||||
const style::color &_fg;
|
||||
const style::color &_bg;
|
||||
const int _height;
|
||||
const not_null<QAction*> _action;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Ui::Menu
|
||||
77
Telegram/lib_ui/ui/widgets/menu/menu_toggle.cpp
Normal file
77
Telegram/lib_ui/ui/widgets/menu/menu_toggle.cpp
Normal file
@@ -0,0 +1,77 @@
|
||||
// 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_toggle.h"
|
||||
|
||||
#include "ui/widgets/checkbox.h"
|
||||
|
||||
namespace Ui::Menu {
|
||||
|
||||
Toggle::Toggle(
|
||||
not_null<RpWidget*> parent,
|
||||
const style::Menu &st,
|
||||
const QString &text,
|
||||
Fn<void()> &&callback,
|
||||
const style::icon *icon,
|
||||
const style::icon *iconOver)
|
||||
: Action(
|
||||
parent,
|
||||
st,
|
||||
CreateAction(parent, text, std::move(callback)),
|
||||
icon,
|
||||
iconOver)
|
||||
, _padding(st.itemPadding)
|
||||
, _toggleShift(st.itemToggleShift)
|
||||
, _itemToggle(st.itemToggle)
|
||||
, _itemToggleOver(st.itemToggleOver) {
|
||||
const auto processAction = [=] {
|
||||
if (!action()->isCheckable()) {
|
||||
_toggle.reset();
|
||||
return;
|
||||
}
|
||||
if (_toggle) {
|
||||
_toggle->setChecked(action()->isChecked(), anim::type::normal);
|
||||
} else {
|
||||
_toggle = std::make_unique<ToggleView>(
|
||||
st.itemToggle,
|
||||
action()->isChecked(),
|
||||
[=] { update(); });
|
||||
}
|
||||
};
|
||||
processAction();
|
||||
connect(action(), &QAction::changed, [=] { processAction(); });
|
||||
|
||||
selects(
|
||||
) | rpl::on_next([=](const CallbackData &data) {
|
||||
if (!_toggle) {
|
||||
return;
|
||||
}
|
||||
_toggle->setStyle(data.selected ? _itemToggleOver : _itemToggle);
|
||||
}, lifetime());
|
||||
}
|
||||
|
||||
Toggle::~Toggle() = default;
|
||||
|
||||
void Toggle::paintEvent(QPaintEvent *e) {
|
||||
Action::paintEvent(e);
|
||||
if (_toggle) {
|
||||
auto p = QPainter(this);
|
||||
const auto toggleSize = _toggle->getSize();
|
||||
_toggle->paint(
|
||||
p,
|
||||
width() - _padding.right() - toggleSize.width() + _toggleShift,
|
||||
(contentHeight() - toggleSize.height()) / 2, width());
|
||||
}
|
||||
}
|
||||
|
||||
void Toggle::finishAnimating() {
|
||||
ItemBase::finishAnimating();
|
||||
if (_toggle) {
|
||||
_toggle->finishAnimating();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Ui::Menu
|
||||
43
Telegram/lib_ui/ui/widgets/menu/menu_toggle.h
Normal file
43
Telegram/lib_ui/ui/widgets/menu/menu_toggle.h
Normal file
@@ -0,0 +1,43 @@
|
||||
// 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/menu/menu_action.h"
|
||||
#include "styles/style_widgets.h"
|
||||
|
||||
namespace Ui {
|
||||
class ToggleView;
|
||||
} // namespace Ui
|
||||
|
||||
namespace Ui::Menu {
|
||||
|
||||
class Toggle : public Action {
|
||||
public:
|
||||
Toggle(
|
||||
not_null<RpWidget*> parent,
|
||||
const style::Menu &st,
|
||||
const QString &text,
|
||||
Fn<void()> &&callback,
|
||||
const style::icon *icon,
|
||||
const style::icon *iconOver);
|
||||
~Toggle();
|
||||
|
||||
void finishAnimating() override;
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent *e) override;
|
||||
|
||||
private:
|
||||
const style::margins &_padding;
|
||||
const int _toggleShift;
|
||||
const style::Toggle &_itemToggle;
|
||||
const style::Toggle &_itemToggleOver;
|
||||
std::unique_ptr<ToggleView> _toggle;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Ui::Menu
|
||||
Reference in New Issue
Block a user