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/numbers_animation.h
|
||||||
<(src_loc)/ui/effects/panel_animation.cpp
|
<(src_loc)/ui/effects/panel_animation.cpp
|
||||||
<(src_loc)/ui/effects/panel_animation.h
|
<(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.cpp
|
||||||
<(src_loc)/ui/effects/ripple_animation.h
|
<(src_loc)/ui/effects/ripple_animation.h
|
||||||
<(src_loc)/ui/image/image_prepare.cpp
|
<(src_loc)/ui/image/image_prepare.cpp
|
||||||
<(src_loc)/ui/image/image_prepare.h
|
<(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/ui_platform_utility.h
|
||||||
<(src_loc)/ui/platform/linux/ui_platform_utility_linux.cpp
|
<(src_loc)/ui/platform/linux/ui_platform_utility_linux.cpp
|
||||||
<(src_loc)/ui/platform/linux/ui_platform_utility_linux.h
|
<(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_isolated_emoji.h
|
||||||
<(src_loc)/ui/text/text_utilities.cpp
|
<(src_loc)/ui/text/text_utilities.cpp
|
||||||
<(src_loc)/ui/text/text_utilities.h
|
<(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.cpp
|
||||||
<(src_loc)/ui/widgets/buttons.h
|
<(src_loc)/ui/widgets/buttons.h
|
||||||
<(src_loc)/ui/widgets/checkbox.cpp
|
<(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': [
|
'style_files': [
|
||||||
'<(src_loc)/ui/colors.palette',
|
'<(src_loc)/ui/colors.palette',
|
||||||
'<(src_loc)/ui/basic.style',
|
'<(src_loc)/ui/basic.style',
|
||||||
|
'<(src_loc)/ui/layers/layers.style',
|
||||||
'<(src_loc)/ui/widgets/widgets.style',
|
'<(src_loc)/ui/widgets/widgets.style',
|
||||||
],
|
],
|
||||||
'dependent_style_files': [
|
'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 {
|
namespace Ui {
|
||||||
|
|
||||||
|
|
||||||
void DrawRoundedRect(
|
void DrawRoundedRect(
|
||||||
QPainter &p,
|
QPainter &p,
|
||||||
const QRect &rect,
|
const QRect &rect,
|
||||||
|
|
@ -75,8 +74,11 @@ RoundRect::RoundRect(
|
||||||
}, _lifetime);
|
}, _lifetime);
|
||||||
}
|
}
|
||||||
|
|
||||||
void RoundRect::paint(QPainter &p, const QRect &rect) const {
|
void RoundRect::paint(
|
||||||
DrawRoundedRect(p, rect, _color, _corners);
|
QPainter &p,
|
||||||
|
const QRect &rect,
|
||||||
|
RectParts parts) const {
|
||||||
|
DrawRoundedRect(p, rect, _color, _corners, parts);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace Ui
|
} // namespace Ui
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,10 @@ class RoundRect final {
|
||||||
public:
|
public:
|
||||||
RoundRect(ImageRoundRadius radius, const style::color &color);
|
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:
|
private:
|
||||||
style::color _color;
|
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/text/text_entity.h"
|
||||||
#include "ui/effects/animation_value.h"
|
#include "ui/effects/animation_value.h"
|
||||||
#include "ui/widgets/popup_menu.h"
|
#include "ui/widgets/popup_menu.h"
|
||||||
|
#include "ui/widgets/box_content_divider.h"
|
||||||
#include "ui/basic_click_handlers.h" // UrlClickHandler
|
#include "ui/basic_click_handlers.h" // UrlClickHandler
|
||||||
#include "ui/inactive_press.h"
|
#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
|
} // namespace Ui
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@
|
||||||
#include "ui/wrap/padding_wrap.h"
|
#include "ui/wrap/padding_wrap.h"
|
||||||
#include "ui/text/text.h"
|
#include "ui/text/text.h"
|
||||||
#include "ui/click_handler.h"
|
#include "ui/click_handler.h"
|
||||||
|
#include "ui/widgets/box_content_divider.h"
|
||||||
#include "styles/style_widgets.h"
|
#include "styles/style_widgets.h"
|
||||||
|
|
||||||
#include <QtCore/QTimer>
|
#include <QtCore/QTimer>
|
||||||
|
|
@ -19,6 +20,7 @@ class QTouchEvent;
|
||||||
namespace Ui {
|
namespace Ui {
|
||||||
|
|
||||||
class PopupMenu;
|
class PopupMenu;
|
||||||
|
class BoxContentDivider;
|
||||||
|
|
||||||
class CrossFadeAnimation {
|
class CrossFadeAnimation {
|
||||||
public:
|
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
|
} // namespace Ui
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue