Display a toggle in Menu for Checkable actions.

This commit is contained in:
John Preston 2017-06-29 15:29:00 +03:00
parent 5dcf341aaa
commit 19023b4cc2
6 changed files with 180 additions and 15 deletions

View File

@ -621,6 +621,13 @@ public:
}
}
template <typename Lambda>
void setUpdateCallback(Lambda &&updateCallback) {
if (_data) {
_data->updateCallback = std::forward<Lambda>(updateCallback);
}
}
private:
struct Data {
template <typename Lambda>

View File

@ -270,4 +270,60 @@ Radiobutton::~Radiobutton() {
_group->unregisterButton(this);
}
ToggleView::ToggleView(const style::Toggle &st, bool toggled, base::lambda<void()> updateCallback)
: _st(&st)
, _toggled(toggled)
, _updateCallback(std::move(updateCallback)) {
}
void ToggleView::setStyle(const style::Toggle &st) {
_st = &st;
}
void ToggleView::setToggledFast(bool toggled) {
_toggled = toggled;
finishAnimation();
if (_updateCallback) {
_updateCallback();
}
}
void ToggleView::setUpdateCallback(base::lambda<void()> updateCallback) {
_updateCallback = std::move(updateCallback);
if (_toggleAnimation.animating()) {
_toggleAnimation.setUpdateCallback(_updateCallback);
}
}
void ToggleView::setToggledAnimated(bool toggled) {
if (_toggled != toggled) {
_toggled = toggled;
_toggleAnimation.start(_updateCallback, _toggled ? 0. : 1., _toggled ? 1. : 0., _st->duration);
}
}
void ToggleView::finishAnimation() {
_toggleAnimation.finish();
}
void ToggleView::paint(Painter &p, int left, int top, int outerWidth, TimeMs ms) {
PainterHighQualityEnabler hq(p);
auto toggled = _toggleAnimation.current(ms, _toggled ? 1. : 0.);
auto fullWidth = _st->diameter + _st->width;
auto innerDiameter = _st->diameter - 2 * _st->shift;
auto innerRadius = float64(innerDiameter) / 2.;
auto bgRect = rtlrect(left + _st->shift, top + _st->shift, fullWidth - 2 * _st->shift, innerDiameter, outerWidth);
auto fgRect = rtlrect(left + anim::interpolate(0, fullWidth - _st->diameter, toggled), top, _st->diameter, _st->diameter, outerWidth);
p.setPen(Qt::NoPen);
p.setBrush(anim::brush(_st->untoggledFg, _st->toggledFg, toggled));
p.drawRoundedRect(bgRect, innerRadius, innerRadius);
auto pen = anim::pen(_st->untoggledFg, _st->toggledFg, toggled);
pen.setWidth(_st->border);
p.setPen(pen);
p.setBrush(anim::brush(_st->untoggledBg, _st->toggledBg, toggled));
p.drawEllipse(fgRect);
}
} // namespace Ui

View File

@ -194,4 +194,24 @@ public:
};
class ToggleView {
public:
ToggleView(const style::Toggle &st, bool toggled, base::lambda<void()> updateCallback);
void setToggledFast(bool toggled);
void setToggledAnimated(bool toggled);
void finishAnimation();
void setStyle(const style::Toggle &st);
void setUpdateCallback(base::lambda<void()> updateCallback);
void paint(Painter &p, int left, int top, int outerWidth, TimeMs ms);
private:
gsl::not_null<const style::Toggle*> _st;
bool _toggled = false;
base::lambda<void()> _updateCallback;
Animation _toggleAnimation;
};
} // namespace Ui

View File

@ -18,9 +18,12 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "ui/widgets/menu.h"
#include "ui/effects/ripple_animation.h"
#include "ui/widgets/checkbox.h"
namespace Ui {
Menu::ActionData::~ActionData() = default;
Menu::Menu(QWidget *parent, const style::Menu &st) : TWidget(parent)
, _st(st)
, _itemHeight(_st.itemPadding.top() + _st.itemFont->height + _st.itemPadding.bottom())
@ -43,7 +46,7 @@ Menu::Menu(QWidget *parent, QMenu *menu, const style::Menu &st) : TWidget(parent
}
void Menu::init() {
resize(_st.widthMin, _st.skip * 2);
resize(_forceWidth ? _forceWidth : _st.widthMin, _st.skip * 2);
setMouseTracking(true);
@ -64,18 +67,22 @@ QAction *Menu::addAction(const QString &text, base::lambda<void()> callback, con
QAction *Menu::addAction(QAction *action, const style::icon *icon, const style::icon *iconOver) {
connect(action, SIGNAL(changed()), this, SLOT(actionChanged()));
connect(action, SIGNAL(toggled(bool)), this, SLOT(actionToggled(bool)));
_actions.push_back(action);
ActionData data;
data.icon = icon;
data.iconOver = iconOver ? iconOver : icon;
data.hasSubmenu = (action->menu() != nullptr);
_actionsData.push_back(data);
auto createData = [icon, iconOver, action] {
auto data = ActionData();
data.icon = icon;
data.iconOver = iconOver ? iconOver : icon;
data.hasSubmenu = (action->menu() != nullptr);
return data;
};
_actionsData.push_back(createData());
auto newWidth = qMax(width(), _st.widthMin);
newWidth = processAction(action, _actions.size() - 1, newWidth);
auto newHeight = height() + (action->isSeparator() ? _separatorHeight : _itemHeight);
resize(newWidth, newHeight);
resize(_forceWidth ? _forceWidth : newWidth, newHeight);
if (_resizedCallback) {
_resizedCallback();
}
@ -97,7 +104,7 @@ void Menu::clearActions() {
delete action;
}
}
resize(_st.widthMin, _st.skip * 2);
resize(_forceWidth ? _forceWidth : _st.widthMin, _st.skip * 2);
if (_resizedCallback) {
_resizedCallback();
}
@ -118,6 +125,18 @@ int Menu::processAction(QAction *action, int index, int width) {
} else if (!actionShortcut.isEmpty()) {
goodw += _st.itemPadding.left() + _st.itemFont->width(actionShortcut);
}
if (action->isCheckable()) {
auto updateCallback = [this, index] { updateItem(index); };
goodw += _st.itemPadding.left() + _st.itemToggle.diameter + _st.itemToggle.width - _st.itemToggleShift;
if (data.toggle) {
data.toggle->setUpdateCallback(updateCallback);
data.toggle->setToggledAnimated(action->isChecked());
} else {
data.toggle = std::make_unique<ToggleView>(_st.itemToggle, action->isChecked(), updateCallback);
}
} else {
data.toggle.reset();
}
width = snap(goodw, width, _st.widthMax);
data.text = (width < goodw) ? _st.itemFont->elided(actionText, width - (goodw - textw)) : actionText;
data.shortcut = actionShortcut;
@ -134,12 +153,17 @@ Menu::Actions &Menu::actions() {
return _actions;
}
void Menu::setForceWidth(int forceWidth) {
_forceWidth = forceWidth;
resize(_forceWidth, height());
}
void Menu::actionChanged() {
int newWidth = _st.widthMin;
for (int i = 0, count = _actions.size(); i != count; ++i) {
auto newWidth = _st.widthMin;
for (auto i = 0, count = _actions.size(); i != count; ++i) {
newWidth = processAction(_actions[i], i, newWidth);
}
if (newWidth != width()) {
if (newWidth != width() && !_forceWidth) {
resize(newWidth, height());
if (_resizedCallback) {
_resizedCallback();
@ -193,6 +217,9 @@ void Menu::paintEvent(QPaintEvent *e) {
} else if (!data.shortcut.isEmpty()) {
p.setPen(selected ? _st.itemFgShortcutOver : (enabled ? _st.itemFgShortcut : _st.itemFgShortcutDisabled));
p.drawTextRight(_st.itemPadding.right(), _st.itemPadding.top(), width(), data.shortcut);
} else if (data.toggle) {
auto toggleSize = _st.itemToggle.diameter + _st.itemToggle.width;
data.toggle->paint(p, width() - _st.itemPadding.right() - toggleSize + _st.itemToggleShift, (_itemHeight - _st.itemToggle.diameter) / 2, width(), ms);
}
}
}
@ -220,7 +247,7 @@ void Menu::itemPressed(TriggeredSource source) {
if (source == TriggeredSource::Mouse) {
if (!_actionsData[_pressed].ripple) {
auto mask = RippleAnimation::rectMask(QSize(width(), _itemHeight));
_actionsData[_pressed].ripple = MakeShared<RippleAnimation>(_st.ripple, std::move(mask), [this, selected = _pressed] {
_actionsData[_pressed].ripple = std::make_unique<RippleAnimation>(_st.ripple, std::move(mask), [this, selected = _pressed] {
updateItem(selected);
});
}
@ -234,6 +261,9 @@ void Menu::itemPressed(TriggeredSource source) {
void Menu::itemReleased(TriggeredSource source) {
auto pressed = std::exchange(_pressed, -1);
if (pressed >= 0 && pressed < _actions.size()) {
if (pressed != _selected && _actionsData[pressed].toggle) {
_actionsData[pressed].toggle->setStyle(_st.itemToggle);
}
if (source == TriggeredSource::Mouse && _actionsData[pressed].ripple) {
_actionsData[pressed].ripple->lastStop();
}
@ -316,7 +346,13 @@ void Menu::setSelected(int selected) {
}
if (_selected != selected) {
updateSelectedItem();
if (_selected >= 0 && _selected != _pressed && _actionsData[_selected].toggle) {
_actionsData[_selected].toggle->setStyle(_st.itemToggle);
}
_selected = selected;
if (_selected >= 0 && _actionsData[_selected].toggle && _actions[_selected]->isEnabled()) {
_actionsData[_selected].toggle->setStyle(_st.itemToggleOver);
}
updateSelectedItem();
if (_activatedCallback) {
auto source = _mouseSelection ? TriggeredSource::Mouse : TriggeredSource::Keyboard;

View File

@ -24,6 +24,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
namespace Ui {
class ToggleView;
class RippleAnimation;
class Menu : public TWidget {
@ -48,6 +49,7 @@ public:
_childShown = shown;
}
void setShowSource(TriggeredSource source);
void setForceWidth(int forceWidth);
using Actions = QList<QAction*>;
Actions &actions();
@ -123,19 +125,27 @@ private:
base::lambda<void(QPoint globalPosition)> _mouseReleaseDelegate;
struct ActionData {
ActionData() = default;
ActionData(const ActionData &other) = delete;
ActionData &operator=(const ActionData &other) = delete;
ActionData(ActionData &&other) = default;
ActionData &operator=(ActionData &&other) = default;
~ActionData();
bool hasSubmenu = false;
QString text;
QString shortcut;
const style::icon *icon = nullptr;
const style::icon *iconOver = nullptr;
QSharedPointer<RippleAnimation> ripple;
std::unique_ptr<RippleAnimation> ripple;
std::unique_ptr<ToggleView> toggle;
};
using ActionsData = QList<ActionData>;
QMenu *_wappedMenu = nullptr;
Actions _actions;
ActionsData _actionsData;
std::vector<ActionData> _actionsData;
int _forceWidth = 0;
int _itemHeight, _separatorHeight;
bool _mouseSelection = false;

View File

@ -360,6 +360,18 @@ CallButton {
outerBg: color;
}
Toggle {
toggledBg: color;
toggledFg: color;
untoggledBg: color;
untoggledFg: color;
duration: int;
border: pixels;
shift: pixels;
diameter: pixels;
width: pixels;
}
Menu {
skip: pixels;
@ -374,6 +386,9 @@ Menu {
itemPadding: margins;
itemIconPosition: point;
itemFont: font;
itemToggle: Toggle;
itemToggleOver: Toggle;
itemToggleShift: pixels;
separatorPadding: margins;
separatorWidth: pixels;
@ -766,7 +781,25 @@ defaultRoundCheckbox: RoundCheckbox {
duration: 150;
}
defaultToggle: Toggle {
toggledBg: windowBg;
toggledFg: windowBgActive;
untoggledBg: windowBg;
untoggledFg: checkboxFg;
duration: 200;
border: 2px;
shift: 1px;
diameter: 20px;
width: 16px;
}
defaultMenuArrow: icon {{ "dropdown_submenu_arrow", menuSubmenuArrowFg }};
defaultMenuToggle: Toggle(defaultToggle) {
untoggledFg: menuIconFg;
}
defaultMenuToggleOver: Toggle(defaultToggle) {
untoggledFg: menuIconFgOver;
}
defaultMenu: Menu {
skip: 0px;
@ -781,6 +814,9 @@ defaultMenu: Menu {
itemIconPosition: point(0px, 0px);
itemPadding: margins(17px, 8px, 17px, 7px);
itemFont: normalFont;
itemToggle: defaultMenuToggle;
itemToggleOver: defaultMenuToggleOver;
itemToggleShift: 0px;
separatorPadding: margins(0px, 5px, 0px, 5px);
separatorWidth: 1px;