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/scroll_area.cpp
|
||||
ui/widgets/scroll_area.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
|
||||
|
|
@ -149,6 +151,8 @@ PRIVATE
|
|||
ui/wrap/slide_wrap.h
|
||||
ui/wrap/vertical_layout.cpp
|
||||
ui/wrap/vertical_layout.h
|
||||
ui/wrap/vertical_layout_reorder.cpp
|
||||
ui/wrap/vertical_layout_reorder.h
|
||||
ui/wrap/wrap.h
|
||||
ui/abstract_button.cpp
|
||||
ui/abstract_button.h
|
||||
|
|
|
|||
|
|
@ -592,3 +592,14 @@ walletSubBalanceFg: #f9f9f9; // wallet balance label text
|
|||
walletTopLabelFg: #999999; // wallet top updated label text
|
||||
walletTopIconFg: walletTopLabelFg; // wallet top refresh and menu icons
|
||||
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));
|
||||
}
|
||||
|
||||
not_null<Ui::VerticalLayout*> GenericBox::verticalLayout() {
|
||||
return _content.data();
|
||||
}
|
||||
|
||||
} // namespace Ui
|
||||
|
|
|
|||
|
|
@ -77,6 +77,8 @@ public:
|
|||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] not_null<Ui::VerticalLayout*> verticalLayout();
|
||||
|
||||
using BoxContent::setNoContentMargin;
|
||||
|
||||
protected:
|
||||
|
|
|
|||
|
|
@ -633,12 +633,21 @@ void LayerStackWidget::prepareForAnimation() {
|
|||
show();
|
||||
}
|
||||
if (_mainMenu) {
|
||||
if (Ui::InFocusChain(_mainMenu)) {
|
||||
setFocus();
|
||||
}
|
||||
_mainMenu->hide();
|
||||
}
|
||||
if (_specialLayer) {
|
||||
if (Ui::InFocusChain(_specialLayer)) {
|
||||
setFocus();
|
||||
}
|
||||
_specialLayer->hide();
|
||||
}
|
||||
if (const auto layer = currentLayer()) {
|
||||
if (Ui::InFocusChain(layer)) {
|
||||
setFocus();
|
||||
}
|
||||
layer->hide();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ void InitOnTopPanel(not_null<QWidget*> panel) {
|
|||
Assert([platformWindow isKindOfClass:[NSPanel class]]);
|
||||
|
||||
auto platformPanel = static_cast<NSPanel*>(platformWindow);
|
||||
[platformPanel setLevel:NSPopUpMenuWindowLevel];
|
||||
[platformPanel setLevel:NSModalPanelWindowLevel];
|
||||
[platformPanel setCollectionBehavior:NSWindowCollectionBehaviorCanJoinAllSpaces|NSWindowCollectionBehaviorStationary|NSWindowCollectionBehaviorFullScreenAuxiliary|NSWindowCollectionBehaviorIgnoresCycle];
|
||||
[platformPanel setHidesOnDeactivate:NO];
|
||||
//[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;
|
||||
}
|
||||
|
||||
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 {
|
||||
font: normalFont;
|
||||
maxWidth: 0px;
|
||||
|
|
@ -884,6 +908,26 @@ defaultRoundCheckbox: RoundCheckbox {
|
|||
bgDuration: 0.75;
|
||||
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 }};
|
||||
defaultMenuToggle: Toggle(defaultToggle) {
|
||||
|
|
@ -1061,6 +1105,7 @@ PeerListItem {
|
|||
maximalWidth: pixels;
|
||||
|
||||
button: OutlineButton;
|
||||
checkbox: RoundImageCheckbox;
|
||||
statusFg: color;
|
||||
statusFgOver: color;
|
||||
statusFgActive: color;
|
||||
|
|
@ -1096,6 +1141,7 @@ defaultPeerListItem: PeerListItem {
|
|||
statusPosition: point(68px, 31px);
|
||||
photoSize: 46px;
|
||||
button: defaultPeerListButton;
|
||||
checkbox: defaultPeerListCheckbox;
|
||||
statusFg: windowSubTextFg;
|
||||
statusFgOver: windowSubTextFgOver;
|
||||
statusFgActive: windowActiveTextFg;
|
||||
|
|
@ -1207,6 +1253,19 @@ defaultSettingsButton: SettingsButton {
|
|||
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
|
||||
|
||||
windowTitleButton: IconButton {
|
||||
|
|
|
|||
|
|
@ -48,6 +48,26 @@ int VerticalLayout::naturalWidth() const {
|
|||
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) {
|
||||
_inResize = true;
|
||||
auto guard = gsl::finally([&] { _inResize = false; });
|
||||
|
|
@ -60,7 +80,7 @@ int VerticalLayout::resizeGetHeight(int newWidth) {
|
|||
row.widget,
|
||||
row.margin,
|
||||
newWidth,
|
||||
result);
|
||||
result + row.verticalShift);
|
||||
result += row.margin.top()
|
||||
+ row.widget->heightNoMargins()
|
||||
+ row.margin.bottom();
|
||||
|
|
@ -100,18 +120,12 @@ RpWidget *VerticalLayout::insertChild(
|
|||
object_ptr<RpWidget> child,
|
||||
const style::margins &margin) {
|
||||
Expects(atPosition >= 0 && atPosition <= _rows.size());
|
||||
Expects(!_inResize);
|
||||
|
||||
if (const auto weak = AttachParentChild(this, child)) {
|
||||
_rows.insert(
|
||||
begin(_rows) + atPosition,
|
||||
{ std::move(child), margin });
|
||||
const auto margins = getMargins();
|
||||
updateChildGeometry(
|
||||
margins,
|
||||
weak,
|
||||
margin,
|
||||
width() - margins.left() - margins.right(),
|
||||
height() - margins.top() - margins.bottom());
|
||||
weak->heightValue(
|
||||
) | rpl::start_with_next_done([=] {
|
||||
if (!_inResize) {
|
||||
|
|
|
|||
|
|
@ -15,9 +15,14 @@ class VerticalLayout : public RpWidget {
|
|||
public:
|
||||
using RpWidget::RpWidget;
|
||||
|
||||
int count() const {
|
||||
[[nodiscard]] int count() const {
|
||||
return _rows.size();
|
||||
}
|
||||
[[nodiscard]] not_null<RpWidget*> widgetAt(int index) const {
|
||||
Expects(index >= 0 && index < count());
|
||||
|
||||
return _rows[index].widget.data();
|
||||
}
|
||||
|
||||
template <
|
||||
typename Widget,
|
||||
|
|
@ -46,6 +51,9 @@ public:
|
|||
QMargins getMargins() const override;
|
||||
int naturalWidth() const override;
|
||||
|
||||
void setVerticalShift(int index, int shift);
|
||||
void reorderRows(int oldIndex, int newIndex);
|
||||
|
||||
protected:
|
||||
int resizeGetHeight(int newWidth) override;
|
||||
void visibleTopBottomUpdated(
|
||||
|
|
@ -69,6 +77,7 @@ private:
|
|||
struct Row {
|
||||
object_ptr<RpWidget> widget;
|
||||
style::margins margin;
|
||||
int verticalShift = 0;
|
||||
};
|
||||
std::vector<Row> _rows;
|
||||
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