Merge remote-tracking branch 'upstream/master'

This commit is contained in:
Eric Kotato 2020-03-28 04:54:28 +03:00
commit 659d46b693
13 changed files with 764 additions and 10 deletions

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -77,6 +77,8 @@ public:
}
}
[[nodiscard]] not_null<Ui::VerticalLayout*> verticalLayout();
using BoxContent::setNoContentMargin;
protected:

View file

@ -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();
}
}

View file

@ -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];

View 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

View 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

View file

@ -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 {

View file

@ -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) {

View file

@ -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;

View 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 &current = _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 &current = _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

View 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