init
Some checks failed
Docker. / Ubuntu (push) Has been cancelled
User-agent updater. / User-agent (push) Failing after 15s
Lock Threads / lock (push) Failing after 10s
Waiting for answer. / waiting-for-answer (push) Failing after 22s
Needs user action. / needs-user-action (push) Failing after 8s
Can't reproduce. / cant-reproduce (push) Failing after 8s
Close stale issues and PRs / stale (push) Has been cancelled

This commit is contained in:
allhaileris
2026-02-16 15:50:16 +03:00
commit afb81b8278
13816 changed files with 3689732 additions and 0 deletions

View File

@@ -0,0 +1,120 @@
// 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/fade_wrap.h"
#include "ui/widgets/shadow.h"
#include "ui/painter.h"
#include "styles/palette.h"
namespace Ui {
FadeWrap<RpWidget>::FadeWrap(
QWidget *parent,
object_ptr<RpWidget> &&child,
float64 scale)
: Parent(parent, std::move(child))
, _animation(this, scale)
, _duration(st::fadeWrapDuration) {
if (auto weak = wrapped()) {
weak->show();
}
}
FadeWrap<RpWidget> *FadeWrap<RpWidget>::setDuration(int duration) {
_duration = duration;
return this;
}
FadeWrap<RpWidget> *FadeWrap<RpWidget>::toggle(
bool shown,
anim::type animated) {
auto changed = (shown != _animation.visible());
if (!_duration) {
animated = anim::type::instant;
}
if (shown) {
if (animated == anim::type::normal) {
if (!_animation.animating()) {
wrapped()->show();
}
_animation.fadeIn(_duration);
if (_animation.animating()) {
wrapped()->hide();
}
} else {
_animation.show();
if (!_animation.animating()) {
wrapped()->show();
}
}
} else {
if (animated == anim::type::normal) {
if (!_animation.animating()) {
wrapped()->show();
}
_animation.fadeOut(_duration);
if (_animation.animating()) {
wrapped()->hide();
}
} else {
_animation.hide();
if (!_animation.animating()) {
wrapped()->show();
}
}
}
if (changed) {
_toggledChanged.fire_copy(shown);
}
return this;
}
FadeWrap<RpWidget> *FadeWrap<RpWidget>::finishAnimating() {
_animation.finish();
wrapped()->show();
return this;
}
FadeWrap<RpWidget> *FadeWrap<RpWidget>::toggleOn(
rpl::producer<bool> &&shown) {
std::move(
shown
) | rpl::on_next([this](bool shown) {
toggle(shown, anim::type::normal);
}, lifetime());
finishAnimating();
return this;
}
void FadeWrap<RpWidget>::paintEvent(QPaintEvent *e) {
Painter p(this);
if (_animation.paint(p)) {
if (!_animation.animating() && _animation.visible()) {
crl::on_main(this, [=] {
if (!_animation.animating() && _animation.visible()) {
wrapped()->show();
}
});
}
return;
}
if (!_animation.animating()) {
wrapped()->show();
}
}
FadeShadow::FadeShadow(QWidget *parent)
: FadeShadow(parent, st::shadowFg) {
}
FadeShadow::FadeShadow(QWidget *parent, style::color color)
: Parent(parent, object_ptr<PlainShadow>(parent, color)) {
hide(anim::type::instant);
}
} // namespace Ui

View File

@@ -0,0 +1,122 @@
// 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/wrap/wrap.h"
#include "ui/effects/fade_animation.h"
namespace Ui {
template <typename Widget = RpWidget>
class FadeWrapScaled;
template <typename Widget = RpWidget>
class FadeWrap;
template <>
class FadeWrap<RpWidget> : public Wrap<RpWidget> {
using Parent = Wrap<RpWidget>;
public:
FadeWrap(
QWidget *parent,
object_ptr<RpWidget> &&child,
float64 scale = 1.);
FadeWrap *setDuration(int duration);
FadeWrap *toggle(bool shown, anim::type animated);
FadeWrap *show(anim::type animated) {
return toggle(true, animated);
}
FadeWrap *hide(anim::type animated) {
return toggle(false, animated);
}
FadeWrap *finishAnimating();
FadeWrap *toggleOn(rpl::producer<bool> &&shown);
bool animating() const {
return _animation.animating();
}
bool toggled() const {
return _animation.visible();
}
auto toggledValue() const {
return _toggledChanged.events_starting_with(
_animation.visible());
}
protected:
void paintEvent(QPaintEvent *e) final override;
private:
rpl::event_stream<bool> _toggledChanged;
FadeAnimation _animation;
int _duration = 0;
};
template <typename Widget>
class FadeWrap : public Wrap<Widget, FadeWrap<RpWidget>> {
using Parent = Wrap<Widget, FadeWrap<RpWidget>>;
public:
FadeWrap(
QWidget *parent,
object_ptr<Widget> &&child,
float64 scale = 1.)
: Parent(parent, std::move(child), scale) {
}
FadeWrap *setDuration(int duration) {
return chain(Parent::setDuration(duration));
}
FadeWrap *toggle(bool shown, anim::type animated) {
return chain(Parent::toggle(shown, animated));
}
FadeWrap *show(anim::type animated) {
return chain(Parent::show(animated));
}
FadeWrap *hide(anim::type animated) {
return chain(Parent::hide(animated));
}
FadeWrap *finishAnimating() {
return chain(Parent::finishAnimating());
}
FadeWrap *toggleOn(rpl::producer<bool> &&shown) {
return chain(Parent::toggleOn(std::move(shown)));
}
private:
FadeWrap *chain(FadeWrap<RpWidget> *result) {
return static_cast<FadeWrap*>(result);
}
};
template <typename Widget>
class FadeWrapScaled : public FadeWrap<Widget> {
using Parent = FadeWrap<Widget>;
public:
FadeWrapScaled(QWidget *parent, object_ptr<Widget> &&child)
: Parent(parent, std::move(child), 0.) {
}
};
class PlainShadow;
class FadeShadow : public FadeWrap<PlainShadow> {
using Parent = FadeWrap<PlainShadow>;
public:
FadeShadow(QWidget *parent);
FadeShadow(QWidget *parent, style::color color);
};
} // namespace Ui

View File

@@ -0,0 +1,46 @@
// 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/follow_slide_wrap.h"
#include "styles/style_basic.h"
namespace Ui {
FollowSlideWrap<RpWidget>::FollowSlideWrap(
QWidget *parent,
object_ptr<RpWidget> &&child)
: Parent(parent, std::move(child))
, _duration(st::slideWrapDuration) {
if (const auto weak = wrapped()) {
wrappedSizeUpdated(weak->size());
wrappedNaturalWidthUpdated(weak->naturalWidth());
}
}
FollowSlideWrap<RpWidget> *FollowSlideWrap<RpWidget>::setDuration(
crl::time duration) {
_duration = duration;
return this;
}
void FollowSlideWrap<RpWidget>::wrappedSizeUpdated(QSize size) {
updateWrappedPosition(size.height());
}
void FollowSlideWrap<RpWidget>::wrappedNaturalWidthUpdated(int width) {
setNaturalWidth(width);
}
void FollowSlideWrap<RpWidget>::updateWrappedPosition(int forHeight) {
_animation.stop();
_animation.start([=](float64 value) {
resize(width(), value);
}, height(), forHeight, _duration);
}
} // namespace Ui

View File

@@ -0,0 +1,54 @@
// 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"
#include "ui/wrap/wrap.h"
namespace Ui {
template <typename Widget = RpWidget>
class FollowSlideWrap;
template <>
class FollowSlideWrap<RpWidget> : public Wrap<RpWidget> {
using Parent = Wrap<RpWidget>;
public:
FollowSlideWrap(
QWidget *parent,
object_ptr<RpWidget> &&child);
FollowSlideWrap *setDuration(crl::time duration);
protected:
void wrappedSizeUpdated(QSize size) override;
void wrappedNaturalWidthUpdated(int width) override;
private:
void updateWrappedPosition(int forHeight);
Animations::Simple _animation;
crl::time _duration = 0;
};
template <typename Widget>
class FollowSlideWrap : public Wrap<Widget, FollowSlideWrap<RpWidget>> {
using Parent = Wrap<Widget, FollowSlideWrap<RpWidget>>;
public:
FollowSlideWrap(
QWidget *parent,
object_ptr<Widget> &&child)
: Parent(parent, std::move(child)) {
}
};
} // namespace Ui

View File

@@ -0,0 +1,68 @@
// 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/padding_wrap.h"
#include "ui/ui_utility.h"
namespace Ui {
PaddingWrap<RpWidget>::PaddingWrap(
QWidget *parent,
object_ptr<RpWidget> &&child,
const style::margins &padding)
: Parent(parent, std::move(child)) {
setPadding(padding);
if (auto weak = wrapped()) {
wrappedNaturalWidthUpdated(weak->naturalWidth());
}
}
void PaddingWrap<RpWidget>::setPadding(const style::margins &padding) {
if (_padding != padding) {
auto oldWidth = width() - _padding.left() - _padding.top();
_padding = padding;
if (auto weak = wrapped()) {
wrappedSizeUpdated(weak->size());
auto margins = weak->getMargins();
weak->moveToLeft(
_padding.left() + margins.left(),
_padding.top() + margins.top());
} else {
resize(QSize(
_padding.left() + oldWidth + _padding.right(),
_padding.top() + _padding.bottom()));
}
}
}
void PaddingWrap<RpWidget>::wrappedSizeUpdated(QSize size) {
resize(QRect(QPoint(), size).marginsAdded(_padding).size());
}
void PaddingWrap<RpWidget>::wrappedNaturalWidthUpdated(int width) {
setNaturalWidth((width < 0)
? width
: (_padding.left() + width + _padding.right()));
}
int PaddingWrap<RpWidget>::resizeGetHeight(int newWidth) {
if (auto weak = wrapped()) {
weak->resizeToWidth(newWidth
- _padding.left()
- _padding.right());
SendPendingMoveResizeEvents(weak);
} else {
resize(QSize(
_padding.left() + newWidth + _padding.right(),
_padding.top() + _padding.bottom()));
}
return heightNoMargins();
}
} // namespace Ui

View File

@@ -0,0 +1,72 @@
// 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/wrap/wrap.h"
namespace Ui {
template <typename Widget = RpWidget>
class PaddingWrap;
template <>
class PaddingWrap<RpWidget> : public Wrap<RpWidget> {
using Parent = Wrap<RpWidget>;
public:
PaddingWrap(
QWidget *parent,
object_ptr<RpWidget> &&child,
const style::margins &padding);
style::margins padding() const {
return _padding;
}
void setPadding(const style::margins &padding);
protected:
int resizeGetHeight(int newWidth) override;
void wrappedSizeUpdated(QSize size) override;
void wrappedNaturalWidthUpdated(int width) override;
private:
style::margins _padding;
};
template <typename Widget>
class PaddingWrap : public Wrap<Widget, PaddingWrap<RpWidget>> {
using Parent = Wrap<Widget, PaddingWrap<RpWidget>>;
public:
PaddingWrap(
QWidget *parent,
object_ptr<Widget> &&child,
const style::margins &padding)
: Parent(parent, std::move(child), padding) {
}
};
class FixedHeightWidget : public RpWidget {
public:
explicit FixedHeightWidget(QWidget *parent, int height = 0)
: RpWidget(parent) {
resize(width(), height);
}
};
inline object_ptr<FixedHeightWidget> CreateSkipWidget(
QWidget *parent,
int skip) {
return object_ptr<FixedHeightWidget>(
parent,
skip);
}
} // namespace Ui

View File

@@ -0,0 +1,184 @@
// 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/slide_wrap.h"
#include "ui/ui_utility.h"
#include "styles/style_basic.h"
#include <rpl/combine.h>
#include <range/v3/algorithm/find.hpp>
namespace Ui {
SlideWrap<RpWidget>::SlideWrap(
QWidget *parent,
object_ptr<RpWidget> &&child)
: SlideWrap(
parent,
std::move(child),
style::margins()) {
}
SlideWrap<RpWidget>::SlideWrap(
QWidget *parent,
const style::margins &padding)
: SlideWrap(parent, nullptr, padding) {
}
SlideWrap<RpWidget>::SlideWrap(
QWidget *parent,
object_ptr<RpWidget> &&child,
const style::margins &padding)
: Parent(
parent,
object_ptr<PaddingWrap<RpWidget>>(
parent,
std::move(child),
padding))
, _duration(st::slideWrapDuration) {
}
SlideWrap<RpWidget> *SlideWrap<RpWidget>::setDuration(int duration) {
_duration = duration;
return this;
}
SlideWrap<RpWidget> *SlideWrap<RpWidget>::setDirectionUp(bool up) {
_up = up;
return this;
}
SlideWrap<RpWidget> *SlideWrap<RpWidget>::toggle(
bool shown,
anim::type animated) {
auto animate = (animated == anim::type::normal) && _duration;
auto changed = (_toggled != shown);
if (changed) {
_toggled = shown;
if (animate) {
_animation.start(
[this] { animationStep(); },
_toggled ? 0. : 1.,
_toggled ? 1. : 0.,
_duration,
anim::linear);
}
}
if (animate) {
animationStep();
} else {
finishAnimating();
}
if (changed) {
_toggledChanged.fire_copy(_toggled);
}
return this;
}
SlideWrap<RpWidget> *SlideWrap<RpWidget>::finishAnimating() {
_animation.stop();
animationStep();
return this;
}
SlideWrap<RpWidget> *SlideWrap<RpWidget>::toggleOn(
rpl::producer<bool> &&shown,
anim::type animated) {
std::move(
shown
) | rpl::on_next([=](bool shown) {
toggle(shown, animated);
}, lifetime());
finishAnimating();
return this;
}
void SlideWrap<RpWidget>::setMinimalHeight(int height) {
_minimalHeight = height;
}
void SlideWrap<RpWidget>::animationStep() {
const auto weak = wrapped();
if (weak && !_up) {
const auto margins = getMargins();
weak->moveToLeft(margins.left(), margins.top());
}
const auto newWidth = weak ? weak->width() : width();
const auto current = _animation.value(_toggled ? 1. : 0.);
const auto newHeight = weak
? (_animation.animating()
? anim::interpolate(
_minimalHeight,
weak->heightNoMargins(),
current)
: (_toggled ? weak->height() : _minimalHeight))
: 0;
if (weak && _up) {
const auto margins = getMargins();
weak->moveToLeft(
margins.left(),
margins.top() - (weak->height() - newHeight));
}
if (newWidth != width() || newHeight != height()) {
resize(newWidth, newHeight);
}
const auto shouldBeHidden = !_toggled && !_animation.animating();
if (shouldBeHidden != isHidden()) {
const auto guard = base::make_weak(this);
setVisible(!shouldBeHidden || _minimalHeight);
if (shouldBeHidden && guard) {
SendPendingMoveResizeEvents(this);
}
}
}
QMargins SlideWrap<RpWidget>::getMargins() const {
auto result = wrapped()->getMargins();
return (animating() || !_toggled)
? QMargins(result.left(), 0, result.right(), 0)
: result;
}
int SlideWrap<RpWidget>::resizeGetHeight(int newWidth) {
if (wrapped()) {
wrapped()->resizeToWidth(newWidth);
}
return heightNoMargins();
}
void SlideWrap<RpWidget>::wrappedSizeUpdated(QSize size) {
if (_animation.animating()) {
animationStep();
} else if (_toggled) {
resize(size);
}
}
rpl::producer<bool> MultiSlideTracker::atLeastOneShownValue() const {
if (_widgets.empty()) {
return rpl::single(false);
}
auto shown = std::vector<rpl::producer<bool>>();
shown.reserve(_widgets.size());
for (auto &widget : _widgets) {
shown.push_back(widget->toggledValue());
}
return rpl::combine(
std::move(shown),
[](const std::vector<bool> &values) {
return ranges::find(values, true) != values.end();
});
}
rpl::producer<bool> MultiSlideTracker::atLeastOneShownValueLater() const {
return _widgetAdded.events() | rpl::map([=] {
return atLeastOneShownValue();
}) | rpl::flatten_latest();
}
} // namespace Ui

View File

@@ -0,0 +1,155 @@
// 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"
#include "ui/wrap/padding_wrap.h"
namespace Ui {
template <typename Widget = RpWidget>
class SlideWrap;
template <>
class SlideWrap<RpWidget> : public Wrap<PaddingWrap<RpWidget>> {
using Parent = Wrap<PaddingWrap<RpWidget>>;
public:
SlideWrap(
QWidget *parent,
object_ptr<RpWidget> &&child);
SlideWrap(
QWidget *parent,
const style::margins &padding);
SlideWrap(
QWidget *parent,
object_ptr<RpWidget> &&child,
const style::margins &padding);
SlideWrap *setDuration(int duration);
SlideWrap *setDirectionUp(bool up);
SlideWrap *toggle(bool shown, anim::type animated);
SlideWrap *show(anim::type animated) {
return toggle(true, animated);
}
SlideWrap *hide(anim::type animated) {
return toggle(false, animated);
}
SlideWrap *finishAnimating();
SlideWrap *toggleOn(
rpl::producer<bool> &&shown,
anim::type animated = anim::type::normal);
bool animating() const {
return _animation.animating();
}
bool toggled() const {
return _toggled;
}
auto toggledValue() const {
return _toggledChanged.events_starting_with_copy(_toggled);
}
void setMinimalHeight(int height);
QMargins getMargins() const override;
protected:
int resizeGetHeight(int newWidth) override;
void wrappedSizeUpdated(QSize size) override;
private:
void animationStep();
rpl::event_stream<bool> _toggledChanged;
Animations::Simple _animation;
int _duration = 0;
bool _toggled = true;
bool _up = false;
int _minimalHeight = 0;
};
template <typename Widget>
class SlideWrap : public Wrap<PaddingWrap<Widget>, SlideWrap<RpWidget>> {
using Parent = Wrap<PaddingWrap<Widget>, SlideWrap<RpWidget>>;
public:
SlideWrap(
QWidget *parent,
object_ptr<Widget> &&child)
: Parent(parent, std::move(child)) {
}
SlideWrap(
QWidget *parent,
const style::margins &padding)
: Parent(parent, padding) {
}
SlideWrap(
QWidget *parent,
object_ptr<Widget> &&child,
const style::margins &padding)
: Parent(parent, std::move(child), padding) {
}
SlideWrap *setDuration(int duration) {
return chain(Parent::setDuration(duration));
}
SlideWrap *setDirectionUp(bool up) {
return chain(Parent::setDirectionUp(up));
}
SlideWrap *toggle(bool shown, anim::type animated) {
return chain(Parent::toggle(shown, animated));
}
SlideWrap *show(anim::type animated) {
return chain(Parent::show(animated));
}
SlideWrap *hide(anim::type animated) {
return chain(Parent::hide(animated));
}
SlideWrap *finishAnimating() {
return chain(Parent::finishAnimating());
}
SlideWrap *toggleOn(
rpl::producer<bool> &&shown,
anim::type animated = anim::type::normal) {
return chain(Parent::toggleOn(std::move(shown), animated));
}
private:
SlideWrap *chain(SlideWrap<RpWidget> *result) {
return static_cast<SlideWrap*>(result);
}
};
inline object_ptr<SlideWrap<>> CreateSlideSkipWidget(
QWidget *parent,
int skip) {
return object_ptr<SlideWrap<>>(
parent,
QMargins(0, 0, 0, skip));
}
class MultiSlideTracker {
public:
template <typename Widget>
void track(const SlideWrap<Widget> *wrap) {
_widgets.push_back(wrap);
_widgetAdded.fire({});
}
rpl::producer<bool> atLeastOneShownValue() const;
rpl::producer<bool> atLeastOneShownValueLater() const;
private:
std::vector<const SlideWrap<Ui::RpWidget>*> _widgets;
rpl::event_stream<> _widgetAdded;
};
} // namespace Ui

View File

@@ -0,0 +1,313 @@
// 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/table_layout.h"
#include "ui/painter.h"
#include "ui/ui_utility.h"
#include "styles/style_widgets.h"
#include <QtGui/QPainterPath>
namespace Ui {
TableLayout::TableLayout(QWidget *parent, const style::Table &st)
: RpWidget(parent)
, _st(st) {
}
TableLayout::~TableLayout() {
_rowsLifetime.destroy();
auto taken = std::move(_rows);
for (auto &row : taken) {
row.label.destroy();
row.value.destroy();
}
}
void TableLayout::paintEvent(QPaintEvent *e) {
if (_rows.empty()) {
return;
}
auto p = QPainter(this);
auto hq = PainterHighQualityEnabler(p);
const auto half = _st.border / 2.;
const auto inner = QRectF(rect()).marginsRemoved(
{ half, half, half, half });
auto labels = QRegion();
auto yfrom = half;
auto ytill = height() - half;
for (auto i = 0, count = int(_rows.size()); i != count; ++i) {
const auto &row = _rows[i];
yfrom = row.top + half;
if (!row.value) {
const auto till = (i + 1 == count)
? (inner.y() + inner.height())
: (_rows[i + 1].top - half);
labels += QRect(inner.x(), yfrom, inner.width(), till - yfrom);
} else if (row.label) {
break;
}
}
for (auto i = 0, count = int(_rows.size()); i != count; ++i) {
const auto index = count - i - 1;
const auto &row = _rows[index];
if (!row.value) {
const auto from = row.top + half;
labels += QRect(inner.x(), from, inner.width(), ytill - from);
} else if (row.label) {
break;
}
ytill = row.top - half;
}
if (ytill > yfrom) {
labels += QRect(0, yfrom, _valueLeft, ytill - yfrom);
}
if (!labels.isEmpty()) {
p.setClipRegion(labels);
p.setBrush(_st.headerBg);
p.setPen(Qt::NoPen);
p.drawRoundedRect(inner, _st.radius, _st.radius);
p.setClipping(false);
}
auto path = QPainterPath();
path.addRoundedRect(inner, _st.radius, _st.radius);
for (auto i = 1, count = int(_rows.size()); i != count; ++i) {
const auto y = _rows[i].top - half;
path.moveTo(half, y);
path.lineTo(width() - half, y);
}
if (ytill > yfrom) {
path.moveTo(_valueLeft - half, yfrom);
path.lineTo(_valueLeft - half, ytill);
}
auto pen = _st.borderFg->p;
pen.setWidth(_st.border);
p.setPen(pen);
p.setBrush(Qt::NoBrush);
p.drawPath(path);
}
int TableLayout::resizeGetHeight(int newWidth) {
_inResize = true;
auto guard = gsl::finally([&] { _inResize = false; });
auto available = newWidth - 3 * _st.border;
const auto labelMax = int(base::SafeRound(
_st.labelMaxWidth * available));
const auto valueMin = available - labelMax;
if (labelMax <= 0 || valueMin <= 0 || _rows.empty()) {
return 0;
}
auto label = _st.labelMinWidth;
for (auto &row : _rows) {
const auto natural = (row.label && row.value)
? (row.label->naturalWidth()
+ row.labelMargin.left()
+ row.labelMargin.right())
: 0;
if (natural < 0 || natural >= labelMax) {
label = labelMax;
break;
} else if (natural > label) {
label = natural;
}
}
_valueLeft = _st.border * 2 + label;
auto result = _st.border;
for (auto &row : _rows) {
updateRowGeometry(row, newWidth, result);
result += rowVerticalSkip(row);
}
return result;
}
void TableLayout::visibleTopBottomUpdated(
int visibleTop,
int visibleBottom) {
for (auto &row : _rows) {
if (row.label) {
setChildVisibleTopBottom(
row.label,
visibleTop,
visibleBottom);
}
if (row.value) {
setChildVisibleTopBottom(
row.value,
visibleTop,
visibleBottom);
}
}
}
void TableLayout::updateRowGeometry(
const Row &row,
int width,
int top) const {
if (row.label && row.value) {
row.label->resizeToNaturalWidth(_valueLeft
- 2 * _st.border
- row.labelMargin.left()
- row.labelMargin.right());
row.value->resizeToNaturalWidth(width
- _valueLeft
- _st.border
- row.valueMargin.left()
- row.valueMargin.right());
} else if (row.label) {
row.label->resizeToNaturalWidth(width
- 2 * _st.border
- row.labelMargin.left()
- row.valueMargin.right());
} else {
row.value->resizeToNaturalWidth(width
- 2 * _st.border
- row.labelMargin.left()
- row.valueMargin.right());
}
updateRowPosition(row, width, top);
}
void TableLayout::updateRowPosition(
const Row &row,
int width,
int top) const {
row.top = top;
if (row.label && row.value) {
row.label->moveToLeft(
_st.border + row.labelMargin.left(),
top + row.labelMargin.top(),
width);
row.value->moveToLeft(
_valueLeft + row.valueMargin.left(),
top + row.valueMargin.top(),
width);
} else if (row.label) {
row.label->moveToLeft(
_st.border + row.labelMargin.left(),
top + row.valueMargin.top(),
width);
} else {
row.value->moveToLeft(
_st.border + row.labelMargin.left(),
top + row.valueMargin.top(),
width);
}
}
void TableLayout::insertRow(
int atPosition,
object_ptr<RpWidget> &&label,
object_ptr<RpWidget> &&value,
const style::margins &labelMargin,
const style::margins &valueMargin) {
Expects(atPosition >= 0 && atPosition <= _rows.size());
Expects(!_inResize);
const auto wlabel = label ? AttachParentChild(this, label) : nullptr;
const auto wvalue = value ? AttachParentChild(this, value) : nullptr;
if (wlabel || wvalue) {
_rows.insert(begin(_rows) + atPosition, {
std::move(label),
std::move(value),
labelMargin,
valueMargin,
});
if (wlabel) {
wlabel->heightValue(
) | rpl::on_next_done([=] {
if (!_inResize) {
childHeightUpdated(wlabel);
}
}, [=] {
removeChild(wlabel);
}, _rowsLifetime);
}
if (wvalue) {
wvalue->heightValue(
) | rpl::on_next_done([=] {
if (!_inResize) {
childHeightUpdated(wvalue);
}
}, [=] {
removeChild(wvalue);
}, _rowsLifetime);
}
}
}
void TableLayout::childHeightUpdated(RpWidget *child) {
auto it = ranges::find_if(_rows, [child](const Row &row) {
return (row.label == child) || (row.value == child);
});
const auto end = _rows.end();
Assert(it != end);
auto top = it->top;
const auto outer = width();
for (; it != end; ++it) {
const auto &row = *it;
updateRowPosition(row, outer, top);
top += rowVerticalSkip(row);
}
resize(width(), _rows.empty() ? 0 : top);
}
void TableLayout::removeChild(RpWidget *child) {
const auto it = ranges::find_if(_rows, [child](const Row &row) {
return (row.label == child) || (row.value == child);
});
if (auto e = end(_rows); it != e) {
auto top = it->top;
auto removed = std::move(*it);
auto next = _rows.erase(it);
const auto outer = width();
for (e = end(_rows); next != e; ++next) {
auto &row = *next;
updateRowPosition(row, outer, top);
top += rowVerticalSkip(row);
}
resize(width(), _rows.empty() ? 0 : top);
if (removed.label.data() == child) {
removed.value.destroy();
} else {
removed.label.destroy();
}
}
}
int TableLayout::rowVerticalSkip(const Row &row) const {
const auto labelHeight = row.label
? (row.labelMargin.top()
+ row.label->heightNoMargins()
+ row.labelMargin.bottom())
: 0;
const auto valueHeight = row.value
? (row.valueMargin.top()
+ row.value->heightNoMargins()
+ row.valueMargin.bottom())
: 0;
return std::max(labelHeight, valueHeight) + _st.border;
}
void TableLayout::clear() {
while (!_rows.empty()) {
const auto &row = _rows.front();
removeChild(row.value ? row.value.data() : row.label.data());
}
}
} // namespace Ui

View File

@@ -0,0 +1,101 @@
// 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 "base/object_ptr.h"
#include "ui/rp_widget.h"
namespace style {
struct Table;
} // namespace style
namespace st {
extern const style::Table &defaultTable;
} // namespace st
namespace Ui {
class TableLayout : public RpWidget {
public:
TableLayout(QWidget *parent, const style::Table &st = st::defaultTable);
~TableLayout();
[[nodiscard]] const style::Table &st() const {
return _st;
}
[[nodiscard]] int rowsCount() const {
return _rows.size();
}
[[nodiscard]] not_null<RpWidget*> labelAt(int index) const {
Expects(index >= 0 && index < rowsCount());
return _rows[index].label.data();
}
[[nodiscard]] not_null<RpWidget*> valueAt(int index) const {
Expects(index >= 0 && index < rowsCount());
return _rows[index].value.data();
}
void insertRow(
int atPosition,
object_ptr<RpWidget> &&label,
object_ptr<RpWidget> &&value,
const style::margins &labelMargin = style::margins(),
const style::margins &valueMargin = style::margins());
void addRow(
object_ptr<RpWidget> &&label,
object_ptr<RpWidget> &&value,
const style::margins &labelMargin = style::margins(),
const style::margins &valueMargin = style::margins()) {
insertRow(
rowsCount(),
std::move(label),
std::move(value),
labelMargin,
valueMargin);
}
void clear();
protected:
void paintEvent(QPaintEvent *e) override;
int resizeGetHeight(int newWidth) override;
void visibleTopBottomUpdated(
int visibleTop,
int visibleBottom) override;
private:
struct Row {
object_ptr<RpWidget> label;
object_ptr<RpWidget> value;
style::margins labelMargin;
style::margins valueMargin;
mutable int top = 0;
};
[[nodiscard]] int rowVerticalSkip(const Row &row) const;
void childHeightUpdated(RpWidget *child);
void removeChild(RpWidget *child);
void updateRowGeometry(const Row &row, int width, int top) const;
void updateRowPosition(const Row &row, int width, int top) const;
const style::Table &_st;
std::vector<Row> _rows;
int _valueLeft = 0;
bool _inResize = false;
rpl::lifetime _rowsLifetime;
};
} // namespace Ui

View File

@@ -0,0 +1,281 @@
// 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.h"
#include "ui/ui_utility.h"
namespace Ui {
QMargins VerticalLayout::getMargins() const {
auto result = QMargins();
if (!_rows.empty()) {
auto &top = _rows.front();
auto topMargin = top.widget->getMargins().top();
result.setTop(
qMax(topMargin - top.margin.top(), 0));
auto &bottom = _rows.back();
auto bottomMargin = bottom.widget->getMargins().bottom();
result.setBottom(
qMax(bottomMargin - bottom.margin.bottom(), 0));
for (auto &row : _rows) {
auto margins = row.widget->getMargins();
result.setLeft(qMax(
margins.left() - row.margin.left(),
result.left()));
result.setRight(qMax(
margins.right() - row.margin.right(),
result.right()));
}
}
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) {
if (newWidth <= 0) {
return 0;
}
_inResize = true;
auto guard = gsl::finally([&] { _inResize = false; });
const auto margins = getMargins();
const auto outerWidth = margins.left() + newWidth + margins.right();
auto result = margins.top();
for (auto &row : _rows) {
const auto widget = row.widget.data();
const auto &margin = row.margin;
const auto available = newWidth - margin.left() - margin.right();
if (available > 0) {
if (row.align == kAlignJustify) {
widget->resizeToWidth(available);
} else {
widget->resizeToNaturalWidth(available);
}
}
result += moveChildGetSkip(row, result, outerWidth, margins);
}
return result - margins.top();
}
void VerticalLayout::visibleTopBottomUpdated(
int visibleTop,
int visibleBottom) {
for (auto &row : _rows) {
setChildVisibleTopBottom(
row.widget,
visibleTop,
visibleBottom);
}
}
int VerticalLayout::moveChildGetSkip(
const Row &row,
int top,
int outerWidth,
const style::margins &margins) const {
const auto align = row.align;
const auto widget = row.widget.data();
const auto wmargins = widget->getMargins();
const auto &margin = row.margin;
const auto full = margins + margin;
top += margin.top() + row.verticalShift;
if (align == kAlignLeft || align == kAlignJustify) {
widget->moveToLeft(full.left(), top, outerWidth);
} else if (align == kAlignCenter) {
const auto available = outerWidth - full.left() - full.right();
const auto free = available
- widget->width()
+ wmargins.left()
+ wmargins.right();
widget->moveToLeft(full.left() + (free / 2), top, outerWidth);
} else if (align == kAlignRight) {
widget->moveToRight(full.right(), top, outerWidth);
}
return margin.top()
- wmargins.top()
+ widget->height()
- wmargins.bottom()
+ margin.bottom();
}
RpWidget *VerticalLayout::insertChild(
int atPosition,
object_ptr<RpWidget> child,
const style::margins &margin,
style::align align) {
Expects(atPosition >= 0 && atPosition <= _rows.size());
Expects(!_inResize);
if (const auto weak = AttachParentChild(this, child)) {
const auto converted = (align == style::al_justify)
? kAlignJustify
: (align & style::al_left)
? kAlignLeft
: (align & style::al_right)
? kAlignRight
: kAlignCenter;
_rows.insert(
begin(_rows) + atPosition,
{ std::move(child), margin, 0, converted });
if (converted != kAlignJustify) {
subscribeToWidth(weak, margin);
}
weak->heightValue(
) | rpl::on_next_done([=] {
if (!_inResize) {
childHeightUpdated(weak);
}
}, [=] {
removeChild(weak);
}, _rowsLifetime);
return weak;
}
return nullptr;
}
void VerticalLayout::subscribeToWidth(
not_null<RpWidget*> child,
const style::margins &margin) {
child->naturalWidthValue(
) | rpl::on_next([=](int naturalWidth) {
setNaturalWidth([&] {
if (naturalWidth < 0) {
return -1;
}
auto result = -1;
for (const auto &row : _rows) {
if (row.align == kAlignJustify) {
return -1;
}
const auto natural = row.widget->naturalWidth();
if (natural < 0) {
return -1;
}
accumulate_max(
result,
row.margin.left() + natural + row.margin.right());
}
return result;
}());
const auto available = widthNoMargins()
- margin.left()
- margin.right();
if (available > 0) {
child->resizeToWidth((naturalWidth >= 0)
? std::min(naturalWidth, available)
: available);
}
}, _rowsLifetime);
const auto taken = std::exchange(_inResize, true);
child->widthValue(
) | rpl::on_next([=] {
if (!_inResize) {
childWidthUpdated(child);
}
}, _rowsLifetime);
_inResize = taken;
}
void VerticalLayout::childWidthUpdated(RpWidget *child) {
const auto it = ranges::find_if(_rows, [child](const Row &row) {
return (row.widget == child);
});
const auto &row = *it;
const auto margins = getMargins();
const auto top = child->y()
+ child->getMargins().top()
- row.margin.top()
- row.verticalShift;
moveChildGetSkip(row, top, width(), margins);
}
void VerticalLayout::childHeightUpdated(RpWidget *child) {
auto it = ranges::find_if(_rows, [child](const Row &row) {
return (row.widget == child);
});
const auto width = this->width();
const auto margins = getMargins();
auto top = [&] {
if (it == _rows.begin()) {
return margins.top();
}
auto prev = it - 1;
const auto widget = prev->widget.data();
return widget->y()
+ widget->height()
- widget->getMargins().bottom()
+ prev->margin.bottom();
}();
for (auto end = _rows.end(); it != end; ++it) {
const auto &row = *it;
top += moveChildGetSkip(row, top, width, margins);
}
resize(width, top + margins.bottom());
}
void VerticalLayout::removeChild(RpWidget *child) {
auto it = ranges::find_if(_rows, [child](const Row &row) {
return (row.widget == child);
});
auto end = _rows.end();
Assert(it != end);
const auto width = this->width();
const auto margins = getMargins();
auto top = [&] {
if (it == _rows.begin()) {
return margins.top();
}
auto prev = it - 1;
const auto widget = prev->widget.data();
return widget->y()
+ widget->height()
- widget->getMargins().bottom()
+ prev->margin.bottom();
}();
for (auto next = it + 1; next != end; ++next) {
const auto &row = *next;
top += moveChildGetSkip(row, top, width, margins);
}
it->widget = nullptr;
_rows.erase(it);
resize(width, top + margins.bottom());
}
void VerticalLayout::clear() {
while (!_rows.empty()) {
removeChild(_rows.front().widget.data());
}
}
} // namespace Ui

View File

@@ -0,0 +1,127 @@
// 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 "base/object_ptr.h"
namespace Ui {
class VerticalLayout : public RpWidget {
public:
using RpWidget::RpWidget;
[[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,
typename = std::enable_if_t<
std::is_base_of_v<RpWidget, Widget>>>
Widget *insert(
int atPosition,
object_ptr<Widget> &&child,
const style::margins &margin = style::margins(),
style::align align = style::al_left) {
return static_cast<Widget*>(insertChild(
atPosition,
std::move(child),
margin,
align));
}
template <
typename Widget,
typename = std::enable_if_t<
std::is_base_of_v<RpWidget, Widget>>>
Widget *insert(
int atPosition,
object_ptr<Widget> &&child,
style::align align) {
return static_cast<Widget*>(insertChild(
atPosition,
std::move(child),
style::margins(),
align));
}
template <
typename Widget,
typename = std::enable_if_t<
std::is_base_of_v<RpWidget, Widget>>>
Widget *add(
object_ptr<Widget> &&child,
const style::margins &margin = style::margins(),
style::align align = style::al_left) {
return insert(count(), std::move(child), margin, align);
}
template <
typename Widget,
typename = std::enable_if_t<
std::is_base_of_v<RpWidget, Widget>>>
Widget *add(object_ptr<Widget> &&child, style::align align) {
return insert(count(), std::move(child), align);
}
QMargins getMargins() const override;
void setVerticalShift(int index, int shift);
void reorderRows(int oldIndex, int newIndex);
void clear();
protected:
int resizeGetHeight(int newWidth) override;
void visibleTopBottomUpdated(
int visibleTop,
int visibleBottom) override;
private:
static constexpr auto kAlignLeft = 0;
static constexpr auto kAlignCenter = 1;
static constexpr auto kAlignRight = -1;
static constexpr auto kAlignJustify = -2;
struct Row {
object_ptr<RpWidget> widget;
style::margins margin;
int32 verticalShift : 30 = 0;
int32 align : 2 = 0;
};
RpWidget *insertChild(
int addPosition,
object_ptr<RpWidget> child,
const style::margins &margin,
style::align align);
void subscribeToWidth(
not_null<RpWidget*> child,
const style::margins &margin);
void childWidthUpdated(RpWidget *child);
void childHeightUpdated(RpWidget *child);
void removeChild(RpWidget *child);
int moveChildGetSkip(
const Row &row,
int top,
int outerWidth,
const style::margins &margins) const;
std::vector<Row> _rows;
bool _inResize = false;
rpl::lifetime _rowsLifetime;
};
} // namespace Ui

View File

@@ -0,0 +1,364 @@
// 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 {
namespace {
constexpr auto kScrollFactor = 0.05;
} // namespace
VerticalLayoutReorder::VerticalLayoutReorder(
not_null<VerticalLayout*> layout,
not_null<ScrollArea*> scroll)
: _layout(layout)
, _scroll(scroll)
, _scrollAnimation([=] { updateScrollCallback(); }) {
}
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);
const auto eventsProducer = _proxyWidgetCallback
? _proxyWidgetCallback(i)
: widget;
eventsProducer->events(
) | rpl::on_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(static_cast<QMouseEvent*>(e.get())->button());
break;
}
}, [=] {
cancel();
}, _lifetime);
_entries.push_back({ widget });
}
}
void VerticalLayoutReorder::addPinnedInterval(int from, int length) {
_pinnedIntervals.push_back({ from, length });
}
void VerticalLayoutReorder::clearPinnedIntervals() {
_pinnedIntervals.clear();
}
void VerticalLayoutReorder::setMouseEventProxy(ProxyCallback callback) {
_proxyWidgetCallback = std::move(callback);
}
bool VerticalLayoutReorder::Interval::isIn(int index) const {
return (index >= from) && (index < (from + length));
}
bool VerticalLayoutReorder::isIndexPinned(int index) const {
return ranges::any_of(_pinnedIntervals, [&](const Interval &i) {
return i.isIn(index);
});
}
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) {
if (isIndexPinned(index)) {
return;
}
const auto shift = position.y() - _currentStart;
auto &current = _entries[index];
current.shiftAnimation.stop();
current.shift = current.finalShift = shift;
_layout->setVerticalShift(index, shift);
checkForScrollAnimation();
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) {
if (isIndexPinned(next)) {
return;
}
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) {
if (isIndexPinned(prev)) {
return;
}
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(Qt::MouseButton button) {
if (button != Qt::LeftButton) {
return;
}
finishReordering();
}
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::finishReordering() {
if (_scroll) {
_scrollAnimation.stop();
}
finishCurrent();
}
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 = base::SafeRound(
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();
}
void VerticalLayoutReorder::updateScrollCallback() {
if (!_scroll) {
return;
}
const auto delta = deltaFromEdge();
const auto oldTop = _scroll->scrollTop();
_scroll->scrollToY(oldTop + delta);
const auto newTop = _scroll->scrollTop();
_currentStart += oldTop - newTop;
if (newTop == 0 || newTop == _scroll->scrollTopMax()) {
_scrollAnimation.stop();
}
}
void VerticalLayoutReorder::checkForScrollAnimation() {
if (!_scroll || !deltaFromEdge() || _scrollAnimation.animating()) {
return;
}
_scrollAnimation.start();
}
int VerticalLayoutReorder::deltaFromEdge() {
Expects(_currentWidget != nullptr);
Expects(_scroll);
const auto globalPosition = _currentWidget->mapToGlobal(QPoint(0, 0));
const auto localTop = _scroll->mapFromGlobal(globalPosition).y();
const auto localBottom = localTop
+ _currentWidget->height()
- _scroll->height();
const auto isTopEdge = (localTop < 0);
const auto isBottomEdge = (localBottom > 0);
if (!isTopEdge && !isBottomEdge) {
_scrollAnimation.stop();
return 0;
}
return int((isBottomEdge ? localBottom : localTop) * kScrollFactor);
}
} // namespace Ui

View File

@@ -0,0 +1,102 @@
// 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"
#include "ui/widgets/scroll_area.h"
namespace Ui {
class RpWidget;
class VerticalLayout;
class VerticalLayoutReorder final {
public:
using ProxyCallback = Fn<not_null<Ui::RpWidget*>(int)>;
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,
not_null<ScrollArea*> scroll);
VerticalLayoutReorder(not_null<VerticalLayout*> layout);
void start();
void cancel();
void finishReordering();
void addPinnedInterval(int from, int length);
void clearPinnedIntervals();
void setMouseEventProxy(ProxyCallback callback);
[[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;
};
struct Interval {
[[nodiscard]] bool isIn(int index) const;
int from = 0;
int length = 0;
};
void mouseMove(not_null<RpWidget*> widget, QPoint position);
void mousePress(
not_null<RpWidget*> widget,
Qt::MouseButton button,
QPoint position);
void mouseRelease(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);
void updateScrollCallback();
void checkForScrollAnimation();
[[nodiscard]] int deltaFromEdge();
[[nodiscard]] bool isIndexPinned(int index) const;
const not_null<Ui::VerticalLayout*> _layout;
Ui::ScrollArea *_scroll = nullptr;
Ui::Animations::Basic _scrollAnimation;
std::vector<Interval> _pinnedIntervals;
ProxyCallback _proxyWidgetCallback = nullptr;
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

View File

@@ -0,0 +1,203 @@
// 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 "base/object_ptr.h"
namespace Ui {
template <typename Widget, typename ParentType = RpWidget>
class Wrap;
namespace details {
struct UnwrapHelper {
template <
typename Widget,
typename = typename std::decay_t<Widget>::WrapParentType>
static std::true_type Is(Widget &&widget);
static std::false_type Is(...);
template <typename Entity>
static auto Unwrap(Entity *entity, std::true_type) {
return entity
? entity->entity()
: nullptr;
}
template <typename Entity>
static Entity *Unwrap(Entity *entity, std::false_type) {
return entity;
}
template <typename Entity>
static auto Unwrap(Entity *entity) {
using Selector = decltype(Is(std::declval<Entity>()));
return Unwrap(
entity,
Selector());
}
};
} // namespace details
template <typename Widget>
class Wrap<Widget, RpWidget> : public RpWidget {
public:
Wrap(QWidget *parent, object_ptr<Widget> &&child);
Widget *wrapped() {
return _wrapped;
}
const Widget *wrapped() const {
return _wrapped;
}
auto entity() {
return details::UnwrapHelper::Unwrap(wrapped());
}
auto entity() const {
return details::UnwrapHelper::Unwrap(wrapped());
}
QMargins getMargins() const override {
if (auto weak = wrapped()) {
return weak->getMargins();
}
return RpWidget::getMargins();
}
using WrapParentType = RpWidget;
protected:
int resizeGetHeight(int newWidth) override {
if (auto weak = wrapped()) {
weak->resizeToWidth(newWidth);
return weak->heightNoMargins();
}
return heightNoMargins();
}
void visibleTopBottomUpdated(
int visibleTop,
int visibleBottom) override {
setChildVisibleTopBottom(
wrapped(),
visibleTop,
visibleBottom);
}
virtual void wrappedSizeUpdated(QSize size) {
resize(size);
}
virtual void wrappedNaturalWidthUpdated(int naturalWidth) {
setNaturalWidth(naturalWidth);
}
private:
object_ptr<Widget> _wrapped;
};
template <typename Widget>
Wrap<Widget, RpWidget>::Wrap(
QWidget *parent,
object_ptr<Widget> &&child)
: RpWidget(parent)
, _wrapped(std::move(child)) {
if (_wrapped) {
_wrapped->sizeValue(
) | rpl::on_next([this](const QSize &value) {
wrappedSizeUpdated(value);
}, lifetime());
_wrapped->naturalWidthValue(
) | rpl::on_next([this](int naturalWidth) {
wrappedNaturalWidthUpdated(naturalWidth);
}, lifetime());
_wrapped->setParent(this);
_wrapped->show();
_wrapped->move(0, 0);
_wrapped->alive(
) | rpl::on_done([this] {
_wrapped->setParent(nullptr);
_wrapped = nullptr;
delete this;
}, lifetime());
}
}
template <typename Widget, typename ParentType>
class Wrap : public ParentType {
public:
using ParentType::ParentType;
Widget *wrapped() {
return static_cast<Widget*>(ParentType::wrapped());
}
const Widget *wrapped() const {
return static_cast<const Widget*>(ParentType::wrapped());
}
auto entity() {
return details::UnwrapHelper::Unwrap(wrapped());
}
auto entity() const {
return details::UnwrapHelper::Unwrap(wrapped());
}
using WrapParentType = ParentType;
};
class OverrideMargins : public Wrap<RpWidget> {
using Parent = Wrap<RpWidget>;
public:
OverrideMargins(
QWidget *parent,
object_ptr<RpWidget> &&child,
QMargins margins = QMargins())
: Parent(parent, std::move(child)), _margins(margins) {
if (auto weak = wrapped()) {
auto margins = weak->getMargins();
resizeToWidth(weak->width()
- margins.left()
- margins.right());
}
}
QMargins getMargins() const override {
return _margins;
}
protected:
int resizeGetHeight(int newWidth) override {
if (auto weak = wrapped()) {
weak->resizeToWidth(newWidth);
weak->moveToLeft(_margins.left(), _margins.top());
return weak->heightNoMargins();
}
return height();
}
private:
void wrappedSizeUpdated(QSize size) override {
auto margins = wrapped()->getMargins();
resize(
(size.width()
- margins.left()
- margins.right()
+ _margins.left()
+ _margins.right()),
(size.height()
- margins.top()
- margins.bottom()
+ _margins.top()
+ _margins.bottom()));
}
QMargins _margins;
};
} // namespace Ui