From 1b673b7e406af46e931fd37feabaf9e277ada93b Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 9 Mar 2020 13:02:51 +0400 Subject: [PATCH] Replace SideBarMenu with SideBarButton. --- CMakeLists.txt | 4 +- ui/widgets/side_bar_button.cpp | 80 ++++++++++ ui/widgets/side_bar_button.h | 103 ++++++++++++ ui/widgets/side_bar_menu.cpp | 277 --------------------------------- ui/widgets/side_bar_menu.h | 83 ---------- ui/widgets/widgets.style | 25 ++- 6 files changed, 197 insertions(+), 375 deletions(-) create mode 100644 ui/widgets/side_bar_button.cpp create mode 100644 ui/widgets/side_bar_button.h delete mode 100644 ui/widgets/side_bar_menu.cpp delete mode 100644 ui/widgets/side_bar_menu.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 508d6b0..209df61 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -135,8 +135,8 @@ PRIVATE ui/widgets/popup_menu.h ui/widgets/scroll_area.cpp ui/widgets/scroll_area.h - ui/widgets/side_bar_menu.cpp - ui/widgets/side_bar_menu.h + ui/widgets/side_bar_button.cpp + ui/widgets/side_bar_button.h ui/widgets/shadow.cpp ui/widgets/shadow.h ui/widgets/tooltip.cpp diff --git a/ui/widgets/side_bar_button.cpp b/ui/widgets/side_bar_button.cpp new file mode 100644 index 0000000..c4b047e --- /dev/null +++ b/ui/widgets/side_bar_button.cpp @@ -0,0 +1,80 @@ +// 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/side_bar_button.h" + +#include "ui/effects/ripple_animation.h" + +#include + +namespace Ui { +namespace { + +constexpr auto kMaxLabelLines = 3; + +} // namespace + +SideBarButton::SideBarButton( + not_null parent, + const QString &title, + const style::SideBarButton &st) +: RippleButton(parent, st.ripple) +, _st(st) +, _text(_st.minTextWidth) { + _text.setText(_st.style, title); + setAttribute(Qt::WA_OpaquePaintEvent); +} + +void SideBarButton::setActive(bool active) { + if (_active == active) { + return; + } + _active = active; + update(); +} + +void SideBarButton::setBadge(const QString &badge) { + if (_badge.toString() == badge) { + return; + } + _badge.setText(_st.badgeStyle, badge); + update(); +} + +int SideBarButton::resizeGetHeight(int newWidth) { + auto result = _st.minHeight; + const auto text = _text.countHeight(newWidth - _st.textSkip * 2); + const auto add = text - _st.style.font->height; + return result + std::max(add, 0); +} + +void SideBarButton::paintEvent(QPaintEvent *e) { + auto p = Painter(this); + const auto clip = e->rect(); + + p.fillRect(clip, _active ? _st.textBgActive : _st.textBg); + + RippleButton::paintRipple(p, 0, 0); + + const auto &icon = _active ? _st.iconActive : _st.icon; + const auto x = (_st.iconPosition.x() < 0) + ? (width() - icon.width()) / 2 + : _st.iconPosition.x(); + const auto y = (_st.iconPosition.y() < 0) + ? (height() - icon.height()) / 2 + : _st.iconPosition.y(); + icon.paint(p, x, y, width()); + p.setPen(_active ? _st.textFgActive : _st.textFg); + _text.drawElided( + p, + _st.textSkip, + _st.textTop, + (width() - 2 * _st.textSkip), + kMaxLabelLines, + style::al_top); +} + +} // namespace Ui diff --git a/ui/widgets/side_bar_button.h b/ui/widgets/side_bar_button.h new file mode 100644 index 0000000..40d977c --- /dev/null +++ b/ui/widgets/side_bar_button.h @@ -0,0 +1,103 @@ +// This file is part of Desktop App Toolkit, +// a set of libraries for developing nice desktop applications. +// +// For license and copyright information please follow this link: +// https://github.com/desktop-app/legal/blob/master/LEGAL +// +#pragma once + +#include "ui/widgets/buttons.h" +#include "ui/text/text.h" + +namespace style { +struct SideBarButton; +} // namespace style + +namespace Ui { + +class RippleAnimation; + +class SideBarButton final : public Ui::RippleButton { +public: + SideBarButton( + not_null parent, + const QString &title, + const style::SideBarButton &st); + + void setActive(bool active); + void setBadge(const QString &badge); + + int resizeGetHeight(int newWidth) override; + +private: + void paintEvent(QPaintEvent *e) override; + + const style::SideBarButton &_st; + Ui::Text::String _text; + Ui::Text::String _badge; + bool _active = false; + +}; +// +//class SideBarMenu final { +//public: +// struct Item { +// QString id; +// QString title; +// QString badge; +// not_null icon; +// not_null iconActive; +// int iconTop = 0; +// }; +// +// SideBarMenu(not_null parent, const style::SideBarMenu &st); +// ~SideBarMenu(); +// +// [[nodiscard]] not_null widget() const; +// +// void setGeometry(QRect geometry); +// void setItems(std::vector items); +// void setActive( +// const QString &id, +// anim::type animated = anim::type::normal); +// [[nodiscard]] rpl::producer activateRequests() const; +// +// [[nodiscard]] rpl::lifetime &lifetime(); +// +//private: +// struct MenuItem { +// Item data; +// Ui::Text::String text; +// mutable std::unique_ptr ripple; +// int top = 0; +// int height = 0; +// }; +// void setup(); +// void paint(Painter &p, QRect clip) const; +// [[nodiscard]] int countContentHeight(int width, int outerHeight); +// +// void mouseMove(QPoint position); +// void mousePress(Qt::MouseButton button); +// void mouseRelease(Qt::MouseButton button); +// +// void setSelected(int selected); +// void setPressed(int pressed); +// void addRipple(MenuItem &item, QPoint position); +// void repaint(const QString &id); +// [[nodiscard]] MenuItem *itemById(const QString &id); +// +// const style::SideBarMenu &_st; +// +// Ui::RpWidget _outer; +// const not_null _scroll; +// const not_null _inner; +// std::vector _items; +// int _selected = -1; +// int _pressed = -1; +// +// QString _activeId; +// rpl::event_stream _activateRequests; +// +//}; + +} // namespace Ui diff --git a/ui/widgets/side_bar_menu.cpp b/ui/widgets/side_bar_menu.cpp deleted file mode 100644 index 8b70688..0000000 --- a/ui/widgets/side_bar_menu.cpp +++ /dev/null @@ -1,277 +0,0 @@ -// 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/side_bar_menu.h" - -#include "ui/effects/ripple_animation.h" - -namespace Ui { -namespace { - -constexpr auto kMaxLabelLines = 3; - -} // namespace - -SideBarMenu::SideBarMenu( - not_null parent, - const style::SideBarMenu &st) -: _st(st) -, _outer(parent) -, _scroll(Ui::CreateChild(&_outer)) -, _inner(_scroll->setOwnedWidget(object_ptr(_scroll))) { - setup(); -} - -SideBarMenu::~SideBarMenu() = default; - -not_null SideBarMenu::widget() const { - return &_outer; -} - -void SideBarMenu::setGeometry(QRect geometry) { - _outer.setGeometry(geometry); -} - -void SideBarMenu::setItems(std::vector items) { - const auto itemId = [](const MenuItem &item) { - return item.data.id; - }; - const auto textWidth = _st.minTextWidth; - const auto finalize = gsl::finally([&] { - _inner->resize( - _inner->width(), - countContentHeight(_inner->width(), _outer.height())); - _inner->update(); - }); - if (ranges::equal(items, _items, std::less<>(), &Item::id, itemId)) { - for (auto &&[was, now] : ranges::view::zip(_items, items)) { - if (was.data.title != now.title) { - was.data.title = now.title; - was.text.setText(_st.style, now.title); - } - if (was.data.badge != now.badge) { - was.data.badge = now.badge; - } - } - _inner->update(); - return; - } - const auto selected = _selected; - if (_selected >= 0) { - setSelected(-1); - } - if (_pressed >= 0) { - setPressed(-1); - } - - auto current = base::take(_items); - _items.reserve(items.size()); - for (const auto &item : items) { - const auto i = ranges::find(current, item.id, itemId); - if (i != end(current)) { - _items.push_back(std::move(*i)); - } else { - _items.push_back({ item }); - _items.back().text = Ui::Text::String(textWidth); - _items.back().text.setText(_st.style, item.title); - } - } - if (selected >= 0 && selected < _items.size()) { - setSelected(selected); - } -} - -void SideBarMenu::setActive(const QString &id, anim::type animated) { - _activeId = id; - _inner->update(); -} - -rpl::producer SideBarMenu::activateRequests() const { - return _activateRequests.events(); -} - -rpl::lifetime &SideBarMenu::lifetime() { - return _outer.lifetime(); -} - -void SideBarMenu::setup() { - _inner->move(0, 0); - _scroll->move(0, 0); - - _outer.sizeValue( - ) | rpl::start_with_next([=](QSize size) { - _scroll->resize(size); - _inner->resize( - size.width(), - countContentHeight(size.width(), size.height())); - }, lifetime()); - - _inner->paintRequest( - ) | rpl::start_with_next([=](QRect clip) { - auto p = Painter(_inner); - paint(p, clip); - }, lifetime()); - - _inner->setMouseTracking(true); - _inner->events( - ) | rpl::start_with_next([=](not_null e) { - switch (e->type()) { - case QEvent::MouseMove: - mouseMove(static_cast(e.get())->pos()); - break; - case QEvent::MouseButtonPress: - mousePress(static_cast(e.get())->button()); - break; - case QEvent::MouseButtonRelease: - mouseRelease(static_cast(e.get())->button()); - break; - case QEvent::Leave: - setSelected(-1); - break; - } - }, lifetime()); - - _outer.show(); -} - -void SideBarMenu::mouseMove(QPoint position) { - auto selected = -1; - auto y = _st.margins.top(); - for (auto &item : _items) { - if (position.y() < y) { - break; - } - ++selected; - y += item.height; - } - if (selected + 1 == _items.size() && position.y() >= y) { - selected = -1; - } - setSelected(selected); -} - -void SideBarMenu::mousePress(Qt::MouseButton button) { - if (button != Qt::LeftButton) { - return; - } - setPressed(_selected); -} - -void SideBarMenu::mouseRelease(Qt::MouseButton button) { - if (button != Qt::LeftButton) { - return; - } - const auto pressed = _pressed; - setPressed(-1); - if (_selected != pressed || pressed < 0) { - return; - } - _activateRequests.fire_copy(_items[pressed].data.id); -} - -void SideBarMenu::setSelected(int selected) { - const auto was = (_selected >= 0); - _selected = selected; - const auto now = (_selected >= 0); - if (was != now) { - _inner->setCursor(now ? style::cur_pointer : style::cur_default); - } -} - -void SideBarMenu::setPressed(int pressed) { - if (_pressed == pressed) { - return; - } else if (_pressed >= 0 && _items[_pressed].ripple) { - _items[_pressed].ripple->lastStop(); - } - _pressed = pressed; - if (_pressed >= 0) { - addRipple(_items[_pressed], _inner->mapFromGlobal(QCursor::pos())); - } -} - -void SideBarMenu::addRipple(MenuItem &item, QPoint position) { - auto &ripple = item.ripple; - const auto id = item.data.id; - if (!ripple) { - ripple = std::make_unique( - st::defaultRippleAnimation, - RippleAnimation::rectMask({ _inner->width(), item.height }), - [=] { repaint(id); }); - } - const auto local = _inner->mapFromGlobal(QCursor::pos()); - ripple->add(local - QPoint(0, item.top)); -} - -void SideBarMenu::repaint(const QString &id) { - if (const auto item = itemById(id)) { - _inner->update(0, item->top, _inner->width(), item->height); - } -} - -SideBarMenu::MenuItem *SideBarMenu::itemById(const QString &id) { - const auto i = ranges::find(_items, id, [](const MenuItem &item) { - return item.data.id; - }); - return (i != end(_items)) ? &*i : nullptr; -} - -void SideBarMenu::paint(Painter &p, QRect clip) const { - auto y = _st.margins.top(); - const auto fullWidth = _inner->width(); - const auto availableWidth = fullWidth - - _st.margins.left() - - _st.margins.right(); - p.fillRect(clip, _st.textBg); - for (const auto &item : _items) { - if (y + item.height <= clip.y()) { - y += item.height; - continue; - } else if (y >= clip.y() + clip.height()) { - break; - } - const auto active = (item.data.id == _activeId); - if (active) { - p.fillRect(0, y, fullWidth, item.height, _st.textBgActive); - } - if (item.ripple) { - item.ripple->paint(p, 0, y, fullWidth, &_st.rippleBg->c); - if (item.ripple->empty()) { - item.ripple = nullptr; - } - } - const auto icon = (active ? item.data.iconActive : item.data.icon); - const auto x = (fullWidth - icon->width()) / 2; - icon->paint(p, x, y + item.data.iconTop, fullWidth); - p.setPen(active ? _st.textFgActive : _st.textFg); - item.text.drawElided( - p, - _st.margins.left(), - y + _st.textTop, - availableWidth, - kMaxLabelLines, - style::al_top); - y += item.height; - } -} - -int SideBarMenu::countContentHeight(int width, int outerHeight) { - const auto available = width - _st.margins.left() - _st.margins.right(); - const auto withoutText = _st.textTop + _st.bottomSkip; - auto rows = _st.margins.top(); - for (auto &item : _items) { - const auto fullTextHeight = item.text.countHeight(available); - const auto textHeight = std::min( - fullTextHeight, - kMaxLabelLines * _st.style.font->height); - item.top = rows; - item.height = withoutText + textHeight; - rows += item.height; - } - return std::max(rows + _st.margins.bottom(), outerHeight); -} - -} // namespace Ui diff --git a/ui/widgets/side_bar_menu.h b/ui/widgets/side_bar_menu.h deleted file mode 100644 index 15ad122..0000000 --- a/ui/widgets/side_bar_menu.h +++ /dev/null @@ -1,83 +0,0 @@ -// 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/rp_widget.h" -#include "ui/widgets/scroll_area.h" -#include "ui/text/text.h" -#include "ui/painter.h" - -namespace style { -struct SideBarMenu; -} // namespace style - -namespace Ui { - -class RippleAnimation; - -class SideBarMenu final { -public: - struct Item { - QString id; - QString title; - QString badge; - not_null icon; - not_null iconActive; - int iconTop = 0; - }; - - SideBarMenu(not_null parent, const style::SideBarMenu &st); - ~SideBarMenu(); - - [[nodiscard]] not_null widget() const; - - void setGeometry(QRect geometry); - void setItems(std::vector items); - void setActive( - const QString &id, - anim::type animated = anim::type::normal); - [[nodiscard]] rpl::producer activateRequests() const; - - [[nodiscard]] rpl::lifetime &lifetime(); - -private: - struct MenuItem { - Item data; - Ui::Text::String text; - mutable std::unique_ptr ripple; - int top = 0; - int height = 0; - }; - void setup(); - void paint(Painter &p, QRect clip) const; - [[nodiscard]] int countContentHeight(int width, int outerHeight); - - void mouseMove(QPoint position); - void mousePress(Qt::MouseButton button); - void mouseRelease(Qt::MouseButton button); - - void setSelected(int selected); - void setPressed(int pressed); - void addRipple(MenuItem &item, QPoint position); - void repaint(const QString &id); - [[nodiscard]] MenuItem *itemById(const QString &id); - - const style::SideBarMenu &_st; - - Ui::RpWidget _outer; - const not_null _scroll; - const not_null _inner; - std::vector _items; - int _selected = -1; - int _pressed = -1; - - QString _activeId; - rpl::event_stream _activateRequests; - -}; - -} // namespace Ui diff --git a/ui/widgets/widgets.style b/ui/widgets/widgets.style index 1e4cc61..b443bb1 100644 --- a/ui/widgets/widgets.style +++ b/ui/widgets/widgets.style @@ -544,20 +544,24 @@ WindowTitle { closeIconActiveOver: icon; } -SideBarMenu { - margins: margins; +SideBarButton { + icon: icon; + iconActive: icon; + iconPosition: point; textTop: pixels; + textSkip: pixels; minTextWidth: pixels; - bottomSkip: pixels; + minHeight: pixels; style: TextStyle; + badgeStyle: TextStyle; textBg: color; textBgActive: color; textFg: color; textFgActive: color; - rippleBg: color; badgeBg: color; badgeBgMuted: color; badgeFg: color; + ripple: RippleAnimation; } defaultLabelSimple: LabelSimple { @@ -1223,22 +1227,17 @@ defaultSettingsButton: SettingsButton { ripple: defaultRippleAnimation; } -defaultSideBarMenu: SideBarMenu { - margins: margins(6px, 6px, 6px, 6px); - textTop: 42px; - minTextWidth: 48px; - bottomSkip: 7px; - style: TextStyle(defaultTextStyle) { - font: font(11px semibold); - } +defaultSideBarButton: SideBarButton { textBg: sideBarBg; textBgActive: sideBarBgActive; textFg: sideBarTextFg; textFgActive: sideBarTextFgActive; - rippleBg: sideBarBgRipple; badgeBg: sideBarBadgeBg; badgeBgMuted: sideBarBadgeBgMuted; badgeFg: sideBarBadgeFg; + ripple: RippleAnimation(defaultRippleAnimation) { + color: sideBarBgRipple; + } } // Windows specific title