From f3e5220dfb18af034d55a2a5971854de2767b722 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Wed, 13 Jan 2021 01:47:06 +0300 Subject: [PATCH] Moved menu action item to separate file. --- CMakeLists.txt | 2 + ui/widgets/menu.cpp | 212 +------------------------------- ui/widgets/menu.h | 2 - ui/widgets/menu/menu_action.cpp | 205 ++++++++++++++++++++++++++++++ ui/widgets/menu/menu_action.h | 53 ++++++++ 5 files changed, 262 insertions(+), 212 deletions(-) create mode 100644 ui/widgets/menu/menu_action.cpp create mode 100644 ui/widgets/menu/menu_action.h diff --git a/CMakeLists.txt b/CMakeLists.txt index e319652..435774b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -148,6 +148,8 @@ PRIVATE ui/widgets/labels.h ui/widgets/menu.cpp ui/widgets/menu.h + ui/widgets/menu/menu_action.cpp + ui/widgets/menu/menu_action.h ui/widgets/menu/menu_common.h ui/widgets/menu/menu_item_base.cpp ui/widgets/menu/menu_item_base.h diff --git a/ui/widgets/menu.cpp b/ui/widgets/menu.cpp index 2cb18eb..591e74e 100644 --- a/ui/widgets/menu.cpp +++ b/ui/widgets/menu.cpp @@ -6,223 +6,15 @@ // #include "ui/widgets/menu.h" -#include "ui/effects/ripple_animation.h" #include "ui/widgets/buttons.h" #include "ui/widgets/checkbox.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/text/text.h" #include namespace Ui { -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, - result.text.size(), - 1 }); - result.text.append(ch); - } - } else if (ch == '&') { - afterAmpersand = true; - } else { - result.text.append(ch); - } - } - return result; -} - -TextParseOptions MenuTextOptions = { - TextParseLinks | TextParseRichText, // flags - 0, // maxw - 0, // maxh - Qt::LayoutDirectionAuto, // dir -}; - -} // namespace - -class Menu::Action : public ItemBase { -public: - Action( - not_null parent, - const style::Menu &st, - int index, - not_null action, - const style::icon *icon, - const style::icon *iconOver, - bool hasSubmenu) - : ItemBase(parent, st, index) - , _action(action) - , _st(st) - , _icon(icon) - , _iconOver(iconOver) - , _height(_st.itemPadding.top() - + _st.itemStyle.font->height - + _st.itemPadding.bottom()) { - - initResizeHook(parent->sizeValue()); - processAction(); - setHasSubmenu(hasSubmenu); - - paintRequest( - ) | rpl::start_with_next([=] { - Painter p(this); - paint(p); - }, lifetime()); - - events( - ) | rpl::filter([=](not_null e) { - return _action->isEnabled() - && ((e->type() == QEvent::Leave) - || (e->type() == QEvent::Enter)); - }) | rpl::map([=](not_null e) { - return (e->type() == QEvent::Enter); - }) | rpl::start_with_next([=](bool selected) { - setSelected(selected); - }, lifetime()); - - events( - ) | rpl::filter([=](not_null e) { - return _action->isEnabled() && (e->type() == QEvent::MouseMove); - }) | rpl::start_with_next([=](not_null e) { - setSelected(true); - }, lifetime()); - - connect(_action, &QAction::changed, [=] { processAction(); }); - } - -void paint(Painter &p) { - - const auto enabled = _action->isEnabled(); - const auto selected = isSelected(); - if (selected && _st.itemBgOver->c.alpha() < 255) { - p.fillRect(0, 0, width(), _height, _st.itemBg); - } - p.fillRect(0, 0, width(), _height, selected ? _st.itemBgOver : _st.itemBg); - if (isEnabled()) { - paintRipple(p, 0, 0); - } - if (const auto icon = (selected ? _iconOver : _icon)) { - icon->paint(p, _st.itemIconPosition, width()); - } - p.setPen(selected ? _st.itemFgOver : (enabled ? _st.itemFg : _st.itemFgDisabled)); - _text.drawLeftElided(p, _st.itemPadding.left(), _st.itemPadding.top(), _textWidth, width()); - if (hasSubmenu()) { - const auto left = width() - _st.itemPadding.right() - _st.arrow.width(); - const auto top = (_height - _st.arrow.height()) / 2; - if (enabled) { - _st.arrow.paint(p, left, top, width()); - } else { - _st.arrow.paint( - p, - left, - top, - width(), - _st.itemFgDisabled->c); - } - } else if (!_shortcut.isEmpty()) { - p.setPen(selected ? _st.itemFgShortcutOver : (enabled ? _st.itemFgShortcut : _st.itemFgShortcutDisabled)); - p.drawTextRight(_st.itemPadding.right(), _st.itemPadding.top(), width(), _shortcut); - } -} - -void processAction() { - if (_action->text().isEmpty()) { - _shortcut = QString(); - _text.clear(); - return; - } - const auto actionTextParts = _action->text().split('\t'); - const auto actionText = actionTextParts.empty() - ? QString() - : actionTextParts[0]; - const auto actionShortcut = (actionTextParts.size() > 1) - ? actionTextParts[1] - : QString(); - _text.setMarkedText( - _st.itemStyle, - ParseMenuItem(actionText), - MenuTextOptions); - const auto textWidth = _text.maxWidth(); - const auto &padding = _st.itemPadding; - - const auto additionalWidth = hasSubmenu() - ? padding.right() + _st.arrow.width() - : (!actionShortcut.isEmpty()) - ? (padding.right() + _st.itemStyle.font->width(actionShortcut)) - : 0; - const auto goodWidth = padding.left() - + textWidth - + padding.right() - + additionalWidth; - // if (action->isCheckable()) { - // auto updateCallback = [this, index] { updateItem(index); }; - // if (_toggle) { - // _toggle->setUpdateCallback(updateCallback); - // _toggle->setChecked(action->isChecked(), anim::type::normal); - // } else { - // _toggle = std::make_unique(_st.itemToggle, action->isChecked(), updateCallback); - // } - // goodWidth += _st.itemPadding.right() + _toggle->getSize().width() - _st.itemToggleShift; - // } else { - // _toggle.reset(); - // } - const auto w = std::clamp(goodWidth, _st.widthMin, _st.widthMax); - _textWidth = w - (goodWidth - textWidth); - _shortcut = actionShortcut; - setContentWidth(w); - update(); -} - -bool isEnabled() const override { - return _action->isEnabled(); -} - -QAction *action() const override { - return _action; -} - -protected: -QPoint prepareRippleStartPosition() const override { - return mapFromGlobal(QCursor::pos()); -} - -QImage prepareRippleMask() const override { - return RippleAnimation::rectMask(size()); -} - -protected: - int contentHeight() const override { - return _height; - } - -private: - - Text::String _text; - QString _shortcut; - const not_null _action; - const style::Menu &_st; - const style::icon *_icon; - const style::icon *_iconOver; - // std::unique_ptr _ripple; - std::unique_ptr _toggle; - int _textWidth = 0; - const int _height; - - // rpl::variable _selected = false; - -}; Menu::Menu(QWidget *parent, const style::Menu &st) : RpWidget(parent) @@ -286,7 +78,7 @@ not_null Menu::addAction(not_null action, const style::icon widget->show(); _actionWidgets.push_back(std::move(widget)); } else { - auto widget = base::make_unique_q( + auto widget = base::make_unique_q( this, _st, index, diff --git a/ui/widgets/menu.h b/ui/widgets/menu.h index 9e2f3e5..2bbc5f1 100644 --- a/ui/widgets/menu.h +++ b/ui/widgets/menu.h @@ -76,8 +76,6 @@ protected: void mouseReleaseEvent(QMouseEvent *e) override; private: - class Action; - void updateSelected(QPoint globalPosition); void init(); diff --git a/ui/widgets/menu/menu_action.cpp b/ui/widgets/menu/menu_action.cpp new file mode 100644 index 0000000..1323e19 --- /dev/null +++ b/ui/widgets/menu/menu_action.cpp @@ -0,0 +1,205 @@ +// 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" + +namespace Ui { +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, + result.text.size(), + 1 }); + result.text.append(ch); + } + } else if (ch == '&') { + afterAmpersand = true; + } else { + result.text.append(ch); + } + } + return result; +} + +TextParseOptions MenuTextOptions = { + TextParseLinks | TextParseRichText, // flags + 0, // maxw + 0, // maxh + Qt::LayoutDirectionAuto, // dir +}; + +} // namespace + +Action::Action( + not_null parent, + const style::Menu &st, + int index, + not_null action, + const style::icon *icon, + const style::icon *iconOver, + bool hasSubmenu) +: ItemBase(parent, st, index) +, _action(action) +, _st(st) +, _icon(icon) +, _iconOver(iconOver) +, _height(_st.itemPadding.top() + + _st.itemStyle.font->height + + _st.itemPadding.bottom()) { + + initResizeHook(parent->sizeValue()); + processAction(); + setHasSubmenu(hasSubmenu); + + paintRequest( + ) | rpl::start_with_next([=] { + Painter p(this); + paint(p); + }, lifetime()); + + events( + ) | rpl::filter([=](not_null e) { + return _action->isEnabled() + && ((e->type() == QEvent::Leave) + || (e->type() == QEvent::Enter)); + }) | rpl::map([=](not_null e) { + return (e->type() == QEvent::Enter); + }) | rpl::start_with_next([=](bool selected) { + setSelected(selected); + }, lifetime()); + + events( + ) | rpl::filter([=](not_null e) { + return _action->isEnabled() && (e->type() == QEvent::MouseMove); + }) | rpl::start_with_next([=](not_null e) { + setSelected(true); + }, lifetime()); + + connect(_action, &QAction::changed, [=] { processAction(); }); +} + +void Action::paint(Painter &p) { + const auto enabled = _action->isEnabled(); + const auto selected = isSelected(); + if (selected && _st.itemBgOver->c.alpha() < 255) { + p.fillRect(0, 0, width(), _height, _st.itemBg); + } + p.fillRect(0, 0, width(), _height, selected ? _st.itemBgOver : _st.itemBg); + if (isEnabled()) { + paintRipple(p, 0, 0); + } + if (const auto icon = (selected ? _iconOver : _icon)) { + icon->paint(p, _st.itemIconPosition, width()); + } + p.setPen(selected ? _st.itemFgOver : (enabled ? _st.itemFg : _st.itemFgDisabled)); + _text.drawLeftElided(p, _st.itemPadding.left(), _st.itemPadding.top(), _textWidth, width()); + if (hasSubmenu()) { + const auto left = width() - _st.itemPadding.right() - _st.arrow.width(); + const auto top = (_height - _st.arrow.height()) / 2; + if (enabled) { + _st.arrow.paint(p, left, top, width()); + } else { + _st.arrow.paint( + p, + left, + top, + width(), + _st.itemFgDisabled->c); + } + } else if (!_shortcut.isEmpty()) { + p.setPen(selected + ? _st.itemFgShortcutOver + : (enabled ? _st.itemFgShortcut : _st.itemFgShortcutDisabled)); + p.drawTextRight( + _st.itemPadding.right(), + _st.itemPadding.top(), + width(), + _shortcut); + } +} + +void Action::processAction() { + if (_action->text().isEmpty()) { + _shortcut = QString(); + _text.clear(); + return; + } + const auto actionTextParts = _action->text().split('\t'); + const auto actionText = actionTextParts.empty() + ? QString() + : actionTextParts[0]; + const auto actionShortcut = (actionTextParts.size() > 1) + ? actionTextParts[1] + : QString(); + _text.setMarkedText( + _st.itemStyle, + ParseMenuItem(actionText), + MenuTextOptions); + const auto textWidth = _text.maxWidth(); + const auto &padding = _st.itemPadding; + + const auto additionalWidth = hasSubmenu() + ? padding.right() + _st.arrow.width() + : (!actionShortcut.isEmpty()) + ? (padding.right() + _st.itemStyle.font->width(actionShortcut)) + : 0; + const auto goodWidth = padding.left() + + textWidth + + padding.right() + + additionalWidth; + // if (action->isCheckable()) { + // auto updateCallback = [this, index] { updateItem(index); }; + // if (_toggle) { + // _toggle->setUpdateCallback(updateCallback); + // _toggle->setChecked(action->isChecked(), anim::type::normal); + // } else { + // _toggle = std::make_unique(_st.itemToggle, action->isChecked(), updateCallback); + // } + // goodWidth += _st.itemPadding.right() + _toggle->getSize().width() - _st.itemToggleShift; + // } else { + // _toggle.reset(); + // } + const auto w = std::clamp(goodWidth, _st.widthMin, _st.widthMax); + _textWidth = w - (goodWidth - textWidth); + _shortcut = actionShortcut; + setContentWidth(w); + update(); +} + +bool Action::isEnabled() const { + return _action->isEnabled(); +} + +QAction *Action::action() const { + return _action; +} + +QPoint Action::prepareRippleStartPosition() const { + return mapFromGlobal(QCursor::pos()); +} + +QImage Action::prepareRippleMask() const { + return RippleAnimation::rectMask(size()); +} + +int Action::contentHeight() const { + return _height; +} + +} // namespace Ui diff --git a/ui/widgets/menu/menu_action.h b/ui/widgets/menu/menu_action.h new file mode 100644 index 0000000..d2aa1cd --- /dev/null +++ b/ui/widgets/menu/menu_action.h @@ -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 +// +#pragma once + +#include "ui/text/text.h" +#include "ui/widgets/menu/menu_item_base.h" +#include "styles/style_widgets.h" + +class Painter; + +namespace Ui { + +class Action : public ItemBase { +public: + Action( + not_null parent, + const style::Menu &st, + int index, + not_null action, + const style::icon *icon, + const style::icon *iconOver, + bool hasSubmenu); + + bool isEnabled() const override; + QAction *action() const override; + +protected: + QPoint prepareRippleStartPosition() const override; + QImage prepareRippleMask() const override; + + int contentHeight() const override; + +private: + void processAction(); + void paint(Painter &p); + + Text::String _text; + QString _shortcut; + const not_null _action; + const style::Menu &_st; + const style::icon *_icon; + const style::icon *_iconOver; +// std::unique_ptr _toggle; + int _textWidth = 0; + const int _height; + +}; + +} // namespace Ui