Add layer and box management.

This commit is contained in:
John Preston 2019-09-18 14:17:24 +03:00
parent 946c33ef25
commit f4904e5ec4
25 changed files with 3013 additions and 4 deletions

View file

@ -10,10 +10,20 @@
<(src_loc)/ui/effects/numbers_animation.h
<(src_loc)/ui/effects/panel_animation.cpp
<(src_loc)/ui/effects/panel_animation.h
<(src_loc)/ui/effects/radial_animation.cpp
<(src_loc)/ui/effects/radial_animation.h
<(src_loc)/ui/effects/ripple_animation.cpp
<(src_loc)/ui/effects/ripple_animation.h
<(src_loc)/ui/image/image_prepare.cpp
<(src_loc)/ui/image/image_prepare.h
<(src_loc)/ui/layers/box_content.cpp
<(src_loc)/ui/layers/box_content.h
<(src_loc)/ui/layers/box_layer_widget.cpp
<(src_loc)/ui/layers/box_layer_widget.h
<(src_loc)/ui/layers/generic_box.cpp
<(src_loc)/ui/layers/generic_box.h
<(src_loc)/ui/layers/layer_widget.cpp
<(src_loc)/ui/layers/layer_widget.h
<(src_loc)/ui/platform/ui_platform_utility.h
<(src_loc)/ui/platform/linux/ui_platform_utility_linux.cpp
<(src_loc)/ui/platform/linux/ui_platform_utility_linux.h
@ -44,6 +54,8 @@
<(src_loc)/ui/text/text_isolated_emoji.h
<(src_loc)/ui/text/text_utilities.cpp
<(src_loc)/ui/text/text_utilities.h
<(src_loc)/ui/widgets/box_content_divider.cpp
<(src_loc)/ui/widgets/box_content_divider.h
<(src_loc)/ui/widgets/buttons.cpp
<(src_loc)/ui/widgets/buttons.h
<(src_loc)/ui/widgets/checkbox.cpp

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 B

BIN
icons/box_divider_top.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 106 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 B

View file

@ -34,6 +34,7 @@
'style_files': [
'<(src_loc)/ui/colors.palette',
'<(src_loc)/ui/basic.style',
'<(src_loc)/ui/layers/layers.style',
'<(src_loc)/ui/widgets/widgets.style',
],
'dependent_style_files': [

View file

@ -0,0 +1,309 @@
// 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/effects/radial_animation.h"
#include "ui/painter.h"
#include "styles/style_widgets.h"
namespace Ui {
namespace {
constexpr auto kFullArcLength = 360 * 16;
constexpr auto kQuarterArcLength = (kFullArcLength / 4);
constexpr auto kMinArcLength = (kFullArcLength / 360);
constexpr auto kAlmostFullArcLength = (kFullArcLength - kMinArcLength);
} // namespace
const int RadialState::kFull = kFullArcLength;
void RadialAnimation::start(float64 prg) {
_firstStart = _lastStart = _lastTime = crl::now();
const auto iprg = qRound(qMax(prg, 0.0001) * kAlmostFullArcLength);
const auto iprgstrict = qRound(prg * kAlmostFullArcLength);
_arcEnd = anim::value(iprgstrict, iprg);
_animation.start();
}
bool RadialAnimation::update(float64 prg, bool finished, crl::time ms) {
const auto iprg = qRound(qMax(prg, 0.0001) * kAlmostFullArcLength);
const auto result = (iprg != qRound(_arcEnd.to()));
if (_finished != finished) {
_arcEnd.start(iprg);
_finished = finished;
_lastStart = _lastTime;
} else if (result) {
_arcEnd.start(iprg);
_lastStart = _lastTime;
}
_lastTime = ms;
const auto dt = float64(ms - _lastStart);
const auto fulldt = float64(ms - _firstStart);
const auto opacitydt = _finished
? (_lastStart - _firstStart)
: fulldt;
_opacity = qMin(opacitydt / st::radialDuration, 1.);
if (anim::Disabled()) {
_arcEnd.update(1., anim::linear);
if (finished) {
stop();
}
} else if (!finished) {
_arcEnd.update(1. - (st::radialDuration / (st::radialDuration + dt)), anim::linear);
} else if (dt >= st::radialDuration) {
_arcEnd.update(1., anim::linear);
stop();
} else {
auto r = dt / st::radialDuration;
_arcEnd.update(r, anim::linear);
_opacity *= 1 - r;
}
auto fromstart = fulldt / st::radialPeriod;
_arcStart.update(fromstart - std::floor(fromstart), anim::linear);
return result;
}
void RadialAnimation::stop() {
_firstStart = _lastStart = _lastTime = 0;
_arcEnd = anim::value();
_animation.stop();
}
void RadialAnimation::draw(
QPainter &p,
const QRect &inner,
int32 thickness,
style::color color) const {
const auto state = computeState();
auto o = p.opacity();
p.setOpacity(o * state.shown);
auto pen = color->p;
auto was = p.pen();
pen.setWidth(thickness);
pen.setCapStyle(Qt::RoundCap);
p.setPen(pen);
{
PainterHighQualityEnabler hq(p);
p.drawArc(inner, state.arcFrom, state.arcLength);
}
p.setPen(was);
p.setOpacity(o);
}
RadialState RadialAnimation::computeState() const {
auto length = kMinArcLength + qRound(_arcEnd.current());
auto from = kQuarterArcLength
- length
- (anim::Disabled() ? 0 : qRound(_arcStart.current()));
if (style::RightToLeft()) {
from = kQuarterArcLength - (from - kQuarterArcLength) - length;
if (from < 0) from += kFullArcLength;
}
return { _opacity, from, length };
}
void InfiniteRadialAnimation::start(crl::time skip) {
const auto now = crl::now();
if (_workFinished <= now && (_workFinished || !_workStarted)) {
_workStarted = std::max(now + _st.sineDuration - skip, crl::time(1));
_workFinished = 0;
}
if (!_animation.animating()) {
_animation.start();
}
}
void InfiniteRadialAnimation::stop(anim::type animated) {
const auto now = crl::now();
if (anim::Disabled() || animated == anim::type::instant) {
_workFinished = now;
}
if (!_workFinished) {
const auto zero = _workStarted - _st.sineDuration;
const auto index = (now - zero + _st.sinePeriod - _st.sineShift)
/ _st.sinePeriod;
_workFinished = zero
+ _st.sineShift
+ (index * _st.sinePeriod)
+ _st.sineDuration;
} else if (_workFinished <= now) {
_animation.stop();
}
}
void InfiniteRadialAnimation::draw(
QPainter &p,
QPoint position,
int outerWidth) {
draw(p, position, _st.size, outerWidth);
}
void InfiniteRadialAnimation::draw(
QPainter &p,
QPoint position,
QSize size,
int outerWidth) {
const auto state = computeState();
auto o = p.opacity();
p.setOpacity(o * state.shown);
const auto rect = style::rtlrect(
position.x(),
position.y(),
size.width(),
size.height(),
outerWidth);
const auto was = p.pen();
const auto brush = p.brush();
if (anim::Disabled()) {
anim::DrawStaticLoading(p, rect, _st.thickness, _st.color);
} else {
auto pen = _st.color->p;
pen.setWidth(_st.thickness);
pen.setCapStyle(Qt::RoundCap);
p.setPen(pen);
{
PainterHighQualityEnabler hq(p);
p.drawArc(
rect,
state.arcFrom,
state.arcLength);
}
}
p.setPen(was);
p.setBrush(brush);
p.setOpacity(o);
}
RadialState InfiniteRadialAnimation::computeState() {
const auto now = crl::now();
const auto linear = kFullArcLength
- int(((now * kFullArcLength) / _st.linearPeriod) % kFullArcLength);
if (!_workStarted || (_workFinished && _workFinished <= now)) {
const auto shown = 0.;
_animation.stop();
return {
shown,
linear,
kFullArcLength };
}
if (anim::Disabled()) {
const auto shown = 1.;
return { 1., 0, kFullArcLength };
}
const auto min = int(std::round(kFullArcLength * _st.arcMin));
const auto max = int(std::round(kFullArcLength * _st.arcMax));
if (now <= _workStarted) {
// zero .. _workStarted
const auto zero = _workStarted - _st.sineDuration;
const auto shown = (now - zero) / float64(_st.sineDuration);
const auto length = anim::interpolate(
kFullArcLength,
min,
anim::sineInOut(1., std::clamp(shown, 0., 1.)));
return {
shown,
linear,
length };
} else if (!_workFinished || now <= _workFinished - _st.sineDuration) {
// _workStared .. _workFinished - _st.sineDuration
const auto shown = 1.;
const auto cycles = (now - _workStarted) / _st.sinePeriod;
const auto relative = (now - _workStarted) % _st.sinePeriod;
const auto smallDuration = _st.sineShift - _st.sineDuration;
const auto largeDuration = _st.sinePeriod
- _st.sineShift
- _st.sineDuration;
const auto basic = int((linear
+ min
+ (cycles * (kFullArcLength + min - max))) % kFullArcLength);
if (relative <= smallDuration) {
// localZero .. growStart
return {
shown,
basic - min,
min };
} else if (relative <= smallDuration + _st.sineDuration) {
// growStart .. growEnd
const auto growLinear = (relative - smallDuration) /
float64(_st.sineDuration);
const auto growProgress = anim::sineInOut(1., growLinear);
const auto length = anim::interpolate(min, max, growProgress);
return {
shown,
basic - length,
length };
} else if (relative <= _st.sinePeriod - _st.sineDuration) {
// growEnd .. shrinkStart
return {
shown,
basic - max,
max };
} else {
// shrinkStart .. shrinkEnd
const auto shrinkLinear = (relative
- (_st.sinePeriod - _st.sineDuration))
/ float64(_st.sineDuration);
const auto shrinkProgress = anim::sineInOut(1., shrinkLinear);
const auto shrink = anim::interpolate(
0,
max - min,
shrinkProgress);
return {
shown,
basic - max,
max - shrink }; // interpolate(max, min, shrinkProgress)
}
} else {
// _workFinished - _st.sineDuration .. _workFinished
const auto hidden = (now - (_workFinished - _st.sineDuration))
/ float64(_st.sineDuration);
const auto cycles = (_workFinished - _workStarted) / _st.sinePeriod;
const auto basic = int((linear
+ min
+ cycles * (kFullArcLength + min - max)) % kFullArcLength);
const auto length = anim::interpolate(
min,
kFullArcLength,
anim::sineInOut(1., std::clamp(hidden, 0., 1.)));
return {
1. - hidden,
basic - length,
length };
}
//const auto frontPeriods = time / st.sinePeriod;
//const auto frontCurrent = time % st.sinePeriod;
//const auto frontProgress = anim::sineInOut(
// st.arcMax - st.arcMin,
// std::min(frontCurrent, crl::time(st.sineDuration))
// / float64(st.sineDuration));
//const auto backTime = std::max(time - st.sineShift, 0LL);
//const auto backPeriods = backTime / st.sinePeriod;
//const auto backCurrent = backTime % st.sinePeriod;
//const auto backProgress = anim::sineInOut(
// st.arcMax - st.arcMin,
// std::min(backCurrent, crl::time(st.sineDuration))
// / float64(st.sineDuration));
//const auto front = linear + std::round((st.arcMin + frontProgress + frontPeriods * (st.arcMax - st.arcMin)) * kFullArcLength);
//const auto from = linear + std::round((backProgress + backPeriods * (st.arcMax - st.arcMin)) * kFullArcLength);
//const auto length = (front - from);
//return {
// _opacity,
// from,
// length
//};
}
} // namespace Ui

View file

@ -0,0 +1,110 @@
// This file is part of Desktop App Toolkit,
// a set of libraries for developing nice desktop applications.
//
// For license and copyright information please follow this link:
// https://github.com/desktop-app/legal/blob/master/LEGAL
//
#pragma once
#include "ui/effects/animations.h"
namespace style {
struct InfiniteRadialAnimation;
} // namespace style
namespace Ui {
struct RadialState {
static const int kFull;
float64 shown = 0.;
int arcFrom = 0;
int arcLength = kFull;
};
class RadialAnimation {
public:
template <typename Callback>
RadialAnimation(Callback &&callback);
float64 opacity() const {
return _opacity;
}
bool animating() const {
return _animation.animating();
}
void start(float64 prg);
bool update(float64 prg, bool finished, crl::time ms);
void stop();
void draw(
QPainter &p,
const QRect &inner,
int32 thickness,
style::color color) const;
RadialState computeState() const;
private:
crl::time _firstStart = 0;
crl::time _lastStart = 0;
crl::time _lastTime = 0;
float64 _opacity = 0.;
anim::value _arcEnd;
anim::value _arcStart;
Ui::Animations::Basic _animation;
bool _finished = false;
};
template <typename Callback>
inline RadialAnimation::RadialAnimation(Callback &&callback)
: _arcStart(0, RadialState::kFull)
, _animation(std::forward<Callback>(callback)) {
}
class InfiniteRadialAnimation {
public:
template <typename Callback>
InfiniteRadialAnimation(
Callback &&callback,
const style::InfiniteRadialAnimation &st);
bool animating() const {
return _animation.animating();
}
void start(crl::time skip = 0);
void stop(anim::type animated = anim::type::normal);
void draw(
QPainter &p,
QPoint position,
int outerWidth);
void draw(
QPainter &p,
QPoint position,
QSize size,
int outerWidth);
RadialState computeState();
private:
const style::InfiniteRadialAnimation &_st;
crl::time _workStarted = 0;
crl::time _workFinished = 0;
Ui::Animations::Basic _animation;
};
template <typename Callback>
inline InfiniteRadialAnimation::InfiniteRadialAnimation(
Callback &&callback,
const style::InfiniteRadialAnimation &st)
: _st(st)
, _animation(std::forward<Callback>(callback)) {
}
} // namespace Ui

254
ui/layers/box_content.cpp Normal file
View file

@ -0,0 +1,254 @@
// 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/layers/box_content.h"
#include "ui/widgets/buttons.h"
#include "ui/widgets/scroll_area.h"
#include "ui/widgets/labels.h"
#include "ui/widgets/shadow.h"
#include "ui/wrap/fade_wrap.h"
#include "ui/text/text_utilities.h"
#include "ui/painter.h"
#include "base/timer.h"
#include "styles/style_layers.h"
#include "styles/palette.h"
namespace Ui {
void BoxContent::setTitle(rpl::producer<QString> title) {
getDelegate()->setTitle(std::move(title) | Text::ToWithEntities());
}
QPointer<RoundButton> BoxContent::addButton(
rpl::producer<QString> text,
Fn<void()> clickCallback) {
return addButton(
std::move(text),
std::move(clickCallback),
st::defaultBoxButton);
}
QPointer<RoundButton> BoxContent::addLeftButton(
rpl::producer<QString> text,
Fn<void()> clickCallback) {
return getDelegate()->addLeftButton(
std::move(text),
std::move(clickCallback),
st::defaultBoxButton);
}
void BoxContent::setInner(object_ptr<TWidget> inner) {
setInner(std::move(inner), st::boxLayerScroll);
}
void BoxContent::setInner(object_ptr<TWidget> inner, const style::ScrollArea &st) {
if (inner) {
getDelegate()->setLayerType(true);
_scroll.create(this, st);
_scroll->setGeometryToLeft(0, _innerTopSkip, width(), 0);
_scroll->setOwnedWidget(std::move(inner));
if (_topShadow) {
_topShadow->raise();
_bottomShadow->raise();
} else {
_topShadow.create(this);
_bottomShadow.create(this);
}
if (!_preparing) {
// We didn't set dimensions yet, this will be called from finishPrepare();
finishScrollCreate();
}
} else {
getDelegate()->setLayerType(false);
_scroll.destroyDelayed();
_topShadow.destroyDelayed();
_bottomShadow.destroyDelayed();
}
}
void BoxContent::finishPrepare() {
_preparing = false;
if (_scroll) {
finishScrollCreate();
}
setInnerFocus();
}
void BoxContent::finishScrollCreate() {
Expects(_scroll != nullptr);
if (!_scroll->isHidden()) {
_scroll->show();
}
updateScrollAreaGeometry();
connect(_scroll, SIGNAL(scrolled()), this, SLOT(onScroll()));
connect(_scroll, SIGNAL(innerResized()), this, SLOT(onInnerResize()));
}
void BoxContent::scrollToWidget(not_null<QWidget*> widget) {
if (_scroll) {
_scroll->scrollToWidget(widget);
}
}
void BoxContent::onScrollToY(int top, int bottom) {
if (_scroll) {
_scroll->scrollToY(top, bottom);
}
}
void BoxContent::onDraggingScrollDelta(int delta) {
_draggingScrollDelta = _scroll ? delta : 0;
if (_draggingScrollDelta) {
if (!_draggingScrollTimer) {
_draggingScrollTimer.create(this);
_draggingScrollTimer->setSingleShot(false);
connect(_draggingScrollTimer, SIGNAL(timeout()), this, SLOT(onDraggingScrollTimer()));
}
_draggingScrollTimer->start(15);
} else {
_draggingScrollTimer.destroy();
}
}
void BoxContent::onDraggingScrollTimer() {
auto delta = (_draggingScrollDelta > 0) ? qMin(_draggingScrollDelta * 3 / 20 + 1, int32(kMaxScrollSpeed)) : qMax(_draggingScrollDelta * 3 / 20 - 1, -int32(kMaxScrollSpeed));
_scroll->scrollToY(_scroll->scrollTop() + delta);
}
void BoxContent::updateInnerVisibleTopBottom() {
if (auto widget = static_cast<TWidget*>(_scroll ? _scroll->widget() : nullptr)) {
auto top = _scroll->scrollTop();
widget->setVisibleTopBottom(top, top + _scroll->height());
}
}
void BoxContent::updateShadowsVisibility() {
if (!_scroll) return;
auto top = _scroll->scrollTop();
_topShadow->toggle(
(top > 0 || _innerTopSkip > 0),
anim::type::normal);
_bottomShadow->toggle(
(top < _scroll->scrollTopMax() || _innerBottomSkip > 0),
anim::type::normal);
}
void BoxContent::onScroll() {
updateInnerVisibleTopBottom();
updateShadowsVisibility();
}
void BoxContent::onInnerResize() {
updateInnerVisibleTopBottom();
updateShadowsVisibility();
}
void BoxContent::setDimensionsToContent(
int newWidth,
not_null<RpWidget*> content) {
content->resizeToWidth(newWidth);
content->heightValue(
) | rpl::start_with_next([=](int height) {
setDimensions(newWidth, height);
}, content->lifetime());
}
void BoxContent::setInnerTopSkip(int innerTopSkip, bool scrollBottomFixed) {
if (_innerTopSkip != innerTopSkip) {
auto delta = innerTopSkip - _innerTopSkip;
_innerTopSkip = innerTopSkip;
if (_scroll && width() > 0) {
auto scrollTopWas = _scroll->scrollTop();
updateScrollAreaGeometry();
if (scrollBottomFixed) {
_scroll->scrollToY(scrollTopWas + delta);
}
}
}
}
void BoxContent::setInnerBottomSkip(int innerBottomSkip) {
if (_innerBottomSkip != innerBottomSkip) {
auto delta = innerBottomSkip - _innerBottomSkip;
_innerBottomSkip = innerBottomSkip;
if (_scroll && width() > 0) {
updateScrollAreaGeometry();
}
}
}
void BoxContent::setInnerVisible(bool scrollAreaVisible) {
if (_scroll) {
_scroll->setVisible(scrollAreaVisible);
}
}
QPixmap BoxContent::grabInnerCache() {
auto isTopShadowVisible = !_topShadow->isHidden();
auto isBottomShadowVisible = !_bottomShadow->isHidden();
if (isTopShadowVisible) _topShadow->setVisible(false);
if (isBottomShadowVisible) _bottomShadow->setVisible(false);
auto result = GrabWidget(this, _scroll->geometry());
if (isTopShadowVisible) _topShadow->setVisible(true);
if (isBottomShadowVisible) _bottomShadow->setVisible(true);
return result;
}
void BoxContent::resizeEvent(QResizeEvent *e) {
if (_scroll) {
updateScrollAreaGeometry();
}
}
void BoxContent::keyPressEvent(QKeyEvent *e) {
if (e->key() == Qt::Key_Escape && !_closeByEscape) {
e->accept();
} else {
RpWidget::keyPressEvent(e);
}
}
void BoxContent::updateScrollAreaGeometry() {
auto newScrollHeight = height() - _innerTopSkip - _innerBottomSkip;
auto changed = (_scroll->height() != newScrollHeight);
_scroll->setGeometryToLeft(0, _innerTopSkip, width(), newScrollHeight);
_topShadow->entity()->resize(width(), st::lineWidth);
_topShadow->moveToLeft(0, _innerTopSkip);
_bottomShadow->entity()->resize(width(), st::lineWidth);
_bottomShadow->moveToLeft(
0,
height() - _innerBottomSkip - st::lineWidth);
if (changed) {
updateInnerVisibleTopBottom();
auto top = _scroll->scrollTop();
_topShadow->toggle(
(top > 0 || _innerTopSkip > 0),
anim::type::instant);
_bottomShadow->toggle(
(top < _scroll->scrollTopMax() || _innerBottomSkip > 0),
anim::type::instant);
}
}
object_ptr<TWidget> BoxContent::doTakeInnerWidget() {
return _scroll->takeWidget<TWidget>();
}
void BoxContent::paintEvent(QPaintEvent *e) {
Painter p(this);
if (testAttribute(Qt::WA_OpaquePaintEvent)) {
for (auto rect : e->region().rects()) {
p.fillRect(rect, st::boxBg);
}
}
}
} // namespace Ui

341
ui/layers/box_content.h Normal file
View file

@ -0,0 +1,341 @@
// 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/unique_qptr.h"
#include "base/flags.h"
#include "ui/wrap/padding_wrap.h"
#include "ui/widgets/labels.h"
#include "ui/layers/layer_widget.h"
#include "ui/effects/animation_value.h"
#include "ui/text/text_entity.h"
#include "ui/rp_widget.h"
namespace style {
struct RoundButton;
struct IconButton;
struct ScrollArea;
} // namespace style
namespace Ui {
class GenericBox;
} // namespace Ui
template <typename BoxType = Ui::GenericBox, typename ...Args>
inline object_ptr<BoxType> Box(Args &&...args) {
const auto parent = static_cast<QWidget*>(nullptr);
return object_ptr<BoxType>(parent, std::forward<Args>(args)...);
}
namespace Ui {
class RoundButton;
class IconButton;
class ScrollArea;
class FlatLabel;
class FadeShadow;
class BoxContent;
class BoxContentDelegate {
public:
virtual void setLayerType(bool layerType) = 0;
virtual void setTitle(rpl::producer<TextWithEntities> title) = 0;
virtual void setAdditionalTitle(rpl::producer<QString> additional) = 0;
virtual void setCloseByOutsideClick(bool close) = 0;
virtual void clearButtons() = 0;
virtual QPointer<RoundButton> addButton(
rpl::producer<QString> text,
Fn<void()> clickCallback,
const style::RoundButton &st) = 0;
virtual QPointer<RoundButton> addLeftButton(
rpl::producer<QString> text,
Fn<void()> clickCallback,
const style::RoundButton &st) = 0;
virtual QPointer<IconButton> addTopButton(
const style::IconButton &st,
Fn<void()> clickCallback) = 0;
virtual void showLoading(bool show) = 0;
virtual void updateButtonsPositions() = 0;
virtual void showBox(
object_ptr<BoxContent> box,
LayerOptions options,
anim::type animated) = 0;
virtual void setDimensions(
int newWidth,
int maxHeight,
bool forceCenterPosition = false) = 0;
virtual void setNoContentMargin(bool noContentMargin) = 0;
virtual bool isBoxShown() const = 0;
virtual void closeBox() = 0;
template <typename BoxType>
QPointer<BoxType> show(
object_ptr<BoxType> content,
LayerOptions options = LayerOption::KeepOther,
anim::type animated = anim::type::normal) {
auto result = QPointer<BoxType>(content.data());
showBox(std::move(content), options, animated);
return result;
}
virtual QPointer<QWidget> outerContainer() = 0;
};
class BoxContent : public RpWidget {
Q_OBJECT
public:
BoxContent() {
setAttribute(Qt::WA_OpaquePaintEvent);
}
bool isBoxShown() const {
return getDelegate()->isBoxShown();
}
void closeBox() {
getDelegate()->closeBox();
}
void setTitle(rpl::producer<QString> title);
void setTitle(rpl::producer<TextWithEntities> title) {
getDelegate()->setTitle(std::move(title));
}
void setAdditionalTitle(rpl::producer<QString> additional) {
getDelegate()->setAdditionalTitle(std::move(additional));
}
void setCloseByEscape(bool close) {
_closeByEscape = close;
}
void setCloseByOutsideClick(bool close) {
getDelegate()->setCloseByOutsideClick(close);
}
void scrollToWidget(not_null<QWidget*> widget);
void clearButtons() {
getDelegate()->clearButtons();
}
QPointer<RoundButton> addButton(
rpl::producer<QString> text,
Fn<void()> clickCallback = nullptr);
QPointer<RoundButton> addLeftButton(
rpl::producer<QString> text,
Fn<void()> clickCallback = nullptr);
QPointer<IconButton> addTopButton(
const style::IconButton &st,
Fn<void()> clickCallback = nullptr) {
return getDelegate()->addTopButton(st, std::move(clickCallback));
}
QPointer<RoundButton> addButton(
rpl::producer<QString> text,
const style::RoundButton &st) {
return getDelegate()->addButton(std::move(text), nullptr, st);
}
QPointer<RoundButton> addButton(
rpl::producer<QString> text,
Fn<void()> clickCallback,
const style::RoundButton &st) {
return getDelegate()->addButton(
std::move(text),
std::move(clickCallback),
st);
}
void showLoading(bool show) {
getDelegate()->showLoading(show);
}
void updateButtonsGeometry() {
getDelegate()->updateButtonsPositions();
}
virtual void setInnerFocus() {
setFocus();
}
rpl::producer<> boxClosing() const {
return _boxClosingStream.events();
}
void notifyBoxClosing() {
_boxClosingStream.fire({});
}
void setDelegate(not_null<BoxContentDelegate*> newDelegate) {
_delegate = newDelegate;
_preparing = true;
prepare();
finishPrepare();
}
not_null<BoxContentDelegate*> getDelegate() const {
return _delegate;
}
public slots:
void onScrollToY(int top, int bottom = -1);
void onDraggingScrollDelta(int delta);
protected:
virtual void prepare() = 0;
void setLayerType(bool layerType) {
getDelegate()->setLayerType(layerType);
}
void setNoContentMargin(bool noContentMargin) {
if (_noContentMargin != noContentMargin) {
_noContentMargin = noContentMargin;
setAttribute(Qt::WA_OpaquePaintEvent, !_noContentMargin);
}
getDelegate()->setNoContentMargin(noContentMargin);
}
void setDimensions(
int newWidth,
int maxHeight,
bool forceCenterPosition = false) {
getDelegate()->setDimensions(
newWidth,
maxHeight,
forceCenterPosition);
}
void setDimensionsToContent(
int newWidth,
not_null<RpWidget*> content);
void setInnerTopSkip(int topSkip, bool scrollBottomFixed = false);
void setInnerBottomSkip(int bottomSkip);
template <typename Widget>
QPointer<Widget> setInnerWidget(
object_ptr<Widget> inner,
const style::ScrollArea &st,
int topSkip = 0,
int bottomSkip = 0) {
auto result = QPointer<Widget>(inner.data());
setInnerTopSkip(topSkip);
setInnerBottomSkip(bottomSkip);
setInner(std::move(inner), st);
return result;
}
template <typename Widget>
QPointer<Widget> setInnerWidget(
object_ptr<Widget> inner,
int topSkip = 0,
int bottomSkip = 0) {
auto result = QPointer<Widget>(inner.data());
setInnerTopSkip(topSkip);
setInnerBottomSkip(bottomSkip);
setInner(std::move(inner));
return result;
}
template <typename Widget>
object_ptr<Widget> takeInnerWidget() {
return object_ptr<Widget>::fromRaw(
static_cast<Widget*>(doTakeInnerWidget().release()));
}
void setInnerVisible(bool scrollAreaVisible);
QPixmap grabInnerCache();
void resizeEvent(QResizeEvent *e) override;
void paintEvent(QPaintEvent *e) override;
void keyPressEvent(QKeyEvent *e) override;
private slots:
void onScroll();
void onInnerResize();
void onDraggingScrollTimer();
private:
void finishPrepare();
void finishScrollCreate();
void setInner(object_ptr<TWidget> inner);
void setInner(object_ptr<TWidget> inner, const style::ScrollArea &st);
void updateScrollAreaGeometry();
void updateInnerVisibleTopBottom();
void updateShadowsVisibility();
object_ptr<TWidget> doTakeInnerWidget();
BoxContentDelegate *_delegate = nullptr;
bool _preparing = false;
bool _noContentMargin = false;
bool _closeByEscape = true;
int _innerTopSkip = 0;
int _innerBottomSkip = 0;
object_ptr<ScrollArea> _scroll = { nullptr };
object_ptr<FadeShadow> _topShadow = { nullptr };
object_ptr<FadeShadow> _bottomShadow = { nullptr };
object_ptr<QTimer> _draggingScrollTimer = { nullptr };
int _draggingScrollDelta = 0;
rpl::event_stream<> _boxClosingStream;
};
class BoxPointer {
public:
BoxPointer() = default;
BoxPointer(const BoxPointer &other) = default;
BoxPointer(BoxPointer &&other) : _value(base::take(other._value)) {
}
BoxPointer(BoxContent *value) : _value(value) {
}
BoxPointer &operator=(const BoxPointer &other) {
if (_value != other._value) {
destroy();
_value = other._value;
}
return *this;
}
BoxPointer &operator=(BoxPointer &&other) {
if (_value != other._value) {
destroy();
_value = base::take(other._value);
}
return *this;
}
BoxPointer &operator=(BoxContent *other) {
if (_value != other) {
destroy();
_value = other;
}
return *this;
}
~BoxPointer() {
destroy();
}
BoxContent *get() const {
return _value.data();
}
operator BoxContent*() const {
return get();
}
explicit operator bool() const {
return get();
}
BoxContent *operator->() const {
return get();
}
private:
void destroy() {
if (const auto value = base::take(_value)) {
value->closeBox();
}
}
QPointer<BoxContent> _value;
};
} // namespace Ui

View file

@ -0,0 +1,351 @@
// 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/layers/box_layer_widget.h"
#include "ui/effects/radial_animation.h"
#include "ui/widgets/buttons.h"
#include "ui/widgets/scroll_area.h"
#include "ui/widgets/labels.h"
#include "ui/widgets/shadow.h"
#include "ui/wrap/fade_wrap.h"
#include "ui/text/text_utilities.h"
#include "ui/image/image_prepare.h"
#include "ui/painter.h"
#include "base/timer.h"
#include "styles/style_layers.h"
#include "styles/palette.h"
namespace Ui {
struct BoxLayerWidget::LoadingProgress {
LoadingProgress(
Fn<void()> &&callback,
const style::InfiniteRadialAnimation &st);
InfiniteRadialAnimation animation;
base::Timer removeTimer;
};
BoxLayerWidget::LoadingProgress::LoadingProgress(
Fn<void()> &&callback,
const style::InfiniteRadialAnimation &st)
: animation(std::move(callback), st) {
}
BoxLayerWidget::BoxLayerWidget(
not_null<LayerStackWidget*> layer,
object_ptr<BoxContent> content)
: LayerWidget(layer)
, _layer(layer)
, _content(std::move(content))
, _roundRect(ImageRoundRadius::Small, st::boxBg) {
_content->setParent(this);
_content->setDelegate(this);
_additionalTitle.changes(
) | rpl::start_with_next([=] {
updateSize();
update();
}, lifetime());
}
BoxLayerWidget::~BoxLayerWidget() = default;
void BoxLayerWidget::setLayerType(bool layerType) {
_layerType = layerType;
updateTitlePosition();
}
int BoxLayerWidget::titleHeight() const {
return _layerType ? st::boxLayerTitleHeight : st::boxTitleHeight;
}
int BoxLayerWidget::buttonsHeight() const {
const auto padding = _layerType
? st::boxLayerButtonPadding
: st::boxButtonPadding;
return padding.top() + st::defaultBoxButton.height + padding.bottom();
}
int BoxLayerWidget::buttonsTop() const {
const auto padding = _layerType
? st::boxLayerButtonPadding
: st::boxButtonPadding;
return height() - padding.bottom() - st::defaultBoxButton.height;
}
QRect BoxLayerWidget::loadingRect() const {
const auto padding = _layerType
? st::boxLayerButtonPadding
: st::boxButtonPadding;
const auto size = st::boxLoadingSize;
const auto skipx = _layerType
? st::boxLayerTitlePosition.x()
: st::boxTitlePosition.x();
const auto skipy = (st::defaultBoxButton.height - size) / 2;
return QRect(
skipx,
height() - padding.bottom() - skipy - size,
size,
size);
}
void BoxLayerWidget::paintEvent(QPaintEvent *e) {
Painter p(this);
auto clip = e->rect();
auto paintTopRounded = clip.intersects(QRect(0, 0, width(), st::boxRadius));
auto paintBottomRounded = clip.intersects(QRect(0, height() - st::boxRadius, width(), st::boxRadius));
if (paintTopRounded || paintBottomRounded) {
auto parts = RectPart::None | 0;
if (paintTopRounded) parts |= RectPart::FullTop;
if (paintBottomRounded) parts |= RectPart::FullBottom;
_roundRect.paint(p, rect(), parts);
}
auto other = e->region().intersected(QRect(0, st::boxRadius, width(), height() - 2 * st::boxRadius));
if (!other.isEmpty()) {
for (auto rect : other.rects()) {
p.fillRect(rect, st::boxBg);
}
}
if (!_additionalTitle.current().isEmpty()
&& clip.intersects(QRect(0, 0, width(), titleHeight()))) {
paintAdditionalTitle(p);
}
if (_loadingProgress) {
const auto rect = loadingRect();
_loadingProgress->animation.draw(
p,
rect.topLeft(),
rect.size(),
width());
}
}
void BoxLayerWidget::paintAdditionalTitle(Painter &p) {
p.setFont(st::boxLayerTitleAdditionalFont);
p.setPen(st::boxTitleAdditionalFg);
p.drawTextLeft(_titleLeft + (_title ? _title->width() : 0) + st::boxLayerTitleAdditionalSkip, _titleTop + st::boxTitleFont->ascent - st::boxLayerTitleAdditionalFont->ascent, width(), _additionalTitle.current());
}
void BoxLayerWidget::parentResized() {
auto newHeight = countRealHeight();
auto parentSize = parentWidget()->size();
setGeometry((parentSize.width() - width()) / 2, (parentSize.height() - newHeight) / 2, width(), newHeight);
update();
}
void BoxLayerWidget::setTitle(rpl::producer<TextWithEntities> title) {
const auto wasTitle = hasTitle();
if (title) {
_title.create(this, std::move(title), st::boxTitle);
_title->show();
updateTitlePosition();
} else {
_title.destroy();
}
if (wasTitle != hasTitle()) {
updateSize();
}
}
void BoxLayerWidget::setAdditionalTitle(rpl::producer<QString> additional) {
_additionalTitle = std::move(additional);
}
void BoxLayerWidget::setCloseByOutsideClick(bool close) {
_closeByOutsideClick = close;
}
bool BoxLayerWidget::closeByOutsideClick() const {
return _closeByOutsideClick;
}
bool BoxLayerWidget::hasTitle() const {
return (_title != nullptr) || !_additionalTitle.current().isEmpty();
}
void BoxLayerWidget::showBox(
object_ptr<BoxContent> box,
LayerOptions options,
anim::type animated) {
_layer->showBox(std::move(box), options, animated);
}
void BoxLayerWidget::updateSize() {
setDimensions(width(), _maxContentHeight);
}
void BoxLayerWidget::updateButtonsPositions() {
if (!_buttons.empty() || _leftButton) {
auto padding = _layerType ? st::boxLayerButtonPadding : st::boxButtonPadding;
auto right = padding.right();
auto top = buttonsTop();
if (_leftButton) {
_leftButton->moveToLeft(right, top);
}
for (const auto &button : _buttons) {
button->moveToRight(right, top);
right += button->width() + padding.left();
}
}
if (_topButton) {
_topButton->moveToRight(0, 0);
}
}
QPointer<QWidget> BoxLayerWidget::outerContainer() {
return parentWidget();
}
void BoxLayerWidget::updateTitlePosition() {
_titleLeft = _layerType ? st::boxLayerTitlePosition.x() : st::boxTitlePosition.x();
_titleTop = _layerType ? st::boxLayerTitlePosition.y() : st::boxTitlePosition.y();
if (_title) {
_title->resizeToWidth(qMin(_title->naturalWidth(), width() - _titleLeft * 2));
_title->moveToLeft(_titleLeft, _titleTop);
}
}
void BoxLayerWidget::clearButtons() {
for (auto &button : base::take(_buttons)) {
button.destroy();
}
_leftButton.destroy();
_topButton = nullptr;
}
QPointer<RoundButton> BoxLayerWidget::addButton(
rpl::producer<QString> text,
Fn<void()> clickCallback,
const style::RoundButton &st) {
_buttons.emplace_back(this, std::move(text), st);
auto result = QPointer<RoundButton>(_buttons.back());
result->setClickedCallback(std::move(clickCallback));
result->show();
result->widthValue(
) | rpl::start_with_next([=] {
updateButtonsPositions();
}, result->lifetime());
return result;
}
QPointer<RoundButton> BoxLayerWidget::addLeftButton(
rpl::producer<QString> text,
Fn<void()> clickCallback,
const style::RoundButton &st) {
_leftButton = object_ptr<RoundButton>(this, std::move(text), st);
auto result = QPointer<RoundButton>(_leftButton);
result->setClickedCallback(std::move(clickCallback));
result->show();
result->widthValue(
) | rpl::start_with_next([=] {
updateButtonsPositions();
}, result->lifetime());
return result;
}
QPointer<IconButton> BoxLayerWidget::addTopButton(const style::IconButton &st, Fn<void()> clickCallback) {
_topButton = base::make_unique_q<IconButton>(this, st);
auto result = QPointer<IconButton>(_topButton.get());
result->setClickedCallback(std::move(clickCallback));
result->show();
updateButtonsPositions();
return result;
}
void BoxLayerWidget::showLoading(bool show) {
const auto &st = st::boxLoadingAnimation;
if (!show) {
if (_loadingProgress && !_loadingProgress->removeTimer.isActive()) {
_loadingProgress->removeTimer.callOnce(
st.sineDuration + st.sinePeriod);
_loadingProgress->animation.stop();
}
return;
}
if (!_loadingProgress) {
const auto callback = [=] {
if (!anim::Disabled()) {
const auto t = st::boxLoadingAnimation.thickness;
update(loadingRect().marginsAdded({ t, t, t, t }));
}
};
_loadingProgress = std::make_unique<LoadingProgress>(
callback,
st::boxLoadingAnimation);
_loadingProgress->removeTimer.setCallback([=] {
_loadingProgress = nullptr;
});
} else {
_loadingProgress->removeTimer.cancel();
}
_loadingProgress->animation.start();
}
void BoxLayerWidget::setDimensions(int newWidth, int maxHeight, bool forceCenterPosition) {
_maxContentHeight = maxHeight;
auto fullHeight = countFullHeight();
if (width() != newWidth || _fullHeight != fullHeight) {
_fullHeight = fullHeight;
if (parentWidget()) {
auto oldGeometry = geometry();
resize(newWidth, countRealHeight());
auto newGeometry = geometry();
auto parentHeight = parentWidget()->height();
if (newGeometry.top() + newGeometry.height() + st::boxVerticalMargin > parentHeight
|| forceCenterPosition) {
const auto top1 = parentHeight - int(st::boxVerticalMargin) - newGeometry.height();
const auto top2 = (parentHeight - newGeometry.height()) / 2;
const auto newTop = forceCenterPosition
? std::min(top1, top2)
: std::max(top1, top2);
if (newTop != newGeometry.top()) {
move(newGeometry.left(), newTop);
resizeEvent(0);
}
}
parentWidget()->update(oldGeometry.united(geometry()).marginsAdded(st::boxRoundShadow.extend));
} else {
resize(newWidth, 0);
}
}
}
int BoxLayerWidget::countRealHeight() const {
return qMin(_fullHeight, parentWidget()->height() - 2 * st::boxVerticalMargin);
}
int BoxLayerWidget::countFullHeight() const {
return contentTop() + _maxContentHeight + buttonsHeight();
}
int BoxLayerWidget::contentTop() const {
return hasTitle() ? titleHeight() : (_noContentMargin ? 0 : st::boxTopMargin);
}
void BoxLayerWidget::resizeEvent(QResizeEvent *e) {
updateButtonsPositions();
updateTitlePosition();
auto top = contentTop();
_content->resize(width(), height() - top - buttonsHeight());
_content->moveToLeft(0, top);
LayerWidget::resizeEvent(e);
}
void BoxLayerWidget::keyPressEvent(QKeyEvent *e) {
if (e->key() == Qt::Key_Escape) {
closeBox();
} else {
LayerWidget::keyPressEvent(e);
}
}
} // namespace Ui

View file

@ -0,0 +1,142 @@
// 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/unique_qptr.h"
#include "base/flags.h"
#include "ui/layers/layer_widget.h"
#include "ui/layers/box_content.h"
#include "ui/wrap/padding_wrap.h"
#include "ui/widgets/labels.h"
#include "ui/effects/animation_value.h"
#include "ui/text/text_entity.h"
#include "ui/round_rect.h"
#include "ui/rp_widget.h"
class Painter;
namespace style {
struct RoundButton;
struct IconButton;
struct ScrollArea;
} // namespace style
namespace Ui {
class RoundButton;
class IconButton;
class ScrollArea;
class FlatLabel;
class FadeShadow;
class BoxLayerWidget : public LayerWidget, public BoxContentDelegate {
public:
BoxLayerWidget(
not_null<LayerStackWidget*> layer,
object_ptr<BoxContent> content);
~BoxLayerWidget();
void parentResized() override;
void setLayerType(bool layerType) override;
void setTitle(rpl::producer<TextWithEntities> title) override;
void setAdditionalTitle(rpl::producer<QString> additional) override;
void showBox(
object_ptr<BoxContent> box,
LayerOptions options,
anim::type animated) override;
void clearButtons() override;
QPointer<RoundButton> addButton(
rpl::producer<QString> text,
Fn<void()> clickCallback,
const style::RoundButton &st) override;
QPointer<RoundButton> addLeftButton(
rpl::producer<QString> text,
Fn<void()> clickCallback,
const style::RoundButton &st) override;
QPointer<IconButton> addTopButton(
const style::IconButton &st,
Fn<void()> clickCallback) override;
void showLoading(bool show) override;
void updateButtonsPositions() override;
QPointer<QWidget> outerContainer() override;
void setDimensions(
int newWidth,
int maxHeight,
bool forceCenterPosition = false) override;
void setNoContentMargin(bool noContentMargin) override {
if (_noContentMargin != noContentMargin) {
_noContentMargin = noContentMargin;
updateSize();
}
}
bool isBoxShown() const override {
return !isHidden();
}
void closeBox() override {
closeLayer();
}
void setCloseByOutsideClick(bool close) override;
bool closeByOutsideClick() const override;
protected:
void keyPressEvent(QKeyEvent *e) override;
void resizeEvent(QResizeEvent *e) override;
void paintEvent(QPaintEvent *e) override;
void doSetInnerFocus() override {
_content->setInnerFocus();
}
void closeHook() override {
_content->notifyBoxClosing();
}
private:
struct LoadingProgress;
void paintAdditionalTitle(Painter &p);
void updateTitlePosition();
[[nodiscard]] bool hasTitle() const;
[[nodiscard]] int titleHeight() const;
[[nodiscard]] int buttonsHeight() const;
[[nodiscard]] int buttonsTop() const;
[[nodiscard]] int contentTop() const;
[[nodiscard]] int countFullHeight() const;
[[nodiscard]] int countRealHeight() const;
[[nodiscard]] QRect loadingRect() const;
void updateSize();
not_null<LayerStackWidget*> _layer;
int _fullHeight = 0;
bool _noContentMargin = false;
int _maxContentHeight = 0;
object_ptr<BoxContent> _content;
RoundRect _roundRect;
object_ptr<FlatLabel> _title = { nullptr };
Fn<TextWithEntities()> _titleFactory;
rpl::variable<QString> _additionalTitle;
int _titleLeft = 0;
int _titleTop = 0;
bool _layerType = false;
bool _closeByOutsideClick = true;
std::vector<object_ptr<RoundButton>> _buttons;
object_ptr<RoundButton> _leftButton = { nullptr };
base::unique_qptr<IconButton> _topButton = { nullptr };
std::unique_ptr<LoadingProgress> _loadingProgress;
};
} // namespace Ui

28
ui/layers/generic_box.cpp Normal file
View file

@ -0,0 +1,28 @@
// 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/layers/generic_box.h"
#include "ui/wrap/vertical_layout.h"
#include "ui/wrap/padding_wrap.h"
#include "ui/wrap/wrap.h"
#include "styles/style_layers.h"
namespace Ui {
void GenericBox::prepare() {
_init(this);
auto wrap = object_ptr<Ui::OverrideMargins>(this, std::move(_content));
setDimensionsToContent(_width ? _width : st::boxWidth, wrap.data());
setInnerWidget(std::move(wrap));
}
void GenericBox::addSkip(int height) {
addRow(object_ptr<Ui::FixedHeightWidget>(this, height));
}
} // namespace Ui

157
ui/layers/generic_box.h Normal file
View file

@ -0,0 +1,157 @@
// 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/layers/box_content.h"
#include "ui/wrap/vertical_layout.h"
#include <tuple>
namespace st {
extern const style::margins &boxRowPadding;
} // namespace st
namespace Ui {
class GenericBox : public BoxContent {
public:
// InitMethod::operator()(not_null<GenericBox*> box, InitArgs...)
// init(box, args...)
template <
typename InitMethod,
typename ...InitArgs,
typename = decltype(std::declval<std::decay_t<InitMethod>>()(
std::declval<not_null<GenericBox*>>(),
std::declval<std::decay_t<InitArgs>>()...))>
GenericBox(
QWidget*,
InitMethod &&init,
InitArgs &&...args);
void setWidth(int width) {
_width = width;
}
void setFocusCallback(Fn<void()> callback) {
_focus = callback;
}
int rowsCount() const {
return _content->count();
}
template <
typename Widget,
typename = std::enable_if_t<
std::is_base_of_v<RpWidget, Widget>>>
Widget *insertRow(
int atPosition,
object_ptr<Widget> &&child,
const style::margins &margin = st::boxRowPadding) {
return _content->insert(
atPosition,
std::move(child),
margin);
}
template <
typename Widget,
typename = std::enable_if_t<
std::is_base_of_v<RpWidget, Widget>>>
Widget *addRow(
object_ptr<Widget> &&child,
const style::margins &margin = st::boxRowPadding) {
return _content->add(std::move(child), margin);
}
void addSkip(int height);
void setInnerFocus() override {
if (_focus) {
_focus();
}
}
protected:
void prepare() override;
private:
template <typename InitMethod, typename ...InitArgs>
struct Initer {
template <
typename OtherMethod,
typename ...OtherArgs,
typename = std::enable_if_t<
std::is_constructible_v<InitMethod, OtherMethod&&>>>
Initer(OtherMethod &&method, OtherArgs &&...args);
void operator()(not_null<GenericBox*> box);
template <std::size_t... I>
void call(
not_null<GenericBox*> box,
std::index_sequence<I...>);
InitMethod method;
std::tuple<InitArgs...> args;
};
template <typename InitMethod, typename ...InitArgs>
auto MakeIniter(InitMethod &&method, InitArgs &&...args)
-> Initer<std::decay_t<InitMethod>, std::decay_t<InitArgs>...>;
FnMut<void(not_null<GenericBox*>)> _init;
Fn<void()> _focus;
object_ptr<Ui::VerticalLayout> _content;
int _width = 0;
};
template <typename InitMethod, typename ...InitArgs>
template <typename OtherMethod, typename ...OtherArgs, typename>
GenericBox::Initer<InitMethod, InitArgs...>::Initer(
OtherMethod &&method,
OtherArgs &&...args)
: method(std::forward<OtherMethod>(method))
, args(std::forward<OtherArgs>(args)...) {
}
template <typename InitMethod, typename ...InitArgs>
inline void GenericBox::Initer<InitMethod, InitArgs...>::operator()(
not_null<GenericBox*> box) {
call(box, std::make_index_sequence<sizeof...(InitArgs)>());
}
template <typename InitMethod, typename ...InitArgs>
template <std::size_t... I>
inline void GenericBox::Initer<InitMethod, InitArgs...>::call(
not_null<GenericBox*> box,
std::index_sequence<I...>) {
std::invoke(method, box, std::get<I>(std::move(args))...);
}
template <typename InitMethod, typename ...InitArgs>
inline auto GenericBox::MakeIniter(InitMethod &&method, InitArgs &&...args)
-> Initer<std::decay_t<InitMethod>, std::decay_t<InitArgs>...> {
return {
std::forward<InitMethod>(method),
std::forward<InitArgs>(args)...
};
}
template <typename InitMethod, typename ...InitArgs, typename>
inline GenericBox::GenericBox(
QWidget*,
InitMethod &&init,
InitArgs &&...args)
: _init(
MakeIniter(
std::forward<InitMethod>(init),
std::forward<InitArgs>(args)...))
, _content(this) {
}
} // namespace Ui

856
ui/layers/layer_widget.cpp Normal file
View file

@ -0,0 +1,856 @@
// 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/layers/layer_widget.h"
#include "ui/layers/box_layer_widget.h"
#include "ui/widgets/shadow.h"
#include "ui/image/image_prepare.h"
#include "ui/ui_utility.h"
#include "ui/round_rect.h"
#include "styles/style_layers.h"
#include "styles/style_widgets.h"
#include "styles/palette.h"
#include <QtGui/QtEvents>
namespace Ui {
class LayerStackWidget::BackgroundWidget : public TWidget {
public:
explicit BackgroundWidget(QWidget *parent);
void setDoneCallback(Fn<void()> callback) {
_doneCallback = std::move(callback);
}
void setLayerBoxes(const QRect &specialLayerBox, const QRect &layerBox);
void setCacheImages(
QPixmap &&bodyCache,
QPixmap &&mainMenuCache,
QPixmap &&specialLayerCache,
QPixmap &&layerCache);
void removeBodyCache();
void startAnimation(Action action);
void skipAnimation(Action action);
void finishAnimating();
bool animating() const {
return _a_mainMenuShown.animating() || _a_specialLayerShown.animating() || _a_layerShown.animating();
}
protected:
void paintEvent(QPaintEvent *e) override;
private:
bool isShown() const {
return _mainMenuShown || _specialLayerShown || _layerShown;
}
void checkIfDone();
void setMainMenuShown(bool shown);
void setSpecialLayerShown(bool shown);
void setLayerShown(bool shown);
void checkWasShown(bool wasShown);
void animationCallback();
QPixmap _bodyCache;
QPixmap _mainMenuCache;
int _mainMenuCacheWidth = 0;
QPixmap _specialLayerCache;
QPixmap _layerCache;
RoundRect _roundRect;
Fn<void()> _doneCallback;
bool _wasAnimating = false;
bool _inPaintEvent = false;
Ui::Animations::Simple _a_shown;
Ui::Animations::Simple _a_mainMenuShown;
Ui::Animations::Simple _a_specialLayerShown;
Ui::Animations::Simple _a_layerShown;
QRect _specialLayerBox, _specialLayerCacheBox;
QRect _layerBox, _layerCacheBox;
int _mainMenuRight = 0;
bool _mainMenuShown = false;
bool _specialLayerShown = false;
bool _layerShown = false;
};
LayerStackWidget::BackgroundWidget::BackgroundWidget(QWidget *parent)
: TWidget(parent)
, _roundRect(ImageRoundRadius::Small, st::boxBg) {
}
void LayerStackWidget::BackgroundWidget::setCacheImages(
QPixmap &&bodyCache,
QPixmap &&mainMenuCache,
QPixmap &&specialLayerCache,
QPixmap &&layerCache) {
_bodyCache = std::move(bodyCache);
_mainMenuCache = std::move(mainMenuCache);
_specialLayerCache = std::move(specialLayerCache);
_layerCache = std::move(layerCache);
_specialLayerCacheBox = _specialLayerBox;
_layerCacheBox = _layerBox;
setAttribute(Qt::WA_OpaquePaintEvent, !_bodyCache.isNull());
}
void LayerStackWidget::BackgroundWidget::removeBodyCache() {
if (!_bodyCache.isNull()) {
_bodyCache = {};
setAttribute(Qt::WA_OpaquePaintEvent, false);
}
}
void LayerStackWidget::BackgroundWidget::startAnimation(Action action) {
if (action == Action::ShowMainMenu) {
setMainMenuShown(true);
} else if (action != Action::HideLayer
&& action != Action::HideSpecialLayer) {
setMainMenuShown(false);
}
if (action == Action::ShowSpecialLayer) {
setSpecialLayerShown(true);
} else if (action == Action::ShowMainMenu
|| action == Action::HideAll
|| action == Action::HideSpecialLayer) {
setSpecialLayerShown(false);
}
if (action == Action::ShowLayer) {
setLayerShown(true);
} else if (action != Action::ShowSpecialLayer
&& action != Action::HideSpecialLayer) {
setLayerShown(false);
}
_wasAnimating = true;
checkIfDone();
}
void LayerStackWidget::BackgroundWidget::skipAnimation(Action action) {
startAnimation(action);
finishAnimating();
}
void LayerStackWidget::BackgroundWidget::checkIfDone() {
if (!_wasAnimating || _inPaintEvent || animating()) {
return;
}
_wasAnimating = false;
_mainMenuCache = _specialLayerCache = _layerCache = QPixmap();
removeBodyCache();
if (_doneCallback) {
_doneCallback();
}
}
void LayerStackWidget::BackgroundWidget::setMainMenuShown(bool shown) {
auto wasShown = isShown();
if (_mainMenuShown != shown) {
_mainMenuShown = shown;
_a_mainMenuShown.start([this] { animationCallback(); }, _mainMenuShown ? 0. : 1., _mainMenuShown ? 1. : 0., st::boxDuration, anim::easeOutCirc);
}
_mainMenuCacheWidth = (_mainMenuCache.width() / style::DevicePixelRatio())
- st::boxRoundShadow.extend.right();
_mainMenuRight = _mainMenuShown ? _mainMenuCacheWidth : 0;
checkWasShown(wasShown);
}
void LayerStackWidget::BackgroundWidget::setSpecialLayerShown(bool shown) {
auto wasShown = isShown();
if (_specialLayerShown != shown) {
_specialLayerShown = shown;
_a_specialLayerShown.start([this] { animationCallback(); }, _specialLayerShown ? 0. : 1., _specialLayerShown ? 1. : 0., st::boxDuration);
}
checkWasShown(wasShown);
}
void LayerStackWidget::BackgroundWidget::setLayerShown(bool shown) {
auto wasShown = isShown();
if (_layerShown != shown) {
_layerShown = shown;
_a_layerShown.start([this] { animationCallback(); }, _layerShown ? 0. : 1., _layerShown ? 1. : 0., st::boxDuration);
}
checkWasShown(wasShown);
}
void LayerStackWidget::BackgroundWidget::checkWasShown(bool wasShown) {
if (isShown() != wasShown) {
_a_shown.start([this] { animationCallback(); }, wasShown ? 1. : 0., wasShown ? 0. : 1., st::boxDuration, anim::easeOutCirc);
}
}
void LayerStackWidget::BackgroundWidget::setLayerBoxes(const QRect &specialLayerBox, const QRect &layerBox) {
_specialLayerBox = specialLayerBox;
_layerBox = layerBox;
update();
}
void LayerStackWidget::BackgroundWidget::paintEvent(QPaintEvent *e) {
Painter p(this);
_inPaintEvent = true;
auto guard = gsl::finally([this] {
_inPaintEvent = false;
crl::on_main(this, [=] { checkIfDone(); });
});
if (!_bodyCache.isNull()) {
p.drawPixmap(0, 0, _bodyCache);
}
auto specialLayerBox = _specialLayerCache.isNull() ? _specialLayerBox : _specialLayerCacheBox;
auto layerBox = _layerCache.isNull() ? _layerBox : _layerCacheBox;
auto mainMenuProgress = _a_mainMenuShown.value(-1);
auto mainMenuRight = (_mainMenuCache.isNull() || mainMenuProgress < 0) ? _mainMenuRight : (mainMenuProgress < 0) ? _mainMenuRight : anim::interpolate(0, _mainMenuCacheWidth, mainMenuProgress);
if (mainMenuRight) {
// Move showing boxes to the right while main menu is hiding.
if (!_specialLayerCache.isNull()) {
specialLayerBox.moveLeft(specialLayerBox.left() + mainMenuRight / 2);
}
if (!_layerCache.isNull()) {
layerBox.moveLeft(layerBox.left() + mainMenuRight / 2);
}
}
auto bgOpacity = _a_shown.value(isShown() ? 1. : 0.);
auto specialLayerOpacity = _a_specialLayerShown.value(_specialLayerShown ? 1. : 0.);
auto layerOpacity = _a_layerShown.value(_layerShown ? 1. : 0.);
if (bgOpacity == 0.) {
return;
}
p.setOpacity(bgOpacity);
auto overSpecialOpacity = (layerOpacity * specialLayerOpacity);
auto bg = myrtlrect(mainMenuRight, 0, width() - mainMenuRight, height());
if (_mainMenuCache.isNull() && mainMenuRight > 0) {
// All cache images are taken together with their shadows,
// so we paint shadow only when there is no cache.
Ui::Shadow::paint(p, myrtlrect(0, 0, mainMenuRight, height()), width(), st::boxRoundShadow, RectPart::Right);
}
if (_specialLayerCache.isNull() && !specialLayerBox.isEmpty()) {
// All cache images are taken together with their shadows,
// so we paint shadow only when there is no cache.
auto sides = RectPart::Left | RectPart::Right;
auto topCorners = (specialLayerBox.y() > 0);
auto bottomCorners = (specialLayerBox.y() + specialLayerBox.height() < height());
if (topCorners) {
sides |= RectPart::Top;
}
if (bottomCorners) {
sides |= RectPart::Bottom;
}
if (topCorners || bottomCorners) {
p.setClipRegion(QRegion(rect()) - specialLayerBox.marginsRemoved(QMargins(st::boxRadius, 0, st::boxRadius, 0)) - specialLayerBox.marginsRemoved(QMargins(0, st::boxRadius, 0, st::boxRadius)));
}
Ui::Shadow::paint(p, specialLayerBox, width(), st::boxRoundShadow, sides);
if (topCorners || bottomCorners) {
// In case of painting the shadow above the special layer we get
// glitches in the corners, so we need to paint the corners once more.
p.setClipping(false);
auto parts = (topCorners ? (RectPart::TopLeft | RectPart::TopRight) : RectPart::None)
| (bottomCorners ? (RectPart::BottomLeft | RectPart::BottomRight) : RectPart::None);
_roundRect.paint(p, specialLayerBox, parts);
}
}
if (!layerBox.isEmpty() && !_specialLayerCache.isNull() && overSpecialOpacity < bgOpacity) {
// In case of moving special layer below the background while showing a box
// we need to fill special layer rect below its cache with a complex opacity
// (alpha_final - alpha_current) / (1 - alpha_current) so we won't get glitches
// in the transparent special layer cache corners after filling special layer
// rect above its cache with alpha_current opacity.
auto region = QRegion(bg) - specialLayerBox;
for (auto rect : region.rects()) {
p.fillRect(rect, st::layerBg);
}
p.setOpacity((bgOpacity - overSpecialOpacity) / (1. - (overSpecialOpacity * st::layerBg->c.alphaF())));
p.fillRect(specialLayerBox, st::layerBg);
p.setOpacity(bgOpacity);
} else {
p.fillRect(bg, st::layerBg);
}
if (!_specialLayerCache.isNull() && specialLayerOpacity > 0) {
p.setOpacity(specialLayerOpacity);
auto cacheLeft = specialLayerBox.x() - st::boxRoundShadow.extend.left();
auto cacheTop = specialLayerBox.y() - (specialLayerBox.y() > 0 ? st::boxRoundShadow.extend.top() : 0);
p.drawPixmapLeft(cacheLeft, cacheTop, width(), _specialLayerCache);
}
if (!layerBox.isEmpty()) {
if (!_specialLayerCache.isNull()) {
p.setOpacity(overSpecialOpacity);
p.fillRect(specialLayerBox, st::layerBg);
}
if (_layerCache.isNull()) {
p.setOpacity(layerOpacity);
Ui::Shadow::paint(p, layerBox, width(), st::boxRoundShadow);
}
}
if (!_layerCache.isNull() && layerOpacity > 0) {
p.setOpacity(layerOpacity);
p.drawPixmapLeft(layerBox.topLeft() - QPoint(st::boxRoundShadow.extend.left(), st::boxRoundShadow.extend.top()), width(), _layerCache);
}
if (!_mainMenuCache.isNull() && mainMenuRight > 0) {
p.setOpacity(1.);
auto shownWidth = mainMenuRight + st::boxRoundShadow.extend.right();
auto sourceWidth = shownWidth * style::DevicePixelRatio();
auto sourceRect = style::rtlrect(_mainMenuCache.width() - sourceWidth, 0, sourceWidth, _mainMenuCache.height(), _mainMenuCache.width());
p.drawPixmapLeft(0, 0, shownWidth, height(), width(), _mainMenuCache, sourceRect);
}
}
void LayerStackWidget::BackgroundWidget::finishAnimating() {
_a_shown.stop();
_a_mainMenuShown.stop();
_a_specialLayerShown.stop();
_a_layerShown.stop();
checkIfDone();
}
void LayerStackWidget::BackgroundWidget::animationCallback() {
update();
checkIfDone();
}
LayerStackWidget::LayerStackWidget(QWidget *parent)
: RpWidget(parent)
, _background(this) {
setGeometry(parentWidget()->rect());
hide();
_background->setDoneCallback([this] { animationDone(); });
}
void LayerWidget::setInnerFocus() {
if (!isAncestorOf(window()->focusWidget())) {
doSetInnerFocus();
}
}
bool LayerWidget::overlaps(const QRect &globalRect) {
if (isHidden()) {
return false;
}
auto testRect = QRect(mapFromGlobal(globalRect.topLeft()), globalRect.size());
if (testAttribute(Qt::WA_OpaquePaintEvent)) {
return rect().contains(testRect);
}
if (QRect(0, st::boxRadius, width(), height() - 2 * st::boxRadius).contains(testRect)) {
return true;
}
if (QRect(st::boxRadius, 0, width() - 2 * st::boxRadius, height()).contains(testRect)) {
return true;
}
return false;
}
void LayerWidget::mousePressEvent(QMouseEvent *e) {
e->accept();
}
void LayerWidget::resizeEvent(QResizeEvent *e) {
if (_resizedCallback) {
_resizedCallback();
}
}
void LayerStackWidget::setHideByBackgroundClick(bool hide) {
_hideByBackgroundClick = hide;
}
void LayerStackWidget::keyPressEvent(QKeyEvent *e) {
if (e->key() == Qt::Key_Escape) {
hideCurrent(anim::type::normal);
}
}
void LayerStackWidget::mousePressEvent(QMouseEvent *e) {
Ui::PostponeCall(this, [=] { backgroundClicked(); });
}
void LayerStackWidget::backgroundClicked() {
if (!_hideByBackgroundClick) {
return;
}
if (const auto layer = currentLayer()) {
if (!layer->closeByOutsideClick()) {
return;
}
} else if (const auto special = _specialLayer.data()) {
if (!special->closeByOutsideClick()) {
return;
}
}
hideCurrent(anim::type::normal);
}
void LayerStackWidget::hideCurrent(anim::type animated) {
return currentLayer() ? hideLayers(animated) : hideAll(animated);
}
void LayerStackWidget::hideLayers(anim::type animated) {
startAnimation([] {}, [&] {
clearLayers();
}, Action::HideLayer, animated);
}
void LayerStackWidget::hideAll(anim::type animated) {
startAnimation([] {}, [&] {
clearLayers();
clearSpecialLayer();
_mainMenu.destroy();
}, Action::HideAll, animated);
}
void LayerStackWidget::hideTopLayer(anim::type animated) {
if (_specialLayer || _mainMenu) {
hideLayers(animated);
} else {
hideAll(animated);
}
}
void LayerStackWidget::removeBodyCache() {
_background->removeBodyCache();
setAttribute(Qt::WA_OpaquePaintEvent, false);
}
bool LayerStackWidget::layerShown() const {
return _specialLayer || currentLayer() || _mainMenu;
}
void LayerStackWidget::setCacheImages() {
auto bodyCache = QPixmap(), mainMenuCache = QPixmap();
auto specialLayerCache = QPixmap();
if (_specialLayer) {
Ui::SendPendingMoveResizeEvents(_specialLayer);
auto sides = RectPart::Left | RectPart::Right;
if (_specialLayer->y() > 0) {
sides |= RectPart::Top;
}
if (_specialLayer->y() + _specialLayer->height() < height()) {
sides |= RectPart::Bottom;
}
specialLayerCache = Ui::Shadow::grab(_specialLayer, st::boxRoundShadow, sides);
}
auto layerCache = QPixmap();
if (auto layer = currentLayer()) {
layerCache = Ui::Shadow::grab(layer, st::boxRoundShadow);
}
if (isAncestorOf(window()->focusWidget())) {
setFocus();
}
if (_mainMenu) {
removeBodyCache();
hideChildren();
bodyCache = Ui::GrabWidget(parentWidget());
showChildren();
mainMenuCache = Ui::Shadow::grab(_mainMenu, st::boxRoundShadow, RectPart::Right);
}
setAttribute(Qt::WA_OpaquePaintEvent, !bodyCache.isNull());
updateLayerBoxes();
_background->setCacheImages(std::move(bodyCache), std::move(mainMenuCache), std::move(specialLayerCache), std::move(layerCache));
}
void LayerStackWidget::closeLayer(not_null<LayerWidget*> layer) {
const auto weak = Ui::MakeWeak(layer.get());
if (weak->inFocusChain()) {
setFocus();
}
if (!weak || !weak->setClosing()) {
// This layer is already closing.
return;
} else if (!weak) {
// setClosing() could've killed the layer.
return;
}
if (layer == _specialLayer) {
hideAll(anim::type::normal);
} else if (layer == currentLayer()) {
if (_layers.size() == 1) {
hideCurrent(anim::type::normal);
} else {
const auto taken = std::move(_layers.back());
_layers.pop_back();
layer = currentLayer();
layer->parentResized();
if (!_background->animating()) {
layer->show();
showFinished();
}
}
} else {
for (auto i = _layers.begin(), e = _layers.end(); i != e; ++i) {
if (layer == i->get()) {
const auto taken = std::move(*i);
_layers.erase(i);
break;
}
}
}
}
void LayerStackWidget::updateLayerBoxes() {
const auto layerBox = [&] {
if (const auto layer = currentLayer()) {
return layer->geometry();
}
return QRect();
}();
const auto specialLayerBox = _specialLayer
? _specialLayer->geometry()
: QRect();
_background->setLayerBoxes(specialLayerBox, layerBox);
update();
}
void LayerStackWidget::finishAnimating() {
_background->finishAnimating();
}
bool LayerStackWidget::canSetFocus() const {
return (currentLayer() || _specialLayer || _mainMenu);
}
void LayerStackWidget::setInnerFocus() {
if (_background->animating()) {
setFocus();
} else if (auto l = currentLayer()) {
l->setInnerFocus();
} else if (_specialLayer) {
_specialLayer->setInnerFocus();
} else if (_mainMenu) {
_mainMenu->setInnerFocus();
}
}
bool LayerStackWidget::contentOverlapped(const QRect &globalRect) {
if (isHidden()) {
return false;
}
if (_specialLayer && _specialLayer->overlaps(globalRect)) {
return true;
}
if (auto layer = currentLayer()) {
return layer->overlaps(globalRect);
}
return false;
}
template <typename SetupNew, typename ClearOld>
void LayerStackWidget::startAnimation(
SetupNew setupNewWidgets,
ClearOld clearOldWidgets,
Action action,
anim::type animated) {
if (animated == anim::type::instant) {
setupNewWidgets();
clearOldWidgets();
prepareForAnimation();
_background->skipAnimation(action);
} else {
setupNewWidgets();
setCacheImages();
const auto weak = Ui::MakeWeak(this);
clearOldWidgets();
if (weak) {
prepareForAnimation();
_background->startAnimation(action);
}
}
}
void LayerStackWidget::resizeEvent(QResizeEvent *e) {
const auto weak = Ui::MakeWeak(this);
_background->setGeometry(rect());
if (!weak) {
return;
}
if (_specialLayer) {
_specialLayer->parentResized();
if (!weak) {
return;
}
}
if (const auto layer = currentLayer()) {
layer->parentResized();
if (!weak) {
return;
}
}
if (_mainMenu) {
_mainMenu->parentResized();
if (!weak) {
return;
}
}
updateLayerBoxes();
}
void LayerStackWidget::showBox(
object_ptr<BoxContent> box,
LayerOptions options,
anim::type animated) {
if (options & LayerOption::KeepOther) {
if (options & LayerOption::ShowAfterOther) {
prependBox(std::move(box), animated);
} else {
appendBox(std::move(box), animated);
}
} else {
replaceBox(std::move(box), animated);
}
}
void LayerStackWidget::replaceBox(
object_ptr<BoxContent> box,
anim::type animated) {
const auto pointer = pushBox(std::move(box), animated);
const auto removeTill = ranges::find(
_layers,
pointer,
&std::unique_ptr<LayerWidget>::get);
_closingLayers.insert(
end(_closingLayers),
std::make_move_iterator(begin(_layers)),
std::make_move_iterator(removeTill));
_layers.erase(begin(_layers), removeTill);
clearClosingLayers();
}
void LayerStackWidget::prepareForAnimation() {
if (isHidden()) {
show();
}
if (_mainMenu) {
_mainMenu->hide();
}
if (_specialLayer) {
_specialLayer->hide();
}
if (const auto layer = currentLayer()) {
layer->hide();
}
}
void LayerStackWidget::animationDone() {
bool hidden = true;
if (_mainMenu) {
_mainMenu->show();
hidden = false;
}
if (_specialLayer) {
_specialLayer->show();
hidden = false;
}
if (auto layer = currentLayer()) {
layer->show();
hidden = false;
}
setAttribute(Qt::WA_OpaquePaintEvent, false);
if (hidden) {
_hideFinishStream.fire({});
} else {
showFinished();
}
}
rpl::producer<> LayerStackWidget::hideFinishEvents() const {
return _hideFinishStream.events();
}
void LayerStackWidget::showFinished() {
fixOrder();
sendFakeMouseEvent();
updateLayerBoxes();
if (_specialLayer) {
_specialLayer->showFinished();
}
if (auto layer = currentLayer()) {
layer->showFinished();
}
if (canSetFocus()) {
setInnerFocus();
}
}
void LayerStackWidget::showSpecialLayer(
object_ptr<LayerWidget> layer,
anim::type animated) {
startAnimation([&] {
_specialLayer.destroy();
_specialLayer = std::move(layer);
initChildLayer(_specialLayer);
}, [&] {
_mainMenu.destroy();
}, Action::ShowSpecialLayer, animated);
}
bool LayerStackWidget::showSectionInternal(
not_null<Window::SectionMemento*> memento,
const Window::SectionShow &params) {
if (_specialLayer) {
return _specialLayer->showSectionInternal(memento, params);
}
return false;
}
void LayerStackWidget::hideSpecialLayer(anim::type animated) {
startAnimation([] {}, [&] {
clearSpecialLayer();
_mainMenu.destroy();
}, Action::HideSpecialLayer, animated);
}
void LayerStackWidget::showMainMenu(
object_ptr<LayerWidget> layer,
anim::type animated) {
startAnimation([&] {
_mainMenu = std::move(layer);
initChildLayer(_mainMenu);
_mainMenu->moveToLeft(0, 0);
}, [&] {
clearLayers();
_specialLayer.destroy();
}, Action::ShowMainMenu, animated);
}
void LayerStackWidget::appendBox(
object_ptr<BoxContent> box,
anim::type animated) {
pushBox(std::move(box), animated);
}
LayerWidget *LayerStackWidget::pushBox(
object_ptr<BoxContent> box,
anim::type animated) {
auto oldLayer = currentLayer();
if (oldLayer) {
if (oldLayer->inFocusChain()) setFocus();
oldLayer->hide();
}
_layers.push_back(
std::make_unique<BoxLayerWidget>(this, std::move(box)));
const auto raw = _layers.back().get();
initChildLayer(raw);
if (_layers.size() > 1) {
if (!_background->animating()) {
raw->setVisible(true);
showFinished();
}
} else {
startAnimation([] {}, [&] {
_mainMenu.destroy();
}, Action::ShowLayer, animated);
}
return raw;
}
void LayerStackWidget::prependBox(
object_ptr<BoxContent> box,
anim::type animated) {
if (_layers.empty()) {
replaceBox(std::move(box), animated);
return;
}
_layers.insert(
begin(_layers),
std::make_unique<BoxLayerWidget>(this, std::move(box)));
const auto raw = _layers.front().get();
raw->hide();
initChildLayer(raw);
}
bool LayerStackWidget::takeToThirdSection() {
return _specialLayer
? _specialLayer->takeToThirdSection()
: false;
}
void LayerStackWidget::clearLayers() {
_closingLayers.insert(
end(_closingLayers),
std::make_move_iterator(begin(_layers)),
std::make_move_iterator(end(_layers)));
_layers.clear();
clearClosingLayers();
}
void LayerStackWidget::clearClosingLayers() {
const auto weak = Ui::MakeWeak(this);
while (!_closingLayers.empty()) {
const auto index = _closingLayers.size() - 1;
const auto layer = _closingLayers.back().get();
if (layer->inFocusChain()) {
setFocus();
}
// This may destroy LayerStackWidget (by calling Ui::hideLayer).
// So each time we check a weak pointer (if we are still alive).
layer->setClosing();
// setClosing() could destroy 'this' or could call clearLayers().
if (weak && !_closingLayers.empty()) {
// We could enqueue more closing layers, so we remove by index.
Assert(index < _closingLayers.size());
Assert(_closingLayers[index].get() == layer);
_closingLayers.erase(begin(_closingLayers) + index);
} else {
// Everything was destroyed in clearLayers or ~LayerStackWidget.
break;
}
}
}
void LayerStackWidget::clearSpecialLayer() {
if (_specialLayer) {
_specialLayer->setClosing();
_specialLayer.destroy();
}
}
void LayerStackWidget::initChildLayer(LayerWidget *layer) {
layer->setParent(this);
layer->setClosedCallback([=] { closeLayer(layer); });
layer->setResizedCallback([=] { updateLayerBoxes(); });
Ui::SendPendingMoveResizeEvents(layer);
layer->parentResized();
}
void LayerStackWidget::fixOrder() {
if (const auto layer = currentLayer()) {
_background->raise();
layer->raise();
} else if (_specialLayer) {
_specialLayer->raise();
}
if (_mainMenu) {
_mainMenu->raise();
}
}
void LayerStackWidget::sendFakeMouseEvent() {
SendSynteticMouseEvent(this, QEvent::MouseMove, Qt::NoButton);
}
LayerStackWidget::~LayerStackWidget() {
// Some layer destructors call back into LayerStackWidget.
while (!_layers.empty() || !_closingLayers.empty()) {
hideAll(anim::type::instant);
clearClosingLayers();
}
}
} // namespace Ui

200
ui/layers/layer_widget.h Normal file
View file

@ -0,0 +1,200 @@
// 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 "ui/effects/animations.h"
#include "base/object_ptr.h"
#include "base/flags.h"
namespace Window {
class SectionMemento;
struct SectionShow;
} // namespace Window
namespace Ui {
class BoxContent;
enum class LayerOption {
CloseOther = (1 << 0),
KeepOther = (1 << 1),
ShowAfterOther = (1 << 2),
};
using LayerOptions = base::flags<LayerOption>;
inline constexpr auto is_flag_type(LayerOption) { return true; };
class LayerWidget : public Ui::RpWidget {
public:
using RpWidget::RpWidget;
virtual void parentResized() = 0;
virtual void showFinished() {
}
void setInnerFocus();
bool setClosing() {
if (!_closing) {
_closing = true;
closeHook();
return true;
}
return false;
}
bool overlaps(const QRect &globalRect);
void setClosedCallback(Fn<void()> callback) {
_closedCallback = std::move(callback);
}
void setResizedCallback(Fn<void()> callback) {
_resizedCallback = std::move(callback);
}
virtual bool takeToThirdSection() {
return false;
}
virtual bool showSectionInternal(
not_null<Window::SectionMemento*> memento,
const Window::SectionShow &params) {
return false;
}
virtual bool closeByOutsideClick() const {
return true;
}
protected:
void closeLayer() {
if (const auto callback = base::take(_closedCallback)) {
callback();
}
}
void mousePressEvent(QMouseEvent *e) override;
void resizeEvent(QResizeEvent *e) override;
virtual void doSetInnerFocus() {
setFocus();
}
virtual void closeHook() {
}
private:
bool _closing = false;
Fn<void()> _closedCallback;
Fn<void()> _resizedCallback;
};
class LayerStackWidget : public Ui::RpWidget {
public:
LayerStackWidget(QWidget *parent);
void finishAnimating();
rpl::producer<> hideFinishEvents() const;
void showBox(
object_ptr<BoxContent> box,
LayerOptions options,
anim::type animated);
void showSpecialLayer(
object_ptr<LayerWidget> layer,
anim::type animated);
void showMainMenu(
object_ptr<LayerWidget> menu,
anim::type animated);
bool takeToThirdSection();
bool canSetFocus() const;
void setInnerFocus();
bool contentOverlapped(const QRect &globalRect);
void hideSpecialLayer(anim::type animated);
void hideLayers(anim::type animated);
void hideAll(anim::type animated);
void hideTopLayer(anim::type animated);
void setHideByBackgroundClick(bool hide);
void removeBodyCache();
bool showSectionInternal(
not_null<Window::SectionMemento*> memento,
const Window::SectionShow &params);
bool layerShown() const;
~LayerStackWidget();
protected:
void keyPressEvent(QKeyEvent *e) override;
void mousePressEvent(QMouseEvent *e) override;
void resizeEvent(QResizeEvent *e) override;
private:
void appendBox(
object_ptr<BoxContent> box,
anim::type animated);
void prependBox(
object_ptr<BoxContent> box,
anim::type animated);
void replaceBox(
object_ptr<BoxContent> box,
anim::type animated);
void backgroundClicked();
LayerWidget *pushBox(
object_ptr<BoxContent> box,
anim::type animated);
void showFinished();
void hideCurrent(anim::type animated);
void closeLayer(not_null<LayerWidget*> layer);
enum class Action {
ShowMainMenu,
ShowSpecialLayer,
ShowLayer,
HideSpecialLayer,
HideLayer,
HideAll,
};
template <typename SetupNew, typename ClearOld>
void startAnimation(
SetupNew setupNewWidgets,
ClearOld clearOldWidgets,
Action action,
anim::type animated);
void prepareForAnimation();
void animationDone();
void setCacheImages();
void clearLayers();
void clearSpecialLayer();
void initChildLayer(LayerWidget *layer);
void updateLayerBoxes();
void fixOrder();
void sendFakeMouseEvent();
void clearClosingLayers();
LayerWidget *currentLayer() {
return _layers.empty() ? nullptr : _layers.back().get();
}
const LayerWidget *currentLayer() const {
return const_cast<LayerStackWidget*>(this)->currentLayer();
}
std::vector<std::unique_ptr<LayerWidget>> _layers;
std::vector<std::unique_ptr<LayerWidget>> _closingLayers;
object_ptr<LayerWidget> _specialLayer = { nullptr };
object_ptr<LayerWidget> _mainMenu = { nullptr };
class BackgroundWidget;
object_ptr<BackgroundWidget> _background;
bool _hideByBackgroundClick = true;
rpl::event_stream<> _hideFinishStream;
};
} // namespace Ui

150
ui/layers/layers.style Normal file
View file

@ -0,0 +1,150 @@
// 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
//
using "ui/basic.style";
using "ui/widgets/widgets.style";
ServiceCheck {
margin: margins;
diameter: pixels;
shift: pixels;
thickness: pixels;
tip: point;
small: pixels;
large: pixels;
stroke: pixels;
color: color;
duration: int;
}
boxDuration: 200;
boxRadius: 3px;
boxButtonFont: font(boxFontSize semibold);
defaultBoxButton: RoundButton(defaultLightButton) {
width: -24px;
height: 36px;
font: boxButtonFont;
}
boxTextStyle: TextStyle(defaultTextStyle) {
font: font(boxFontSize);
linkFont: font(boxFontSize);
linkFontOver: font(boxFontSize underline);
}
boxLabelStyle: TextStyle(boxTextStyle) {
lineHeight: 22px;
}
attentionBoxButton: RoundButton(defaultBoxButton) {
textFg: attentionButtonFg;
textFgOver: attentionButtonFgOver;
textBgOver: attentionButtonBgOver;
ripple: RippleAnimation(defaultRippleAnimation) {
color: attentionButtonBgRipple;
}
}
defaultBoxCheckbox: Checkbox(defaultCheckbox) {
width: -46px;
textPosition: point(12px, 1px);
style: boxTextStyle;
}
boxRoundShadow: Shadow {
left: icon {{ "round_shadow_box_left", windowShadowFg }};
topLeft: icon {{ "round_shadow_box_top_left", windowShadowFg }};
top: icon {{ "round_shadow_box_top", windowShadowFg }};
topRight: icon {{ "round_shadow_box_top_left-flip_horizontal", windowShadowFg }};
right: icon {{ "round_shadow_box_left-flip_horizontal", windowShadowFg }};
bottomRight: icon {{ "round_shadow_box_bottom_left-flip_horizontal", windowShadowFg }};
bottom: icon {{ "round_shadow_box_bottom", windowShadowFg }};
bottomLeft: icon {{ "round_shadow_box_bottom_left", windowShadowFg }};
extend: margins(10px, 10px, 10px, 10px);
fallback: windowShadowFgFallback;
}
boxTitleFont: font(17px semibold);
boxTitle: FlatLabel(defaultFlatLabel) {
textFg: boxTitleFg;
maxHeight: 24px;
style: TextStyle(defaultTextStyle) {
font: boxTitleFont;
linkFont: boxTitleFont;
linkFontOver: font(17px semibold underline);
}
}
boxTitlePosition: point(23px, 16px);
boxTitleHeight: 56px;
boxLayerTitlePosition: point(23px, 16px);
boxLayerTitleHeight: 56px;
boxLayerTitleAdditionalSkip: 9px;
boxLayerTitleAdditionalFont: normalFont;
boxLayerScroll: defaultSolidScroll;
boxRowPadding: margins(23px, 0px, 23px, 0px);
boxTopMargin: 6px;
boxTitleClose: IconButton(defaultIconButton) {
width: boxTitleHeight;
height: boxTitleHeight;
icon: boxTitleCloseIcon;
iconOver: boxTitleCloseIconOver;
rippleAreaPosition: point(6px, 6px);
rippleAreaSize: 44px;
ripple: RippleAnimation(defaultRippleAnimation) {
color: windowBgOver;
}
}
boxLinkButton: LinkButton(defaultLinkButton) {
font: boxTextFont;
overFont: font(boxFontSize underline);
}
boxOptionListPadding: margins(0px, 0px, 0px, 0px);
boxOptionListSkip: 20px;
boxOptionInputSkip: 6px;
boxVerticalMargin: 10px;
boxWidth: 320px;
boxWideWidth: 364px;
boxPadding: margins(23px, 30px, 23px, 8px);
boxMaxListHeight: 492px;
boxLittleSkip: 10px;
boxMediumSkip: 20px;
boxButtonPadding: margins(8px, 12px, 13px, 12px);
boxLayerButtonPadding: margins(8px, 8px, 8px, 8px);
boxLabel: FlatLabel(defaultFlatLabel) {
minWidth: 274px;
align: align(topleft);
style: boxLabelStyle;
}
boxDividerLabel: FlatLabel(boxLabel) {
minWidth: 245px;
align: align(topleft);
textFg: windowSubTextFg;
style: defaultTextStyle;
}
boxLoadingAnimation: InfiniteRadialAnimation(defaultInfiniteRadialAnimation) {
color: windowSubTextFg;
thickness: 2px;
}
boxLoadingSize: 20px;
boxDividerBg: windowBgOver;
boxDividerFg: windowShadowFg;
boxDividerTop: icon {{ "box_divider_top", boxDividerFg }};
boxDividerBottom: icon {{ "box_divider_bottom", boxDividerFg }};
boxDividerHeight: 10px;

View file

@ -12,7 +12,6 @@
namespace Ui {
void DrawRoundedRect(
QPainter &p,
const QRect &rect,
@ -75,8 +74,11 @@ RoundRect::RoundRect(
}, _lifetime);
}
void RoundRect::paint(QPainter &p, const QRect &rect) const {
DrawRoundedRect(p, rect, _color, _corners);
void RoundRect::paint(
QPainter &p,
const QRect &rect,
RectParts parts) const {
DrawRoundedRect(p, rect, _color, _corners, parts);
}
} // namespace Ui

View file

@ -25,7 +25,10 @@ class RoundRect final {
public:
RoundRect(ImageRoundRadius radius, const style::color &color);
void paint(QPainter &p, const QRect &rect) const;
void paint(
QPainter &p,
const QRect &rect,
RectParts parts = RectPart::Full) const;
private:
style::color _color;

View file

@ -0,0 +1,42 @@
// This file is part of Desktop App Toolkit,
// a set of libraries for developing nice desktop applications.
//
// For license and copyright information please follow this link:
// https://github.com/desktop-app/legal/blob/master/LEGAL
//
#include "ui/widgets/box_content_divider.h"
#include "styles/style_layers.h"
#include <QtGui/QPainter>
#include <QtGui/QtEvents>
namespace Ui {
BoxContentDivider::BoxContentDivider(QWidget *parent)
: BoxContentDivider(parent, st::boxDividerHeight) {
}
BoxContentDivider::BoxContentDivider(QWidget *parent, int height)
: RpWidget(parent) {
resize(width(), height);
}
void BoxContentDivider::paintEvent(QPaintEvent *e) {
QPainter p(this);
p.fillRect(e->rect(), st::boxDividerBg);
const auto dividerFillTop = QRect(
0,
0,
width(),
st::boxDividerTop.height());
st::boxDividerTop.fill(p, dividerFillTop);
const auto dividerFillBottom = myrtlrect(
0,
height() - st::boxDividerBottom.height(),
width(),
st::boxDividerBottom.height());
st::boxDividerBottom.fill(p, dividerFillBottom);
}
} // namespace Ui

View file

@ -0,0 +1,23 @@
// 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"
namespace Ui {
class BoxContentDivider : public Ui::RpWidget {
public:
BoxContentDivider(QWidget *parent);
BoxContentDivider(QWidget *parent, int height);
protected:
void paintEvent(QPaintEvent *e) override;
};
} // namespace Ui

View file

@ -9,6 +9,7 @@
#include "ui/text/text_entity.h"
#include "ui/effects/animation_value.h"
#include "ui/widgets/popup_menu.h"
#include "ui/widgets/box_content_divider.h"
#include "ui/basic_click_handlers.h" // UrlClickHandler
#include "ui/inactive_press.h"
@ -886,4 +887,14 @@ void FlatLabel::paintEvent(QPaintEvent *e) {
}
}
int DividerLabel::naturalWidth() const {
return -1;
}
void DividerLabel::resizeEvent(QResizeEvent *e) {
_background->lower();
_background->setGeometry(rect());
return PaddingWrap::resizeEvent(e);
}
} // namespace Ui

View file

@ -10,6 +10,7 @@
#include "ui/wrap/padding_wrap.h"
#include "ui/text/text.h"
#include "ui/click_handler.h"
#include "ui/widgets/box_content_divider.h"
#include "styles/style_widgets.h"
#include <QtCore/QTimer>
@ -19,6 +20,7 @@ class QTouchEvent;
namespace Ui {
class PopupMenu;
class BoxContentDivider;
class CrossFadeAnimation {
public:
@ -216,4 +218,19 @@ private:
};
class DividerLabel : public PaddingWrap<FlatLabel> {
public:
using PaddingWrap::PaddingWrap;
int naturalWidth() const override;
protected:
void resizeEvent(QResizeEvent *e) override;
private:
object_ptr<BoxContentDivider> _background
= object_ptr<BoxContentDivider>(this);
};
} // namespace Ui