Add layer and box management.
This commit is contained in:
parent
946c33ef25
commit
f4904e5ec4
25 changed files with 3013 additions and 4 deletions
|
|
@ -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
|
||||
|
|
|
|||
BIN
icons/box_divider_bottom.png
Normal file
BIN
icons/box_divider_bottom.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 81 B |
BIN
icons/box_divider_bottom@2x.png
Normal file
BIN
icons/box_divider_bottom@2x.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 85 B |
BIN
icons/box_divider_bottom@3x.png
Normal file
BIN
icons/box_divider_bottom@3x.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 85 B |
BIN
icons/box_divider_top.png
Normal file
BIN
icons/box_divider_top.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 88 B |
BIN
icons/box_divider_top@2x.png
Normal file
BIN
icons/box_divider_top@2x.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 106 B |
BIN
icons/box_divider_top@3x.png
Normal file
BIN
icons/box_divider_top@3x.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 104 B |
|
|
@ -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': [
|
||||
|
|
|
|||
309
ui/effects/radial_animation.cpp
Normal file
309
ui/effects/radial_animation.cpp
Normal 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
|
||||
110
ui/effects/radial_animation.h
Normal file
110
ui/effects/radial_animation.h
Normal 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
254
ui/layers/box_content.cpp
Normal 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
341
ui/layers/box_content.h
Normal 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
|
||||
351
ui/layers/box_layer_widget.cpp
Normal file
351
ui/layers/box_layer_widget.cpp
Normal 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
|
||||
142
ui/layers/box_layer_widget.h
Normal file
142
ui/layers/box_layer_widget.h
Normal 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
28
ui/layers/generic_box.cpp
Normal 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
157
ui/layers/generic_box.h
Normal 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
856
ui/layers/layer_widget.cpp
Normal 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 ¶ms) {
|
||||
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
200
ui/layers/layer_widget.h
Normal 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 ¶ms) {
|
||||
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 ¶ms);
|
||||
|
||||
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
150
ui/layers/layers.style
Normal 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;
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
42
ui/widgets/box_content_divider.cpp
Normal file
42
ui/widgets/box_content_divider.cpp
Normal 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
|
||||
23
ui/widgets/box_content_divider.h
Normal file
23
ui/widgets/box_content_divider.h
Normal 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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue