Merge remote-tracking branch 'upstream/master'
This commit is contained in:
commit
659d46b693
13 changed files with 764 additions and 10 deletions
|
|
@ -135,6 +135,8 @@ PRIVATE
|
||||||
ui/widgets/popup_menu.h
|
ui/widgets/popup_menu.h
|
||||||
ui/widgets/scroll_area.cpp
|
ui/widgets/scroll_area.cpp
|
||||||
ui/widgets/scroll_area.h
|
ui/widgets/scroll_area.h
|
||||||
|
ui/widgets/side_bar_button.cpp
|
||||||
|
ui/widgets/side_bar_button.h
|
||||||
ui/widgets/shadow.cpp
|
ui/widgets/shadow.cpp
|
||||||
ui/widgets/shadow.h
|
ui/widgets/shadow.h
|
||||||
ui/widgets/tooltip.cpp
|
ui/widgets/tooltip.cpp
|
||||||
|
|
@ -149,6 +151,8 @@ PRIVATE
|
||||||
ui/wrap/slide_wrap.h
|
ui/wrap/slide_wrap.h
|
||||||
ui/wrap/vertical_layout.cpp
|
ui/wrap/vertical_layout.cpp
|
||||||
ui/wrap/vertical_layout.h
|
ui/wrap/vertical_layout.h
|
||||||
|
ui/wrap/vertical_layout_reorder.cpp
|
||||||
|
ui/wrap/vertical_layout_reorder.h
|
||||||
ui/wrap/wrap.h
|
ui/wrap/wrap.h
|
||||||
ui/abstract_button.cpp
|
ui/abstract_button.cpp
|
||||||
ui/abstract_button.h
|
ui/abstract_button.h
|
||||||
|
|
|
||||||
|
|
@ -592,3 +592,14 @@ walletSubBalanceFg: #f9f9f9; // wallet balance label text
|
||||||
walletTopLabelFg: #999999; // wallet top updated label text
|
walletTopLabelFg: #999999; // wallet top updated label text
|
||||||
walletTopIconFg: walletTopLabelFg; // wallet top refresh and menu icons
|
walletTopIconFg: walletTopLabelFg; // wallet top refresh and menu icons
|
||||||
walletTopIconRipple: #ffffff12; // wallet top menu icon ripple effect
|
walletTopIconRipple: #ffffff12; // wallet top menu icon ripple effect
|
||||||
|
|
||||||
|
sideBarBg: #293a4c; // filters side bar background
|
||||||
|
sideBarBgActive: #17212b; // filters side bar active background
|
||||||
|
sideBarBgRipple: #1e2b38; // filters side bar ripple effect
|
||||||
|
sideBarTextFg: #8897a6; // filters side bar text
|
||||||
|
sideBarTextFgActive: #64b9fa; // filters side bar active item text
|
||||||
|
sideBarIconFg: #8393a3; // filters side bar icon
|
||||||
|
sideBarIconFgActive: #5eb5f7; // filters side bar active item icon
|
||||||
|
sideBarBadgeBg: #5eb5f7; // filters side bar badge background
|
||||||
|
sideBarBadgeBgMuted: #8393a3; // filters side bar unimportant badge background
|
||||||
|
sideBarBadgeFg: #ffffff; // filters side bar badge text
|
||||||
|
|
|
||||||
|
|
@ -25,4 +25,8 @@ void GenericBox::addSkip(int height) {
|
||||||
addRow(object_ptr<Ui::FixedHeightWidget>(this, height));
|
addRow(object_ptr<Ui::FixedHeightWidget>(this, height));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
not_null<Ui::VerticalLayout*> GenericBox::verticalLayout() {
|
||||||
|
return _content.data();
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace Ui
|
} // namespace Ui
|
||||||
|
|
|
||||||
|
|
@ -77,6 +77,8 @@ public:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] not_null<Ui::VerticalLayout*> verticalLayout();
|
||||||
|
|
||||||
using BoxContent::setNoContentMargin;
|
using BoxContent::setNoContentMargin;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
|
|
||||||
|
|
@ -633,12 +633,21 @@ void LayerStackWidget::prepareForAnimation() {
|
||||||
show();
|
show();
|
||||||
}
|
}
|
||||||
if (_mainMenu) {
|
if (_mainMenu) {
|
||||||
|
if (Ui::InFocusChain(_mainMenu)) {
|
||||||
|
setFocus();
|
||||||
|
}
|
||||||
_mainMenu->hide();
|
_mainMenu->hide();
|
||||||
}
|
}
|
||||||
if (_specialLayer) {
|
if (_specialLayer) {
|
||||||
|
if (Ui::InFocusChain(_specialLayer)) {
|
||||||
|
setFocus();
|
||||||
|
}
|
||||||
_specialLayer->hide();
|
_specialLayer->hide();
|
||||||
}
|
}
|
||||||
if (const auto layer = currentLayer()) {
|
if (const auto layer = currentLayer()) {
|
||||||
|
if (Ui::InFocusChain(layer)) {
|
||||||
|
setFocus();
|
||||||
|
}
|
||||||
layer->hide();
|
layer->hide();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,7 @@ void InitOnTopPanel(not_null<QWidget*> panel) {
|
||||||
Assert([platformWindow isKindOfClass:[NSPanel class]]);
|
Assert([platformWindow isKindOfClass:[NSPanel class]]);
|
||||||
|
|
||||||
auto platformPanel = static_cast<NSPanel*>(platformWindow);
|
auto platformPanel = static_cast<NSPanel*>(platformWindow);
|
||||||
[platformPanel setLevel:NSPopUpMenuWindowLevel];
|
[platformPanel setLevel:NSModalPanelWindowLevel];
|
||||||
[platformPanel setCollectionBehavior:NSWindowCollectionBehaviorCanJoinAllSpaces|NSWindowCollectionBehaviorStationary|NSWindowCollectionBehaviorFullScreenAuxiliary|NSWindowCollectionBehaviorIgnoresCycle];
|
[platformPanel setCollectionBehavior:NSWindowCollectionBehaviorCanJoinAllSpaces|NSWindowCollectionBehaviorStationary|NSWindowCollectionBehaviorFullScreenAuxiliary|NSWindowCollectionBehaviorIgnoresCycle];
|
||||||
[platformPanel setHidesOnDeactivate:NO];
|
[platformPanel setHidesOnDeactivate:NO];
|
||||||
//[platformPanel setFloatingPanel:YES];
|
//[platformPanel setFloatingPanel:YES];
|
||||||
|
|
|
||||||
183
ui/widgets/side_bar_button.cpp
Normal file
183
ui/widgets/side_bar_button.cpp
Normal file
|
|
@ -0,0 +1,183 @@
|
||||||
|
// 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 <QtGui/QtEvents>
|
||||||
|
|
||||||
|
namespace Ui {
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
constexpr auto kMaxLabelLines = 3;
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
SideBarButton::SideBarButton(
|
||||||
|
not_null<QWidget*> 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);
|
||||||
|
|
||||||
|
style::PaletteChanged(
|
||||||
|
) | rpl::start_with_next([=] {
|
||||||
|
_iconCache = _iconCacheActive = QImage();
|
||||||
|
update();
|
||||||
|
}, lifetime());
|
||||||
|
}
|
||||||
|
|
||||||
|
void SideBarButton::setActive(bool active) {
|
||||||
|
if (_active == active) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_active = active;
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SideBarButton::setBadge(const QString &badge, bool muted) {
|
||||||
|
if (_badge.toString() == badge && _badgeMuted == muted) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_badge.setText(_st.badgeStyle, badge);
|
||||||
|
_badgeMuted = muted;
|
||||||
|
const auto width = badge.isEmpty()
|
||||||
|
? 0
|
||||||
|
: std::max(_st.badgeHeight, _badge.maxWidth() + 2 * _st.badgeSkip);
|
||||||
|
if (_iconCacheBadgeWidth != width) {
|
||||||
|
_iconCacheBadgeWidth = width;
|
||||||
|
_iconCache = _iconCacheActive = QImage();
|
||||||
|
}
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SideBarButton::setIconOverride(
|
||||||
|
const style::icon *iconOverride,
|
||||||
|
const style::icon *iconOverrideActive) {
|
||||||
|
_iconOverride = iconOverride;
|
||||||
|
_iconOverrideActive = iconOverrideActive;
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
|
||||||
|
int SideBarButton::resizeGetHeight(int newWidth) {
|
||||||
|
auto result = _st.minHeight;
|
||||||
|
const auto text = std::min(
|
||||||
|
_text.countHeight(newWidth - _st.textSkip * 2),
|
||||||
|
_st.style.font->height * kMaxLabelLines);
|
||||||
|
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 = computeIcon();
|
||||||
|
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();
|
||||||
|
if (_iconCacheBadgeWidth) {
|
||||||
|
validateIconCache();
|
||||||
|
p.drawImage(x, y, _active ? _iconCacheActive : _iconCache);
|
||||||
|
} else {
|
||||||
|
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);
|
||||||
|
|
||||||
|
if (_iconCacheBadgeWidth) {
|
||||||
|
const auto desiredLeft = width() / 2 + _st.badgePosition.x();
|
||||||
|
const auto x = std::min(
|
||||||
|
desiredLeft,
|
||||||
|
width() - _iconCacheBadgeWidth - st::defaultScrollArea.width);
|
||||||
|
const auto y = _st.badgePosition.y();
|
||||||
|
|
||||||
|
auto hq = PainterHighQualityEnabler(p);
|
||||||
|
p.setPen(Qt::NoPen);
|
||||||
|
p.setBrush((_badgeMuted && !_active)
|
||||||
|
? _st.badgeBgMuted
|
||||||
|
: _st.badgeBg);
|
||||||
|
const auto r = _st.badgeHeight / 2;
|
||||||
|
p.drawRoundedRect(x, y, _iconCacheBadgeWidth, _st.badgeHeight, r, r);
|
||||||
|
|
||||||
|
p.setPen(_st.badgeFg);
|
||||||
|
_badge.draw(
|
||||||
|
p,
|
||||||
|
x + (_iconCacheBadgeWidth - _badge.maxWidth()) / 2,
|
||||||
|
y + (_st.badgeHeight - _st.badgeStyle.font->height) / 2,
|
||||||
|
width());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const style::icon &SideBarButton::computeIcon() const {
|
||||||
|
return _active
|
||||||
|
? (_iconOverrideActive
|
||||||
|
? *_iconOverrideActive
|
||||||
|
: !_st.iconActive.empty()
|
||||||
|
? _st.iconActive
|
||||||
|
: _iconOverride
|
||||||
|
? *_iconOverride
|
||||||
|
: _st.icon)
|
||||||
|
: _iconOverride
|
||||||
|
? *_iconOverride
|
||||||
|
: _st.icon;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SideBarButton::validateIconCache() {
|
||||||
|
Expects(_st.iconPosition.x() < 0);
|
||||||
|
Expects(_st.iconPosition.y() >= 0);
|
||||||
|
|
||||||
|
if (!(_active ? _iconCacheActive : _iconCache).isNull()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto &icon = computeIcon();
|
||||||
|
auto image = QImage(
|
||||||
|
icon.size() * style::DevicePixelRatio(),
|
||||||
|
QImage::Format_ARGB32_Premultiplied);
|
||||||
|
image.setDevicePixelRatio(style::DevicePixelRatio());
|
||||||
|
image.fill(Qt::transparent);
|
||||||
|
{
|
||||||
|
auto p = QPainter(&image);
|
||||||
|
icon.paint(p, 0, 0, icon.width());
|
||||||
|
p.setCompositionMode(QPainter::CompositionMode_Source);
|
||||||
|
p.setBrush(Qt::transparent);
|
||||||
|
auto pen = QPen(Qt::transparent);
|
||||||
|
pen.setWidth(2 * _st.badgeStroke);
|
||||||
|
p.setPen(pen);
|
||||||
|
auto hq = PainterHighQualityEnabler(p);
|
||||||
|
const auto desiredLeft = (icon.width() / 2) + _st.badgePosition.x();
|
||||||
|
const auto x = std::min(
|
||||||
|
desiredLeft,
|
||||||
|
(width()
|
||||||
|
- _iconCacheBadgeWidth
|
||||||
|
- st::defaultScrollArea.width
|
||||||
|
- (width() / 2)
|
||||||
|
+ (icon.width() / 2)));
|
||||||
|
const auto y = _st.badgePosition.y() - _st.iconPosition.y();
|
||||||
|
const auto r = _st.badgeHeight / 2.;
|
||||||
|
p.drawRoundedRect(x, y, _iconCacheBadgeWidth, _st.badgeHeight, r, r);
|
||||||
|
}
|
||||||
|
(_active ? _iconCacheActive : _iconCache) = std::move(image);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Ui
|
||||||
115
ui/widgets/side_bar_button.h
Normal file
115
ui/widgets/side_bar_button.h
Normal file
|
|
@ -0,0 +1,115 @@
|
||||||
|
// 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<QWidget*> parent,
|
||||||
|
const QString &title,
|
||||||
|
const style::SideBarButton &st);
|
||||||
|
|
||||||
|
void setActive(bool active);
|
||||||
|
void setBadge(const QString &badge, bool muted);
|
||||||
|
void setIconOverride(
|
||||||
|
const style::icon *iconOverride,
|
||||||
|
const style::icon *iconOverrideActive = nullptr);
|
||||||
|
|
||||||
|
int resizeGetHeight(int newWidth) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void paintEvent(QPaintEvent *e) override;
|
||||||
|
|
||||||
|
[[nodiscard]] const style::icon &computeIcon() const;
|
||||||
|
void validateIconCache();
|
||||||
|
|
||||||
|
const style::SideBarButton &_st;
|
||||||
|
const style::icon *_iconOverride = nullptr;
|
||||||
|
const style::icon *_iconOverrideActive = nullptr;
|
||||||
|
Ui::Text::String _text;
|
||||||
|
Ui::Text::String _badge;
|
||||||
|
QImage _iconCache;
|
||||||
|
QImage _iconCacheActive;
|
||||||
|
int _iconCacheBadgeWidth = 0;
|
||||||
|
bool _active = false;
|
||||||
|
bool _badgeMuted = false;
|
||||||
|
|
||||||
|
};
|
||||||
|
//
|
||||||
|
//class SideBarMenu final {
|
||||||
|
//public:
|
||||||
|
// struct Item {
|
||||||
|
// QString id;
|
||||||
|
// QString title;
|
||||||
|
// QString badge;
|
||||||
|
// not_null<const style::icon*> icon;
|
||||||
|
// not_null<const style::icon*> iconActive;
|
||||||
|
// int iconTop = 0;
|
||||||
|
// };
|
||||||
|
//
|
||||||
|
// SideBarMenu(not_null<QWidget*> parent, const style::SideBarMenu &st);
|
||||||
|
// ~SideBarMenu();
|
||||||
|
//
|
||||||
|
// [[nodiscard]] not_null<const Ui::RpWidget*> widget() const;
|
||||||
|
//
|
||||||
|
// void setGeometry(QRect geometry);
|
||||||
|
// void setItems(std::vector<Item> items);
|
||||||
|
// void setActive(
|
||||||
|
// const QString &id,
|
||||||
|
// anim::type animated = anim::type::normal);
|
||||||
|
// [[nodiscard]] rpl::producer<QString> activateRequests() const;
|
||||||
|
//
|
||||||
|
// [[nodiscard]] rpl::lifetime &lifetime();
|
||||||
|
//
|
||||||
|
//private:
|
||||||
|
// struct MenuItem {
|
||||||
|
// Item data;
|
||||||
|
// Ui::Text::String text;
|
||||||
|
// mutable std::unique_ptr<Ui::RippleAnimation> 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<Ui::ScrollArea*> _scroll;
|
||||||
|
// const not_null<Ui::RpWidget*> _inner;
|
||||||
|
// std::vector<MenuItem> _items;
|
||||||
|
// int _selected = -1;
|
||||||
|
// int _pressed = -1;
|
||||||
|
//
|
||||||
|
// QString _activeId;
|
||||||
|
// rpl::event_stream<QString> _activateRequests;
|
||||||
|
//
|
||||||
|
//};
|
||||||
|
|
||||||
|
} // namespace Ui
|
||||||
|
|
@ -544,6 +544,30 @@ WindowTitle {
|
||||||
closeIconActiveOver: icon;
|
closeIconActiveOver: icon;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SideBarButton {
|
||||||
|
icon: icon;
|
||||||
|
iconActive: icon;
|
||||||
|
iconPosition: point;
|
||||||
|
textTop: pixels;
|
||||||
|
textSkip: pixels;
|
||||||
|
minTextWidth: pixels;
|
||||||
|
minHeight: pixels;
|
||||||
|
style: TextStyle;
|
||||||
|
badgeStyle: TextStyle;
|
||||||
|
badgeSkip: pixels;
|
||||||
|
badgeHeight: pixels;
|
||||||
|
badgeStroke: pixels;
|
||||||
|
badgePosition: point;
|
||||||
|
textBg: color;
|
||||||
|
textBgActive: color;
|
||||||
|
textFg: color;
|
||||||
|
textFgActive: color;
|
||||||
|
badgeBg: color;
|
||||||
|
badgeBgMuted: color;
|
||||||
|
badgeFg: color;
|
||||||
|
ripple: RippleAnimation;
|
||||||
|
}
|
||||||
|
|
||||||
defaultLabelSimple: LabelSimple {
|
defaultLabelSimple: LabelSimple {
|
||||||
font: normalFont;
|
font: normalFont;
|
||||||
maxWidth: 0px;
|
maxWidth: 0px;
|
||||||
|
|
@ -884,6 +908,26 @@ defaultRoundCheckbox: RoundCheckbox {
|
||||||
bgDuration: 0.75;
|
bgDuration: 0.75;
|
||||||
fgDuration: 1.;
|
fgDuration: 1.;
|
||||||
}
|
}
|
||||||
|
defaultPeerListCheckIcon: icon {{
|
||||||
|
"default_checkbox_check",
|
||||||
|
overviewCheckFgActive,
|
||||||
|
point(3px, 6px)
|
||||||
|
}};
|
||||||
|
defaultPeerListCheck: RoundCheckbox(defaultRoundCheckbox) {
|
||||||
|
size: 20px;
|
||||||
|
sizeSmall: 0.3;
|
||||||
|
bgInactive: overviewCheckBg;
|
||||||
|
bgActive: overviewCheckBgActive;
|
||||||
|
check: defaultPeerListCheckIcon;
|
||||||
|
}
|
||||||
|
defaultPeerListCheckbox: RoundImageCheckbox {
|
||||||
|
imageRadius: 21px;
|
||||||
|
imageSmallRadius: 18px;
|
||||||
|
selectWidth: 2px;
|
||||||
|
selectFg: windowBgActive;
|
||||||
|
selectDuration: 150;
|
||||||
|
check: defaultPeerListCheck;
|
||||||
|
}
|
||||||
|
|
||||||
defaultMenuArrow: icon {{ "dropdown_submenu_arrow", menuSubmenuArrowFg }};
|
defaultMenuArrow: icon {{ "dropdown_submenu_arrow", menuSubmenuArrowFg }};
|
||||||
defaultMenuToggle: Toggle(defaultToggle) {
|
defaultMenuToggle: Toggle(defaultToggle) {
|
||||||
|
|
@ -1061,6 +1105,7 @@ PeerListItem {
|
||||||
maximalWidth: pixels;
|
maximalWidth: pixels;
|
||||||
|
|
||||||
button: OutlineButton;
|
button: OutlineButton;
|
||||||
|
checkbox: RoundImageCheckbox;
|
||||||
statusFg: color;
|
statusFg: color;
|
||||||
statusFgOver: color;
|
statusFgOver: color;
|
||||||
statusFgActive: color;
|
statusFgActive: color;
|
||||||
|
|
@ -1096,6 +1141,7 @@ defaultPeerListItem: PeerListItem {
|
||||||
statusPosition: point(68px, 31px);
|
statusPosition: point(68px, 31px);
|
||||||
photoSize: 46px;
|
photoSize: 46px;
|
||||||
button: defaultPeerListButton;
|
button: defaultPeerListButton;
|
||||||
|
checkbox: defaultPeerListCheckbox;
|
||||||
statusFg: windowSubTextFg;
|
statusFg: windowSubTextFg;
|
||||||
statusFgOver: windowSubTextFgOver;
|
statusFgOver: windowSubTextFgOver;
|
||||||
statusFgActive: windowActiveTextFg;
|
statusFgActive: windowActiveTextFg;
|
||||||
|
|
@ -1207,6 +1253,19 @@ defaultSettingsButton: SettingsButton {
|
||||||
ripple: defaultRippleAnimation;
|
ripple: defaultRippleAnimation;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
defaultSideBarButton: SideBarButton {
|
||||||
|
textBg: sideBarBg;
|
||||||
|
textBgActive: sideBarBgActive;
|
||||||
|
textFg: sideBarTextFg;
|
||||||
|
textFgActive: sideBarTextFgActive;
|
||||||
|
badgeBg: sideBarBadgeBg;
|
||||||
|
badgeBgMuted: sideBarBadgeBgMuted;
|
||||||
|
badgeFg: sideBarBadgeFg;
|
||||||
|
ripple: RippleAnimation(defaultRippleAnimation) {
|
||||||
|
color: sideBarBgRipple;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Windows specific title
|
// Windows specific title
|
||||||
|
|
||||||
windowTitleButton: IconButton {
|
windowTitleButton: IconButton {
|
||||||
|
|
|
||||||
|
|
@ -48,6 +48,26 @@ int VerticalLayout::naturalWidth() const {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void VerticalLayout::setVerticalShift(int index, int shift) {
|
||||||
|
Expects(index >= 0 && index < _rows.size());
|
||||||
|
|
||||||
|
auto &row = _rows[index];
|
||||||
|
if (const auto delta = shift - row.verticalShift) {
|
||||||
|
row.verticalShift = shift;
|
||||||
|
row.widget->move(row.widget->x(), row.widget->y() + delta);
|
||||||
|
row.widget->update();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void VerticalLayout::reorderRows(int oldIndex, int newIndex) {
|
||||||
|
Expects(oldIndex >= 0 && oldIndex < _rows.size());
|
||||||
|
Expects(newIndex >= 0 && newIndex < _rows.size());
|
||||||
|
Expects(!_inResize);
|
||||||
|
|
||||||
|
base::reorder(_rows, oldIndex, newIndex);
|
||||||
|
resizeToWidth(width());
|
||||||
|
}
|
||||||
|
|
||||||
int VerticalLayout::resizeGetHeight(int newWidth) {
|
int VerticalLayout::resizeGetHeight(int newWidth) {
|
||||||
_inResize = true;
|
_inResize = true;
|
||||||
auto guard = gsl::finally([&] { _inResize = false; });
|
auto guard = gsl::finally([&] { _inResize = false; });
|
||||||
|
|
@ -60,7 +80,7 @@ int VerticalLayout::resizeGetHeight(int newWidth) {
|
||||||
row.widget,
|
row.widget,
|
||||||
row.margin,
|
row.margin,
|
||||||
newWidth,
|
newWidth,
|
||||||
result);
|
result + row.verticalShift);
|
||||||
result += row.margin.top()
|
result += row.margin.top()
|
||||||
+ row.widget->heightNoMargins()
|
+ row.widget->heightNoMargins()
|
||||||
+ row.margin.bottom();
|
+ row.margin.bottom();
|
||||||
|
|
@ -100,18 +120,12 @@ RpWidget *VerticalLayout::insertChild(
|
||||||
object_ptr<RpWidget> child,
|
object_ptr<RpWidget> child,
|
||||||
const style::margins &margin) {
|
const style::margins &margin) {
|
||||||
Expects(atPosition >= 0 && atPosition <= _rows.size());
|
Expects(atPosition >= 0 && atPosition <= _rows.size());
|
||||||
|
Expects(!_inResize);
|
||||||
|
|
||||||
if (const auto weak = AttachParentChild(this, child)) {
|
if (const auto weak = AttachParentChild(this, child)) {
|
||||||
_rows.insert(
|
_rows.insert(
|
||||||
begin(_rows) + atPosition,
|
begin(_rows) + atPosition,
|
||||||
{ std::move(child), margin });
|
{ std::move(child), margin });
|
||||||
const auto margins = getMargins();
|
|
||||||
updateChildGeometry(
|
|
||||||
margins,
|
|
||||||
weak,
|
|
||||||
margin,
|
|
||||||
width() - margins.left() - margins.right(),
|
|
||||||
height() - margins.top() - margins.bottom());
|
|
||||||
weak->heightValue(
|
weak->heightValue(
|
||||||
) | rpl::start_with_next_done([=] {
|
) | rpl::start_with_next_done([=] {
|
||||||
if (!_inResize) {
|
if (!_inResize) {
|
||||||
|
|
|
||||||
|
|
@ -15,9 +15,14 @@ class VerticalLayout : public RpWidget {
|
||||||
public:
|
public:
|
||||||
using RpWidget::RpWidget;
|
using RpWidget::RpWidget;
|
||||||
|
|
||||||
int count() const {
|
[[nodiscard]] int count() const {
|
||||||
return _rows.size();
|
return _rows.size();
|
||||||
}
|
}
|
||||||
|
[[nodiscard]] not_null<RpWidget*> widgetAt(int index) const {
|
||||||
|
Expects(index >= 0 && index < count());
|
||||||
|
|
||||||
|
return _rows[index].widget.data();
|
||||||
|
}
|
||||||
|
|
||||||
template <
|
template <
|
||||||
typename Widget,
|
typename Widget,
|
||||||
|
|
@ -46,6 +51,9 @@ public:
|
||||||
QMargins getMargins() const override;
|
QMargins getMargins() const override;
|
||||||
int naturalWidth() const override;
|
int naturalWidth() const override;
|
||||||
|
|
||||||
|
void setVerticalShift(int index, int shift);
|
||||||
|
void reorderRows(int oldIndex, int newIndex);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
int resizeGetHeight(int newWidth) override;
|
int resizeGetHeight(int newWidth) override;
|
||||||
void visibleTopBottomUpdated(
|
void visibleTopBottomUpdated(
|
||||||
|
|
@ -69,6 +77,7 @@ private:
|
||||||
struct Row {
|
struct Row {
|
||||||
object_ptr<RpWidget> widget;
|
object_ptr<RpWidget> widget;
|
||||||
style::margins margin;
|
style::margins margin;
|
||||||
|
int verticalShift = 0;
|
||||||
};
|
};
|
||||||
std::vector<Row> _rows;
|
std::vector<Row> _rows;
|
||||||
bool _inResize = false;
|
bool _inResize = false;
|
||||||
|
|
|
||||||
270
ui/wrap/vertical_layout_reorder.cpp
Normal file
270
ui/wrap/vertical_layout_reorder.cpp
Normal file
|
|
@ -0,0 +1,270 @@
|
||||||
|
// 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/wrap/vertical_layout_reorder.h"
|
||||||
|
|
||||||
|
#include "ui/wrap/vertical_layout.h"
|
||||||
|
#include "styles/style_basic.h"
|
||||||
|
|
||||||
|
#include <QtGui/QtEvents>
|
||||||
|
#include <QtWidgets/QApplication>
|
||||||
|
|
||||||
|
namespace Ui {
|
||||||
|
|
||||||
|
VerticalLayoutReorder::VerticalLayoutReorder(
|
||||||
|
not_null<VerticalLayout*> layout)
|
||||||
|
: _layout(layout) {
|
||||||
|
}
|
||||||
|
|
||||||
|
void VerticalLayoutReorder::cancel() {
|
||||||
|
if (_currentWidget) {
|
||||||
|
cancelCurrent(indexOf(_currentWidget));
|
||||||
|
}
|
||||||
|
_lifetime.destroy();
|
||||||
|
for (auto i = 0, count = _layout->count(); i != count; ++i) {
|
||||||
|
_layout->setVerticalShift(i, 0);
|
||||||
|
}
|
||||||
|
_entries.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
void VerticalLayoutReorder::start() {
|
||||||
|
const auto count = _layout->count();
|
||||||
|
if (count < 2) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (auto i = 0; i != count; ++i) {
|
||||||
|
const auto widget = _layout->widgetAt(i);
|
||||||
|
widget->events(
|
||||||
|
) | rpl::start_with_next_done([=](not_null<QEvent*> e) {
|
||||||
|
switch (e->type()) {
|
||||||
|
case QEvent::MouseMove:
|
||||||
|
mouseMove(
|
||||||
|
widget,
|
||||||
|
static_cast<QMouseEvent*>(e.get())->globalPos());
|
||||||
|
break;
|
||||||
|
case QEvent::MouseButtonPress:
|
||||||
|
mousePress(
|
||||||
|
widget,
|
||||||
|
static_cast<QMouseEvent*>(e.get())->button(),
|
||||||
|
static_cast<QMouseEvent*>(e.get())->globalPos());
|
||||||
|
break;
|
||||||
|
case QEvent::MouseButtonRelease:
|
||||||
|
mouseRelease(
|
||||||
|
widget,
|
||||||
|
static_cast<QMouseEvent*>(e.get())->button());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}, [=] {
|
||||||
|
cancel();
|
||||||
|
}, _lifetime);
|
||||||
|
_entries.push_back({ widget });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void VerticalLayoutReorder::mouseMove(
|
||||||
|
not_null<RpWidget*> widget,
|
||||||
|
QPoint position) {
|
||||||
|
if (_currentWidget != widget) {
|
||||||
|
return;
|
||||||
|
} else if (_currentState != State::Started) {
|
||||||
|
checkForStart(position);
|
||||||
|
} else {
|
||||||
|
updateOrder(indexOf(_currentWidget), position);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void VerticalLayoutReorder::checkForStart(QPoint position) {
|
||||||
|
const auto shift = position.y() - _currentStart;
|
||||||
|
const auto delta = QApplication::startDragDistance();
|
||||||
|
if (std::abs(shift) <= delta) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_currentWidget->raise();
|
||||||
|
_currentState = State::Started;
|
||||||
|
_currentStart += (shift > 0) ? delta : -delta;
|
||||||
|
|
||||||
|
const auto index = indexOf(_currentWidget);
|
||||||
|
_currentDesiredIndex = index;
|
||||||
|
_updates.fire({ _currentWidget, index, index, _currentState });
|
||||||
|
|
||||||
|
updateOrder(index, position);
|
||||||
|
}
|
||||||
|
|
||||||
|
void VerticalLayoutReorder::updateOrder(int index, QPoint position) {
|
||||||
|
const auto shift = position.y() - _currentStart;
|
||||||
|
auto ¤t = _entries[index];
|
||||||
|
current.shiftAnimation.stop();
|
||||||
|
current.shift = current.finalShift = shift;
|
||||||
|
_layout->setVerticalShift(index, shift);
|
||||||
|
|
||||||
|
const auto count = _entries.size();
|
||||||
|
const auto currentHeight = current.widget->height();
|
||||||
|
const auto currentMiddle = current.widget->y() + currentHeight / 2;
|
||||||
|
_currentDesiredIndex = index;
|
||||||
|
if (shift > 0) {
|
||||||
|
auto top = current.widget->y() - shift;
|
||||||
|
for (auto next = index + 1; next != count; ++next) {
|
||||||
|
const auto &entry = _entries[next];
|
||||||
|
top += entry.widget->height();
|
||||||
|
if (currentMiddle < top) {
|
||||||
|
moveToShift(next, 0);
|
||||||
|
} else {
|
||||||
|
_currentDesiredIndex = next;
|
||||||
|
moveToShift(next, -currentHeight);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (auto prev = index - 1; prev >= 0; --prev) {
|
||||||
|
moveToShift(prev, 0);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (auto next = index + 1; next != count; ++next) {
|
||||||
|
moveToShift(next, 0);
|
||||||
|
}
|
||||||
|
for (auto prev = index - 1; prev >= 0; --prev) {
|
||||||
|
const auto &entry = _entries[prev];
|
||||||
|
if (currentMiddle >= entry.widget->y() - entry.shift + currentHeight) {
|
||||||
|
moveToShift(prev, 0);
|
||||||
|
} else {
|
||||||
|
_currentDesiredIndex = prev;
|
||||||
|
moveToShift(prev, currentHeight);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void VerticalLayoutReorder::mousePress(
|
||||||
|
not_null<RpWidget*> widget,
|
||||||
|
Qt::MouseButton button,
|
||||||
|
QPoint position) {
|
||||||
|
if (button != Qt::LeftButton) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
cancelCurrent();
|
||||||
|
_currentWidget = widget;
|
||||||
|
_currentStart = position.y();
|
||||||
|
}
|
||||||
|
|
||||||
|
void VerticalLayoutReorder::mouseRelease(
|
||||||
|
not_null<RpWidget*> widget,
|
||||||
|
Qt::MouseButton button) {
|
||||||
|
if (button != Qt::LeftButton) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
finishCurrent();
|
||||||
|
}
|
||||||
|
|
||||||
|
void VerticalLayoutReorder::cancelCurrent() {
|
||||||
|
if (_currentWidget) {
|
||||||
|
cancelCurrent(indexOf(_currentWidget));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void VerticalLayoutReorder::cancelCurrent(int index) {
|
||||||
|
Expects(_currentWidget != nullptr);
|
||||||
|
|
||||||
|
if (_currentState == State::Started) {
|
||||||
|
_currentState = State::Cancelled;
|
||||||
|
_updates.fire({ _currentWidget, index, index, _currentState });
|
||||||
|
}
|
||||||
|
_currentWidget = nullptr;
|
||||||
|
for (auto i = 0, count = int(_entries.size()); i != count; ++i) {
|
||||||
|
moveToShift(i, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void VerticalLayoutReorder::finishCurrent() {
|
||||||
|
if (!_currentWidget) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto index = indexOf(_currentWidget);
|
||||||
|
if (_currentDesiredIndex == index || _currentState != State::Started) {
|
||||||
|
cancelCurrent(index);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto result = _currentDesiredIndex;
|
||||||
|
const auto widget = _currentWidget;
|
||||||
|
_currentState = State::Cancelled;
|
||||||
|
_currentWidget = nullptr;
|
||||||
|
|
||||||
|
auto ¤t = _entries[index];
|
||||||
|
const auto height = current.widget->height();
|
||||||
|
if (index < result) {
|
||||||
|
auto sum = 0;
|
||||||
|
for (auto i = index; i != result; ++i) {
|
||||||
|
auto &entry = _entries[i + 1];
|
||||||
|
const auto widget = entry.widget;
|
||||||
|
entry.deltaShift += height;
|
||||||
|
updateShift(widget, i + 1);
|
||||||
|
sum += widget->height();
|
||||||
|
}
|
||||||
|
current.finalShift -= sum;
|
||||||
|
} else if (index > result) {
|
||||||
|
auto sum = 0;
|
||||||
|
for (auto i = result; i != index; ++i) {
|
||||||
|
auto &entry = _entries[i];
|
||||||
|
const auto widget = entry.widget;
|
||||||
|
entry.deltaShift -= height;
|
||||||
|
updateShift(widget, i);
|
||||||
|
sum += widget->height();
|
||||||
|
}
|
||||||
|
current.finalShift += sum;
|
||||||
|
}
|
||||||
|
if (!(current.finalShift + current.deltaShift)) {
|
||||||
|
current.shift = 0;
|
||||||
|
_layout->setVerticalShift(index, 0);
|
||||||
|
}
|
||||||
|
base::reorder(_entries, index, result);
|
||||||
|
_layout->reorderRows(index, _currentDesiredIndex);
|
||||||
|
for (auto i = 0, count = int(_entries.size()); i != count; ++i) {
|
||||||
|
moveToShift(i, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
_updates.fire({ widget, index, result, State::Applied });
|
||||||
|
}
|
||||||
|
|
||||||
|
void VerticalLayoutReorder::moveToShift(int index, int shift) {
|
||||||
|
auto &entry = _entries[index];
|
||||||
|
if (entry.finalShift + entry.deltaShift == shift) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto widget = entry.widget;
|
||||||
|
entry.shiftAnimation.start(
|
||||||
|
[=] { updateShift(widget, index); },
|
||||||
|
entry.finalShift,
|
||||||
|
shift - entry.deltaShift,
|
||||||
|
st::slideWrapDuration);
|
||||||
|
entry.finalShift = shift - entry.deltaShift;
|
||||||
|
}
|
||||||
|
|
||||||
|
void VerticalLayoutReorder::updateShift(
|
||||||
|
not_null<RpWidget*> widget,
|
||||||
|
int indexHint) {
|
||||||
|
Expects(indexHint >= 0 && indexHint < _entries.size());
|
||||||
|
|
||||||
|
const auto index = (_entries[indexHint].widget == widget)
|
||||||
|
? indexHint
|
||||||
|
: indexOf(widget);
|
||||||
|
auto &entry = _entries[index];
|
||||||
|
entry.shift = std::round(entry.shiftAnimation.value(entry.finalShift))
|
||||||
|
+ entry.deltaShift;
|
||||||
|
if (entry.deltaShift && !entry.shiftAnimation.animating()) {
|
||||||
|
entry.finalShift += entry.deltaShift;
|
||||||
|
entry.deltaShift = 0;
|
||||||
|
}
|
||||||
|
_layout->setVerticalShift(index, entry.shift);
|
||||||
|
}
|
||||||
|
|
||||||
|
int VerticalLayoutReorder::indexOf(not_null<RpWidget*> widget) const {
|
||||||
|
const auto i = ranges::find(_entries, widget, &Entry::widget);
|
||||||
|
Assert(i != end(_entries));
|
||||||
|
return i - begin(_entries);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto VerticalLayoutReorder::updates() const -> rpl::producer<Single> {
|
||||||
|
return _updates.events();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Ui
|
||||||
74
ui/wrap/vertical_layout_reorder.h
Normal file
74
ui/wrap/vertical_layout_reorder.h
Normal file
|
|
@ -0,0 +1,74 @@
|
||||||
|
// 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/effects/animations.h"
|
||||||
|
|
||||||
|
namespace Ui {
|
||||||
|
|
||||||
|
class RpWidget;
|
||||||
|
class VerticalLayout;
|
||||||
|
|
||||||
|
class VerticalLayoutReorder final {
|
||||||
|
public:
|
||||||
|
enum class State : uchar {
|
||||||
|
Started,
|
||||||
|
Applied,
|
||||||
|
Cancelled,
|
||||||
|
};
|
||||||
|
struct Single {
|
||||||
|
not_null<RpWidget*> widget;
|
||||||
|
int oldPosition = 0;
|
||||||
|
int newPosition = 0;
|
||||||
|
State state = State::Started;
|
||||||
|
};
|
||||||
|
|
||||||
|
VerticalLayoutReorder(not_null<VerticalLayout*> layout);
|
||||||
|
|
||||||
|
void start();
|
||||||
|
void cancel();
|
||||||
|
[[nodiscard]] rpl::producer<Single> updates() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct Entry {
|
||||||
|
not_null<RpWidget*> widget;
|
||||||
|
Ui::Animations::Simple shiftAnimation;
|
||||||
|
int shift = 0;
|
||||||
|
int finalShift = 0;
|
||||||
|
int deltaShift = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
void mouseMove(not_null<RpWidget*> widget, QPoint position);
|
||||||
|
void mousePress(
|
||||||
|
not_null<RpWidget*> widget,
|
||||||
|
Qt::MouseButton button,
|
||||||
|
QPoint position);
|
||||||
|
void mouseRelease(not_null<RpWidget*> widget, Qt::MouseButton button);
|
||||||
|
|
||||||
|
void checkForStart(QPoint position);
|
||||||
|
void updateOrder(int index, QPoint position);
|
||||||
|
void cancelCurrent();
|
||||||
|
void finishCurrent();
|
||||||
|
void cancelCurrent(int index);
|
||||||
|
|
||||||
|
[[nodiscard]] int indexOf(not_null<RpWidget*> widget) const;
|
||||||
|
void moveToShift(int index, int shift);
|
||||||
|
void updateShift(not_null<RpWidget*> widget, int indexHint);
|
||||||
|
|
||||||
|
const not_null<Ui::VerticalLayout*> _layout;
|
||||||
|
|
||||||
|
RpWidget *_currentWidget = nullptr;
|
||||||
|
int _currentStart = 0;
|
||||||
|
int _currentDesiredIndex = 0;
|
||||||
|
State _currentState = State::Cancelled;
|
||||||
|
std::vector<Entry> _entries;
|
||||||
|
rpl::event_stream<Single> _updates;
|
||||||
|
rpl::lifetime _lifetime;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Ui
|
||||||
Loading…
Add table
Reference in a new issue