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
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:
120
Telegram/lib_ui/ui/wrap/fade_wrap.cpp
Normal file
120
Telegram/lib_ui/ui/wrap/fade_wrap.cpp
Normal 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
|
||||
|
||||
122
Telegram/lib_ui/ui/wrap/fade_wrap.h
Normal file
122
Telegram/lib_ui/ui/wrap/fade_wrap.h
Normal 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
|
||||
|
||||
46
Telegram/lib_ui/ui/wrap/follow_slide_wrap.cpp
Normal file
46
Telegram/lib_ui/ui/wrap/follow_slide_wrap.cpp
Normal 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
|
||||
|
||||
54
Telegram/lib_ui/ui/wrap/follow_slide_wrap.h
Normal file
54
Telegram/lib_ui/ui/wrap/follow_slide_wrap.h
Normal 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
|
||||
|
||||
68
Telegram/lib_ui/ui/wrap/padding_wrap.cpp
Normal file
68
Telegram/lib_ui/ui/wrap/padding_wrap.cpp
Normal 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
|
||||
72
Telegram/lib_ui/ui/wrap/padding_wrap.h
Normal file
72
Telegram/lib_ui/ui/wrap/padding_wrap.h
Normal 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
|
||||
184
Telegram/lib_ui/ui/wrap/slide_wrap.cpp
Normal file
184
Telegram/lib_ui/ui/wrap/slide_wrap.cpp
Normal 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
|
||||
|
||||
155
Telegram/lib_ui/ui/wrap/slide_wrap.h
Normal file
155
Telegram/lib_ui/ui/wrap/slide_wrap.h
Normal 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
|
||||
|
||||
313
Telegram/lib_ui/ui/wrap/table_layout.cpp
Normal file
313
Telegram/lib_ui/ui/wrap/table_layout.cpp
Normal 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
|
||||
101
Telegram/lib_ui/ui/wrap/table_layout.h
Normal file
101
Telegram/lib_ui/ui/wrap/table_layout.h
Normal 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
|
||||
281
Telegram/lib_ui/ui/wrap/vertical_layout.cpp
Normal file
281
Telegram/lib_ui/ui/wrap/vertical_layout.cpp
Normal 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
|
||||
127
Telegram/lib_ui/ui/wrap/vertical_layout.h
Normal file
127
Telegram/lib_ui/ui/wrap/vertical_layout.h
Normal 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
|
||||
364
Telegram/lib_ui/ui/wrap/vertical_layout_reorder.cpp
Normal file
364
Telegram/lib_ui/ui/wrap/vertical_layout_reorder.cpp
Normal 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 ¤t = _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 ¤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 = 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
|
||||
102
Telegram/lib_ui/ui/wrap/vertical_layout_reorder.h
Normal file
102
Telegram/lib_ui/ui/wrap/vertical_layout_reorder.h
Normal 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
|
||||
203
Telegram/lib_ui/ui/wrap/wrap.h
Normal file
203
Telegram/lib_ui/ui/wrap/wrap.h
Normal 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
|
||||
Reference in New Issue
Block a user