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

This commit is contained in:
allhaileris
2026-02-16 15:50:16 +03:00
commit afb81b8278
13816 changed files with 3689732 additions and 0 deletions

View 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

View 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

View 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

View 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

View 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

View 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

View File

@@ -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

View 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
//
#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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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