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

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

View File

@@ -0,0 +1,455 @@
// 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/rect_part.h"
#include "ui/painter.h"
#include "ui/ui_utility.h"
#include "base/timer.h"
#include "styles/style_layers.h"
#include "styles/palette.h"
namespace Ui {
namespace {
class BoxShow final : public Show {
public:
explicit BoxShow(not_null<Ui::BoxContent*> box);
~BoxShow();
void showOrHideBoxOrLayer(
std::variant<
v::null_t,
object_ptr<BoxContent>,
std::unique_ptr<LayerWidget>> &&layer,
LayerOptions options,
anim::type animated) const override;
[[nodiscard]] not_null<QWidget*> toastParent() const override;
[[nodiscard]] bool valid() const override;
operator bool() const override;
private:
BoxShow(base::weak_qptr<BoxContent> weak, ShowPtr wrapped);
bool resolve() const;
const base::weak_qptr<Ui::BoxContent> _weak;
mutable std::shared_ptr<Show> _wrapped;
rpl::lifetime _lifetime;
};
BoxShow::BoxShow(not_null<BoxContent*> box)
: BoxShow(base::make_weak(box.get()), nullptr) {
}
BoxShow::BoxShow(base::weak_qptr<BoxContent> weak, ShowPtr wrapped)
: _weak(weak)
, _wrapped(std::move(wrapped)) {
if (!resolve()) {
if (const auto box = _weak.get()) {
box->boxClosing(
) | rpl::on_next([=] {
resolve();
_lifetime.destroy();
}, _lifetime);
}
}
}
BoxShow::~BoxShow() = default;
bool BoxShow::resolve() const {
if (_wrapped) {
return true;
} else if (const auto strong = _weak.get()) {
if (strong->hasDelegate()) {
_wrapped = strong->getDelegate()->showFactory()();
return true;
}
}
return false;
}
void BoxShow::showOrHideBoxOrLayer(
std::variant<
v::null_t,
object_ptr<BoxContent>,
std::unique_ptr<LayerWidget>> &&layer,
LayerOptions options,
anim::type animated) const {
if (resolve()) {
_wrapped->showOrHideBoxOrLayer(std::move(layer), options, animated);
}
}
not_null<QWidget*> BoxShow::toastParent() const {
if (resolve()) {
return _wrapped->toastParent();
}
Unexpected("Stale BoxShow::toastParent call.");
}
bool BoxShow::valid() const {
return resolve() && _wrapped->valid();
}
BoxShow::operator bool() const {
return valid();
}
} // namespace
void BoxContent::setTitle(rpl::producer<QString> title) {
getDelegate()->setTitle(std::move(title) | rpl::map(Text::WithEntities));
}
QPointer<AbstractButton> BoxContent::addButton(
object_ptr<AbstractButton> button) {
auto result = QPointer<AbstractButton>(button.data());
getDelegate()->addButton(std::move(button));
return result;
}
QPointer<RoundButton> BoxContent::addButton(
rpl::producer<QString> text,
Fn<void()> clickCallback) {
return addButton(
std::move(text),
std::move(clickCallback),
getDelegate()->style().button);
}
QPointer<RoundButton> BoxContent::addButton(
rpl::producer<QString> text,
const style::RoundButton &st) {
return addButton(std::move(text), nullptr, st);
}
QPointer<RoundButton> BoxContent::addButton(
rpl::producer<QString> text,
Fn<void()> clickCallback,
const style::RoundButton &st) {
auto button = object_ptr<RoundButton>(this, std::move(text), st);
auto result = QPointer<RoundButton>(button.data());
result->setTextTransform(RoundButton::TextTransform::NoTransform);
result->setClickedCallback(std::move(clickCallback));
getDelegate()->addButton(std::move(button));
return result;
}
QPointer<AbstractButton> BoxContent::addLeftButton(
object_ptr<AbstractButton> button) {
auto result = QPointer<AbstractButton>(button.data());
getDelegate()->addLeftButton(std::move(button));
return result;
}
QPointer<RoundButton> BoxContent::addLeftButton(
rpl::producer<QString> text,
Fn<void()> clickCallback) {
return addLeftButton(
std::move(text),
std::move(clickCallback),
getDelegate()->style().button);
}
QPointer<RoundButton> BoxContent::addLeftButton(
rpl::producer<QString> text,
Fn<void()> clickCallback,
const style::RoundButton &st) {
auto button = object_ptr<RoundButton>(this, std::move(text), st);
const auto result = QPointer<RoundButton>(button.data());
result->setTextTransform(RoundButton::TextTransform::NoTransform);
result->setClickedCallback(std::move(clickCallback));
getDelegate()->addLeftButton(std::move(button));
return result;
}
QPointer<AbstractButton> BoxContent::addTopButton(
object_ptr<AbstractButton> button) {
auto result = QPointer<AbstractButton>(button.data());
getDelegate()->addTopButton(std::move(button));
return result;
}
QPointer<IconButton> BoxContent::addTopButton(
const style::IconButton &st,
Fn<void()> clickCallback) {
auto button = object_ptr<IconButton>(this, st);
const auto result = QPointer<IconButton>(button.data());
result->setClickedCallback(std::move(clickCallback));
getDelegate()->addTopButton(std::move(button));
return result;
}
void BoxContent::setInner(
object_ptr<RpWidget> 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();
_scroll->scrolls(
) | rpl::on_next([=] {
updateInnerVisibleTopBottom();
updateShadowsVisibility();
}, lifetime());
_scroll->innerResizes(
) | rpl::on_next([=] {
updateInnerVisibleTopBottom();
updateShadowsVisibility();
}, lifetime());
_draggingScroll.scrolls(
) | rpl::on_next([=](int delta) {
if (_scroll) {
_scroll->scrollToY(_scroll->scrollTop() + delta);
}
}, lifetime());
}
void BoxContent::scrollToWidget(not_null<QWidget*> widget) {
if (_scroll) {
_scroll->scrollToWidget(widget);
}
}
void BoxContent::scrollToY(int top, int bottom) {
scrollTo({ top, bottom });
}
void BoxContent::scrollTo(ScrollToRequest request, anim::type animated) {
if (_scroll) {
const auto v = _scroll->computeScrollToY(request.ymin, request.ymax);
const auto now = _scroll->scrollTop();
if (animated == anim::type::instant || v == now) {
_scrollAnimation.stop();
_scroll->scrollToY(v);
} else {
_scrollAnimation.start([=] {
_scroll->scrollToY(_scrollAnimation.value(v));
}, now, v, st::slideWrapDuration, anim::sineInOut);
}
}
}
void BoxContent::sendScrollViewportEvent(not_null<QEvent*> event) {
if (_scroll) {
_scroll->viewportEvent(event);
}
}
rpl::producer<> BoxContent::scrolls() const {
return _scroll ? _scroll->scrolls() : rpl::never<>();
}
int BoxContent::scrollTop() const {
return _scroll ? _scroll->scrollTop() : 0;
}
int BoxContent::scrollHeight() const {
return _scroll ? _scroll->height() : 0;
}
base::weak_ptr<Toast::Instance> BoxContent::showToast(
Toast::Config &&config) {
return BoxShow(this).showToast(std::move(config));
}
base::weak_ptr<Toast::Instance> BoxContent::showToast(
TextWithEntities &&text,
crl::time duration) {
return BoxShow(this).showToast(std::move(text), duration);
}
base::weak_ptr<Toast::Instance> BoxContent::showToast(
const QString &text,
crl::time duration) {
return BoxShow(this).showToast(text, duration);
}
std::shared_ptr<Show> BoxContent::uiShow() {
return std::make_shared<BoxShow>(this);
}
void BoxContent::scrollByDraggingDelta(int delta) {
_draggingScroll.checkDeltaScroll(_scroll ? delta : 0);
}
void BoxContent::updateInnerVisibleTopBottom() {
const auto widget = static_cast<RpWidget*>(_scroll
? _scroll->widget()
: nullptr);
if (widget) {
const auto top = _scroll->scrollTop();
widget->setVisibleTopBottom(top, top + _scroll->height());
}
}
void BoxContent::updateShadowsVisibility(anim::type animated) {
if (!_scroll) {
return;
}
const auto top = _scroll->scrollTop();
_topShadow->toggle(
((top > 0)
|| (_innerTopSkip > 0
&& !getDelegate()->style().shadowIgnoreTopSkip)),
animated);
_bottomShadow->toggle(
(top < _scroll->scrollTopMax())
|| (_innerBottomSkip > 0
&& !getDelegate()->style().shadowIgnoreBottomSkip),
animated);
}
void BoxContent::setDimensionsToContent(
int newWidth,
not_null<RpWidget*> content) {
content->resizeToWidth(newWidth);
content->heightValue(
) | rpl::on_next([=](int height) {
setDimensions(newWidth, height);
}, content->lifetime());
}
void BoxContent::setInnerTopSkip(int innerTopSkip, bool scrollBottomFixed) {
if (_innerTopSkip != innerTopSkip) {
const 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) {
_innerBottomSkip = innerBottomSkip;
if (_scroll && width() > 0) {
updateScrollAreaGeometry();
}
}
}
void BoxContent::setInnerVisible(bool scrollAreaVisible) {
if (_scroll) {
_scroll->setVisible(scrollAreaVisible);
}
}
QPixmap BoxContent::grabInnerCache() {
const auto isTopShadowVisible = !_topShadow->isHidden();
const auto isBottomShadowVisible = !_bottomShadow->isHidden();
if (isTopShadowVisible) {
_topShadow->setVisible(false);
}
if (isBottomShadowVisible) {
_bottomShadow->setVisible(false);
}
const 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() {
const auto newScrollHeight = height() - _innerTopSkip - _innerBottomSkip;
const 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();
updateShadowsVisibility(anim::type::instant);
}
}
object_ptr<RpWidget> BoxContent::doTakeInnerWidget() {
return _scroll->takeWidget<RpWidget>();
}
void BoxContent::paintEvent(QPaintEvent *e) {
Painter p(this);
if (testAttribute(Qt::WA_OpaquePaintEvent)) {
const auto &color = getDelegate()->style().bg;
for (const auto &rect : e->region()) {
p.fillRect(rect, color);
}
}
}
} // namespace Ui

View File

@@ -0,0 +1,396 @@
// 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/dragging_scroll_manager.h"
#include "ui/wrap/padding_wrap.h"
#include "ui/widgets/labels.h"
#include "ui/layers/layer_widget.h"
#include "ui/layers/show.h"
#include "ui/effects/animations.h"
#include "ui/effects/animation_value.h"
#include "ui/text/text_entity.h"
#include "ui/rp_widget.h"
enum class RectPart;
using RectParts = base::flags<RectPart>;
namespace base {
class Timer;
} // namespace base
namespace style {
struct RoundButton;
struct IconButton;
struct ScrollArea;
struct Box;
} // namespace style
namespace st {
extern const style::ScrollArea &boxScroll;
} // namespace st
namespace Ui::Toast {
struct Config;
class Instance;
} // namespace Ui::Toast
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 AbstractButton;
class RoundButton;
class IconButton;
class ScrollArea;
class FlatLabel;
class FadeShadow;
class BoxContent;
struct ScrollToRequest;
class BoxContentDelegate {
public:
virtual void setLayerType(bool layerType) = 0;
virtual void setStyle(const style::Box &st) = 0;
virtual const style::Box &style() = 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 setCustomCornersFilling(RectParts corners) = 0;
virtual void clearButtons() = 0;
virtual void addButton(object_ptr<AbstractButton> button) = 0;
virtual void addLeftButton(object_ptr<AbstractButton> button) = 0;
virtual void addTopButton(object_ptr<AbstractButton> button) = 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;
virtual void hideLayer() = 0;
virtual void triggerButton(int index) = 0;
template <typename BoxType>
base::weak_qptr<BoxType> show(
object_ptr<BoxType> content,
LayerOptions options = LayerOption::KeepOther,
anim::type animated = anim::type::normal) {
auto result = base::weak_qptr<BoxType>(content.data());
showBox(std::move(content), options, animated);
return result;
}
virtual ShowFactory showFactory() = 0;
virtual QPointer<QWidget> outerContainer() = 0;
};
class BoxContent : public RpWidget {
public:
BoxContent() {
setAttribute(Qt::WA_OpaquePaintEvent);
}
QAccessible::Role accessibilityRole() override {
return QAccessible::Role::Dialog;
}
bool isBoxShown() const {
return getDelegate()->isBoxShown();
}
void closeBox() {
getDelegate()->closeBox();
}
void triggerButton(int index) {
getDelegate()->triggerButton(index);
}
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);
virtual void showFinished() {
}
void setCustomCornersFilling(RectParts corners) {
getDelegate()->setCustomCornersFilling(corners);
}
void clearButtons() {
getDelegate()->clearButtons();
}
QPointer<AbstractButton> addButton(object_ptr<AbstractButton> button);
QPointer<RoundButton> addButton(
rpl::producer<QString> text,
Fn<void()> clickCallback = nullptr);
QPointer<RoundButton> addButton(
rpl::producer<QString> text,
const style::RoundButton &st);
QPointer<RoundButton> addButton(
rpl::producer<QString> text,
Fn<void()> clickCallback,
const style::RoundButton &st);
QPointer<AbstractButton> addLeftButton(
object_ptr<AbstractButton> button);
QPointer<RoundButton> addLeftButton(
rpl::producer<QString> text,
Fn<void()> clickCallback = nullptr);
QPointer<RoundButton> addLeftButton(
rpl::producer<QString> text,
Fn<void()> clickCallback,
const style::RoundButton& st);
QPointer<AbstractButton> addTopButton(
object_ptr<AbstractButton> button);
QPointer<IconButton> addTopButton(
const style::IconButton &st,
Fn<void()> clickCallback = nullptr);
void showLoading(bool show) {
getDelegate()->showLoading(show);
}
void updateButtonsGeometry() {
getDelegate()->updateButtonsPositions();
}
void setStyle(const style::Box &st) {
getDelegate()->setStyle(st);
}
virtual void setInnerFocus() {
setFocus();
}
[[nodiscard]] rpl::producer<> boxClosing() const {
return _boxClosingStream.events();
}
void notifyBoxClosing() {
_boxClosingStream.fire({});
}
void setDelegate(not_null<BoxContentDelegate*> newDelegate) {
_delegate = newDelegate;
_preparing = true;
prepare();
finishPrepare();
}
[[nodiscard]] bool hasDelegate() const {
return _delegate != nullptr;
}
[[nodiscard]] not_null<BoxContentDelegate*> getDelegate() const {
return _delegate;
}
void setNoContentMargin(bool noContentMargin) {
if (_noContentMargin != noContentMargin) {
_noContentMargin = noContentMargin;
setAttribute(Qt::WA_OpaquePaintEvent, !_noContentMargin);
}
getDelegate()->setNoContentMargin(noContentMargin);
}
void scrollByDraggingDelta(int delta);
void scrollToY(int top, int bottom = -1);
void scrollTo(
ScrollToRequest request,
anim::type animated = anim::type::instant);
void sendScrollViewportEvent(not_null<QEvent*> event);
[[nodiscard]] rpl::producer<> scrolls() const;
[[nodiscard]] int scrollTop() const;
[[nodiscard]] int scrollHeight() const;
base::weak_ptr<Toast::Instance> showToast(Toast::Config &&config);
base::weak_ptr<Toast::Instance> showToast(
TextWithEntities &&text,
crl::time duration = 0);
base::weak_ptr<Toast::Instance> showToast(
const QString &text,
crl::time duration = 0);
[[nodiscard]] std::shared_ptr<Show> uiShow();
protected:
virtual void prepare() = 0;
void setLayerType(bool layerType) {
getDelegate()->setLayerType(layerType);
}
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) {
return setInnerWidget(
std::move(inner),
st::boxScroll,
topSkip,
bottomSkip);
}
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:
void finishPrepare();
void finishScrollCreate();
void setInner(object_ptr<RpWidget> inner, const style::ScrollArea &st);
void updateScrollAreaGeometry();
void updateInnerVisibleTopBottom();
void updateShadowsVisibility(anim::type animated = anim::type::normal);
object_ptr<RpWidget> 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 };
Ui::DraggingScrollManager _draggingScroll;
Ui::Animations::Simple _scrollAnimation;
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(base::weak_qptr<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 &operator=(base::weak_qptr<BoxContent> other) {
if (_value != other) {
destroy();
_value = other;
}
return *this;
}
~BoxPointer() {
destroy();
}
BoxContent *get() const {
return _value.get();
}
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();
}
}
base::weak_qptr<BoxContent> _value;
};
} // namespace Ui

View File

@@ -0,0 +1,427 @@
// 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/labels.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(st::boxRadius, st().bg) {
_content->setParent(this);
_content->setDelegate(this);
_additionalTitle.changes(
) | rpl::on_next([=] {
updateSize();
update();
}, lifetime());
}
BoxLayerWidget::~BoxLayerWidget() = default;
void BoxLayerWidget::setLayerType(bool layerType) {
if (_layerType == layerType) {
return;
}
_layerType = layerType;
updateTitlePosition();
if (_maxContentHeight) {
setDimensions(width(), _maxContentHeight);
}
}
int BoxLayerWidget::titleHeight() const {
return st::boxTitleHeight;
}
const style::Box &BoxLayerWidget::st() const {
return _st
? *_st
: _layerType
? (_layer->boxStyleOverrideLayer()
? *_layer->boxStyleOverrideLayer()
: st::layerBox)
: (_layer->boxStyleOverride()
? *_layer->boxStyleOverride()
: st::defaultBox);
}
void BoxLayerWidget::setStyle(const style::Box &st) {
_st = &st;
_roundRect.setColor(st.bg);
}
const style::Box &BoxLayerWidget::style() {
return st();
}
int BoxLayerWidget::buttonsHeight() const {
const auto padding = st().buttonPadding;
return padding.top() + st().buttonHeight + padding.bottom();
}
int BoxLayerWidget::buttonsTop() const {
const auto padding = st().buttonPadding;
return height() - padding.bottom() - st().buttonHeight;
}
QRect BoxLayerWidget::loadingRect() const {
const auto padding = st().buttonPadding;
const auto size = st::boxLoadingSize;
const auto skipx = st::boxTitlePosition.x();
const auto skipy = (st().buttonHeight - size) / 2;
return QRect(
skipx,
height() - padding.bottom() - skipy - size,
size,
size);
}
void BoxLayerWidget::paintEvent(QPaintEvent *e) {
Painter p(this);
const auto clip = e->rect();
const auto paintTopRounded = !(_customCornersFilling & RectPart::FullTop)
&& clip.intersects(QRect(0, 0, width(), st::boxRadius));
const auto paintBottomRounded = !(_customCornersFilling
& RectPart::FullBottom)
&& clip.intersects(
QRect(0, height() - st::boxRadius, width(), st::boxRadius));
if (paintTopRounded || paintBottomRounded) {
_roundRect.paint(p, rect(), RectPart::None
| (paintTopRounded ? RectPart::FullTop : RectPart::None)
| (paintBottomRounded ? RectPart::FullBottom : RectPart::None));
}
const auto other = e->region().intersected(
QRect(0, st::boxRadius, width(), height() - 2 * st::boxRadius));
if (!other.isEmpty()) {
for (const auto &rect : other) {
p.fillRect(rect, st().bg);
}
}
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::boxTitleAdditionalFont);
p.setPen(st().titleAdditionalFg);
p.drawTextLeft(
_titleLeft + (_title ? _title->width() : 0) + st::boxTitleAdditionalSkip,
_titleTop + st::boxTitleFont->ascent - st::boxTitleAdditionalFont->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, rpl::duplicate(title), st().title);
_title->show();
std::move(
title
) | rpl::on_next([=] {
updateTitlePosition();
}, _title->lifetime());
} else {
_title.destroy();
}
if (wasTitle != hasTitle()) {
updateSize();
}
}
void BoxLayerWidget::setAdditionalTitle(rpl::producer<QString> additional) {
_additionalTitle = std::move(additional);
}
void BoxLayerWidget::triggerButton(int index) {
if (index < _buttons.size()) {
_buttons[index]->clicked(Qt::KeyboardModifiers(), Qt::LeftButton);
}
}
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::hideLayer() {
_layer->hideLayers(anim::type::normal);
}
void BoxLayerWidget::updateSize() {
setDimensions(width(), _maxContentHeight);
}
void BoxLayerWidget::updateButtonsPositions() {
if (!_buttons.empty() || _leftButton) {
auto padding = st().buttonPadding;
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();
}
}
auto right = 0;
for (const auto &button : _topButtons) {
button->moveToRight(right, 0);
right += button->width();
}
}
ShowFactory BoxLayerWidget::showFactory() {
return _layer->showFactory();
}
QPointer<QWidget> BoxLayerWidget::outerContainer() {
return parentWidget();
}
void BoxLayerWidget::updateTitlePosition() {
_titleLeft = st::boxTitlePosition.x();
_titleTop = st::boxTitlePosition.y();
if (_title) {
auto topButtonsSkip = 0;
for (const auto &button : _topButtons) {
topButtonsSkip += button->width();
}
_title->resizeToNaturalWidth(
width() - _titleLeft * 2 - topButtonsSkip);
_title->moveToLeft(_titleLeft, _titleTop);
}
}
void BoxLayerWidget::setCustomCornersFilling(RectParts corners) {
_customCornersFilling = corners;
update();
}
void BoxLayerWidget::clearButtons() {
for (auto &button : base::take(_buttons)) {
button.destroy();
}
_leftButton.destroy();
base::take(_topButtons);
}
void BoxLayerWidget::addButton(object_ptr<AbstractButton> button) {
_buttons.push_back(std::move(button));
const auto raw = _buttons.back().data();
raw->setParent(this);
raw->show();
if (st().buttonWide) {
widthValue() | rpl::on_next([=](int width) {
const auto buttonWidth = width
- st().buttonPadding.left()
- st().buttonPadding.right();
if (buttonWidth > 0) {
raw->resizeToWidth(buttonWidth);
}
}, raw->lifetime());
}
raw->widthValue(
) | rpl::on_next([=] {
if (st().buttonWide) {
const auto buttonWidth = width()
- st().buttonPadding.left()
- st().buttonPadding.right();
if (buttonWidth > 0 && raw->width() != buttonWidth) {
raw->resizeToWidth(buttonWidth);
}
}
updateButtonsPositions();
}, raw->lifetime());
}
void BoxLayerWidget::addLeftButton(object_ptr<AbstractButton> button) {
_leftButton = std::move(button);
const auto raw = _leftButton.data();
raw->setParent(this);
raw->show();
raw->widthValue(
) | rpl::on_next([=] {
updateButtonsPositions();
}, raw->lifetime());
}
void BoxLayerWidget::addTopButton(object_ptr<AbstractButton> button) {
_topButtons.push_back(base::unique_qptr<AbstractButton>(button.release()));
const auto raw = _topButtons.back().get();
raw->setParent(this);
raw->show();
updateButtonsPositions();
updateTitlePosition();
}
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();
const auto bottomMargin = st().margin.bottom();
if (newGeometry.top() + newGeometry.height() + bottomMargin > parentHeight
|| forceCenterPosition) {
const auto top1 = parentHeight - bottomMargin - 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 {
const auto &margin = st().margin;
return std::min(
_fullHeight,
parentWidget()->height() - margin.top() - margin.bottom());
}
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();
const auto top = contentTop();
_content->resize(width(), height() - top - buttonsHeight());
_content->moveToLeft(0, top);
LayerWidget::resizeEvent(e);
}
void BoxLayerWidget::keyPressEvent(QKeyEvent *e) {
if (e->key() == Qt::Key_Escape) {
closeBox();
} else {
LayerWidget::keyPressEvent(e);
}
}
} // namespace Ui

View File

@@ -0,0 +1,143 @@
// 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/round_rect.h"
#include "ui/rp_widget.h"
class Painter;
struct TextWithEntities;
namespace anim {
enum class type : uchar;
} // namespace anim
namespace style {
struct Box;
} // namespace style
namespace Ui {
class AbstractButton;
class FlatLabel;
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 setStyle(const style::Box &st) override;
const style::Box &style() 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 showFinished() override {
_content->showFinished();
}
void setCustomCornersFilling(RectParts corners) override;
void clearButtons() override;
void addButton(object_ptr<AbstractButton> button) override;
void addLeftButton(object_ptr<AbstractButton> button) override;
void addTopButton(object_ptr<AbstractButton> button) override;
void showLoading(bool show) override;
void updateButtonsPositions() override;
ShowFactory showFactory() 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 hideLayer() override;
void triggerButton(int index) override;
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]] const style::Box &st() const;
[[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();
const style::Box *_st = nullptr;
not_null<LayerStackWidget*> _layer;
bool _layerType = false;
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;
RectParts _customCornersFilling;
int _titleLeft = 0;
int _titleTop = 0;
bool _closeByOutsideClick = true;
std::vector<object_ptr<AbstractButton>> _buttons;
object_ptr<AbstractButton> _leftButton = { nullptr };
std::vector<base::unique_qptr<AbstractButton>> _topButtons;
std::unique_ptr<LoadingProgress> _loadingProgress;
};
} // namespace Ui

View File

@@ -0,0 +1,131 @@
// 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);
const auto currentWidth = width();
const auto pinnedToTop = _pinnedToTopContent.data();
const auto pinnedToBottom = _pinnedToBottomContent.data();
if (pinnedToTop) {
pinnedToTop->resizeToWidth(currentWidth);
}
if (pinnedToBottom) {
pinnedToBottom->resizeToWidth(currentWidth);
}
auto wrap = object_ptr<Ui::OverrideMargins>(this, std::move(_owned));
wrap->resizeToWidth(currentWidth);
rpl::combine(
pinnedToTop ? pinnedToTop->heightValue() : rpl::single(0),
wrap->heightValue(),
pinnedToBottom ? pinnedToBottom->heightValue() : rpl::single(0)
) | rpl::on_next([=](int top, int height, int bottom) {
Expects(_minHeight >= 0);
Expects(!_maxHeight || _minHeight <= _maxHeight);
setInnerTopSkip(top);
setInnerBottomSkip(bottom);
const auto desired = top + height + bottom;
setDimensions(
currentWidth,
std::clamp(
desired,
_minHeight,
_maxHeight ? _maxHeight : std::max(_minHeight, desired)),
true);
}, wrap->lifetime());
setInnerWidget(
std::move(wrap),
_scrollSt ? *_scrollSt : st::boxScroll,
pinnedToTop ? pinnedToTop->height() : 0,
pinnedToBottom ? pinnedToBottom->height() : 0);
if (pinnedToBottom) {
rpl::combine(
heightValue(),
pinnedToBottom->heightValue()
) | rpl::on_next([=](int outer, int height) {
pinnedToBottom->move(0, outer - height);
}, pinnedToBottom->lifetime());
}
if (const auto onstack = _initScroll) {
onstack();
}
}
void GenericBox::addSkip(int height) {
addRow(object_ptr<Ui::FixedHeightWidget>(this, height));
}
void GenericBox::setInnerFocus() {
if (_focus) {
_focus();
} else {
BoxContent::setInnerFocus();
}
}
void GenericBox::showFinished() {
const auto guard = QPointer(this);
if (const auto onstack = _showFinished) {
onstack();
if (!guard) {
return;
}
}
_showFinishes.fire({});
}
not_null<Ui::RpWidget*> GenericBox::doSetPinnedToTopContent(
object_ptr<Ui::RpWidget> content) {
_pinnedToTopContent = std::move(content);
return _pinnedToTopContent.data();
}
not_null<Ui::RpWidget*> GenericBox::doSetPinnedToBottomContent(
object_ptr<Ui::RpWidget> content) {
_pinnedToBottomContent = std::move(content);
return _pinnedToBottomContent.data();
}
int GenericBox::rowsCount() const {
return _content->count();
}
int GenericBox::width() const {
return _width ? _width : st::boxWidth;
}
not_null<Ui::VerticalLayout*> GenericBox::verticalLayout() {
return _content;
}
rpl::producer<> BoxShowFinishes(not_null<GenericBox*> box) {
const auto singleShot = box->lifetime().make_state<rpl::lifetime>();
const auto showFinishes = singleShot->make_state<rpl::event_stream<>>();
box->setShowFinishedCallback([=] {
showFinishes->fire({});
singleShot->destroy();
box->setShowFinishedCallback(nullptr);
});
return showFinishes->events();
}
} // namespace Ui

View File

@@ -0,0 +1,230 @@
// 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 final : 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;
}
void setInitScrollCallback(Fn<void()> callback) {
_initScroll = callback;
}
void setShowFinishedCallback(Fn<void()> callback) {
_showFinished = callback;
}
[[nodiscard]] rpl::producer<> showFinishes() const {
return _showFinishes.events();
}
[[nodiscard]] int rowsCount() const;
[[nodiscard]] int width() const;
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,
style::align align = style::al_left) {
return _content->insert(
atPosition,
std::move(child),
margin,
align);
}
template <
typename Widget,
typename = std::enable_if_t<
std::is_base_of_v<RpWidget, Widget>>>
Widget *insertRow(
int atPosition,
object_ptr<Widget> &&child,
style::align align) {
return _content->insert(
atPosition,
std::move(child),
st::boxRowPadding,
align);
}
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,
style::align align = style::al_left) {
return _content->add(std::move(child), margin, align);
}
template <
typename Widget,
typename = std::enable_if_t<
std::is_base_of_v<RpWidget, Widget>>>
Widget *addRow(object_ptr<Widget> &&child, style::align align) {
return _content->add(std::move(child), st::boxRowPadding, align);
}
void addSkip(int height);
void setMaxHeight(int maxHeight) {
_maxHeight = maxHeight;
}
void setMinHeight(int minHeight) {
_minHeight = minHeight;
}
void setScrollStyle(const style::ScrollArea &st) {
_scrollSt = &st;
}
void setInnerFocus() override;
void showFinished() override;
template <typename Widget>
not_null<Widget*> setPinnedToTopContent(object_ptr<Widget> content) {
return static_cast<Widget*>(
doSetPinnedToTopContent(std::move(content)).get());
}
template <typename Widget>
not_null<Widget*> setPinnedToBottomContent(object_ptr<Widget> content) {
return static_cast<Widget*>(
doSetPinnedToBottomContent(std::move(content)).get());
}
[[nodiscard]] not_null<Ui::VerticalLayout*> verticalLayout();
using BoxContent::setNoContentMargin;
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>...>;
void prepare() override;
not_null<Ui::RpWidget*> doSetPinnedToTopContent(
object_ptr<Ui::RpWidget> content);
not_null<Ui::RpWidget*> doSetPinnedToBottomContent(
object_ptr<Ui::RpWidget> content);
FnMut<void(not_null<GenericBox*>)> _init;
Fn<void()> _focus;
Fn<void()> _initScroll;
Fn<void()> _showFinished;
rpl::event_stream<> _showFinishes;
object_ptr<Ui::VerticalLayout> _owned;
not_null<Ui::VerticalLayout*> _content;
const style::ScrollArea *_scrollSt = nullptr;
int _width = 0;
int _minHeight = 0;
int _maxHeight = 0;
object_ptr<Ui::RpWidget> _pinnedToTopContent = { nullptr };
object_ptr<Ui::RpWidget> _pinnedToBottomContent = { nullptr };
};
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)...))
, _owned(this)
, _content(_owned.data()) {
}
[[nodiscard]] rpl::producer<> BoxShowFinishes(not_null<GenericBox*> box);
} // namespace Ui

View File

@@ -0,0 +1,191 @@
// 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_manager.h"
#include "ui/layers/show.h"
#include "ui/ui_utility.h"
namespace Ui {
class LayerManager::ManagerShow final : public Show {
public:
explicit ManagerShow(not_null<LayerManager*> manager);
~ManagerShow();
void showOrHideBoxOrLayer(
std::variant<
v::null_t,
object_ptr<BoxContent>,
std::unique_ptr<LayerWidget>> &&layer,
LayerOptions options,
anim::type animated) const override;
[[nodiscard]] not_null<QWidget*> toastParent() const override;
[[nodiscard]] bool valid() const override;
operator bool() const override;
private:
const base::weak_ptr<LayerManager> _manager;
};
LayerManager::ManagerShow::ManagerShow(not_null<LayerManager*> manager)
: _manager(manager.get()) {
}
LayerManager::ManagerShow::~ManagerShow() = default;
void LayerManager::ManagerShow::showOrHideBoxOrLayer(
std::variant<
v::null_t,
object_ptr<BoxContent>,
std::unique_ptr<LayerWidget>> &&layer,
LayerOptions options,
anim::type animated) const {
using UniqueLayer = std::unique_ptr<Ui::LayerWidget>;
using ObjectBox = object_ptr<Ui::BoxContent>;
if (auto layerWidget = std::get_if<UniqueLayer>(&layer)) {
if (const auto manager = _manager.get()) {
manager->showLayer(std::move(*layerWidget), options, animated);
}
} else if (auto box = std::get_if<ObjectBox>(&layer)) {
if (const auto manager = _manager.get()) {
manager->showBox(std::move(*box), options, animated);
}
} else if (const auto manager = _manager.get()) {
manager->hideAll(animated);
}
}
not_null<QWidget*> LayerManager::ManagerShow::toastParent() const {
const auto manager = _manager.get();
Ensures(manager != nullptr);
return manager->toastParent();
}
bool LayerManager::ManagerShow::valid() const {
return (_manager.get() != nullptr);
}
LayerManager::ManagerShow::operator bool() const {
return valid();
}
LayerManager::LayerManager(not_null<RpWidget*> widget)
: _widget(widget) {
}
void LayerManager::setStyleOverrides(
const style::Box *boxSt,
const style::Box *layerSt) {
_boxSt = boxSt;
_layerSt = layerSt;
if (_layer) {
_layer->setStyleOverrides(_boxSt, _layerSt);
}
}
void LayerManager::setHideByBackgroundClick(bool hide) {
_hideByBackgroundClick = hide;
if (_layer) {
_layer->setHideByBackgroundClick(hide);
}
}
void LayerManager::showBox(
object_ptr<BoxContent> box,
LayerOptions options,
anim::type animated) {
ensureLayerCreated();
_layer->showBox(std::move(box), options, animated);
setFocus();
}
void LayerManager::showLayer(
std::unique_ptr<LayerWidget> layer,
LayerOptions options,
anim::type animated) {
ensureLayerCreated();
_layer->showLayer(std::move(layer), options, animated);
setFocus();
}
void LayerManager::hideAll(anim::type animated) {
if (animated == anim::type::instant) {
destroyLayer();
} else if (_layer) {
_layer->hideAll(animated);
}
}
void LayerManager::raise() {
if (_layer) {
_layer->raise();
}
}
bool LayerManager::setFocus() {
if (!_layer) {
return false;
}
_layer->setInnerFocus();
return true;
}
std::shared_ptr<Show> LayerManager::uiShow() {
if (!_cachedShow) {
_cachedShow = std::make_shared<ManagerShow>(this);
}
return _cachedShow;
}
const LayerWidget *LayerManager::topShownLayer() const {
return _layer ? _layer->topShownLayer() : nullptr;
}
void LayerManager::ensureLayerCreated() {
if (_layer) {
return;
}
_layer.emplace(_widget, crl::guard(this, [=] {
return uiShow();
}));
_layer->setHideByBackgroundClick(_hideByBackgroundClick);
_layer->setStyleOverrides(_boxSt, _layerSt);
_layer->hideFinishEvents(
) | rpl::filter([=] {
return _layer != nullptr; // Last hide finish is sent from destructor.
}) | rpl::on_next([=] {
destroyLayer();
}, _layer->lifetime());
_layer->move(0, 0);
_widget->sizeValue(
) | rpl::on_next([=](QSize size) {
_layer->resize(size);
}, _layer->lifetime());
_layerShown = true;
}
void LayerManager::destroyLayer() {
if (!_layer) {
return;
}
auto layer = base::take(_layer);
_layerShown = false;
const auto resetFocus = Ui::InFocusChain(layer);
if (resetFocus) {
_widget->setFocus();
}
layer = nullptr;
}
} // namespace Ui

View File

@@ -0,0 +1,73 @@
// 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/weak_ptr.h"
#include "ui/layers/layer_widget.h"
#include <QtCore/QMargins>
namespace style {
struct Box;
} // namespace style
namespace Ui {
class BoxContent;
class RpWidget;
class Show;
class LayerManager final : public base::has_weak_ptr {
public:
explicit LayerManager(not_null<RpWidget*> widget);
void setStyleOverrides(
const style::Box *boxSt,
const style::Box *layerSt);
void setHideByBackgroundClick(bool hide);
void showBox(
object_ptr<BoxContent> box,
LayerOptions options = LayerOption::KeepOther,
anim::type animated = anim::type::normal);
void showLayer(
std::unique_ptr<LayerWidget> layer,
LayerOptions options = LayerOption::KeepOther,
anim::type animated = anim::type::normal);
void hideAll(anim::type animated = anim::type::normal);
void raise();
bool setFocus();
[[nodiscard]] rpl::producer<bool> layerShownValue() const {
return _layerShown.value();
}
[[nodiscard]] not_null<Ui::RpWidget*> toastParent() const {
return _widget;
}
[[nodiscard]] const LayerWidget *topShownLayer() const;
[[nodiscard]] std::shared_ptr<Show> uiShow();
private:
class ManagerShow;
void ensureLayerCreated();
void destroyLayer();
const not_null<RpWidget*> _widget;
base::unique_qptr<LayerStackWidget> _layer;
std::shared_ptr<ManagerShow> _cachedShow;
rpl::variable<bool> _layerShown;
const style::Box *_boxSt = nullptr;
const style::Box *_layerSt = nullptr;
bool _hideByBackgroundClick = false;
};
} // namespace Ui

View File

@@ -0,0 +1,955 @@
// 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/cached_special_layer_shadow_corners.h"
#include "ui/layers/box_layer_widget.h"
#include "ui/widgets/shadow.h"
#include "ui/image/image_prepare.h"
#include "ui/painter.h"
#include "ui/ui_utility.h"
#include "ui/round_rect.h"
#include "base/qt/qt_tab_key.h"
#include "base/integration.h"
#include "styles/style_layers.h"
#include "styles/style_widgets.h"
#include "styles/palette.h"
#include <QtGui/QtEvents>
namespace Ui {
class LayerStackWidget::BackgroundWidget : public RpWidget {
public:
using RpWidget::RpWidget;
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();
[[nodiscard]] bool hasBodyCache() const;
void refreshBodyCache(QPixmap &&bodyCache);
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;
Fn<void()> _doneCallback;
bool _wasAnimating = false;
bool _inPaintEvent = false;
bool _repaintIssued = 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;
};
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;
_repaintIssued = false;
setAttribute(Qt::WA_OpaquePaintEvent, !_bodyCache.isNull());
}
void LayerStackWidget::BackgroundWidget::removeBodyCache() {
if (hasBodyCache()) {
_bodyCache = {};
setAttribute(Qt::WA_OpaquePaintEvent, false);
}
}
bool LayerStackWidget::BackgroundWidget::hasBodyCache() const {
return !_bodyCache.isNull();
}
void LayerStackWidget::BackgroundWidget::refreshBodyCache(
QPixmap &&bodyCache) {
_bodyCache = std::move(bodyCache);
setAttribute(Qt::WA_OpaquePaintEvent, !_bodyCache.isNull());
}
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) {
_repaintIssued = false;
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, Ui::SpecialLayerShadowCorners(), sides);
if (topCorners || bottomCorners) {
p.setClipping(false);
}
}
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.
const auto region = QRegion(bg) - specialLayerBox;
for (const auto &rect : region) {
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);
}
if (!_repaintIssued && !_a_shown.animating()) {
_repaintIssued = true;
update();
}
}
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, ShowFactory showFactory)
: RpWidget(parent)
, _background(this)
, _showFactory(std::move(showFactory)) {
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();
}
}
bool LayerWidget::focusNextPrevChild(bool next) {
return base::FocusNextPrevChildBlocked(this, next);
}
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::hideAllAnimatedPrepare() {
prepareAnimation([] {}, [&] {
clearLayers();
clearSpecialLayer();
_mainMenu.destroy();
}, Action::HideAll, anim::type::normal);
}
void LayerStackWidget::hideAllAnimatedRun() {
if (_background->hasBodyCache()) {
removeBodyCache();
hideChildren();
auto bodyCache = Ui::GrabWidget(parentWidget());
showChildren();
_background->refreshBodyCache(std::move(bodyCache));
}
_background->startAnimation(Action::HideAll);
}
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;
}
const LayerWidget *LayerStackWidget::topShownLayer() const {
if (const auto result = currentLayer()) {
return result;
} else if (const auto special = _specialLayer.data()) {
return special;
} else if (const auto menu = _mainMenu.data()) {
return menu;
}
return nullptr;
}
void LayerStackWidget::setStyleOverrides(
const style::Box *boxSt,
const style::Box *layerSt) {
_boxSt = boxSt;
_layerSt = layerSt;
}
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 = base::make_weak(layer.get());
if (Ui::InFocusChain(layer)) {
setFocus();
}
if (!layer->setClosing()) {
// This layer is already closing.
return;
} else if (!weak) {
// setClosing() could've killed the layer.
return;
}
if (layer == _specialLayer || layer == _mainMenu) {
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>
bool LayerStackWidget::prepareAnimation(
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 = base::make_weak(this);
clearOldWidgets();
if (weak) {
prepareForAnimation();
return true;
}
}
return false;
}
template <typename SetupNew, typename ClearOld>
void LayerStackWidget::startAnimation(
SetupNew &&setupNewWidgets,
ClearOld &&clearOldWidgets,
Action action,
anim::type animated) {
const auto alive = prepareAnimation(
std::forward<SetupNew>(setupNewWidgets),
std::forward<ClearOld>(clearOldWidgets),
action,
animated);
if (alive) {
_background->startAnimation(action);
}
}
void LayerStackWidget::resizeEvent(QResizeEvent *e) {
const auto weak = base::make_weak(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::prepareForAnimation() {
if (isHidden()) {
show();
}
if (_mainMenu) {
if (Ui::InFocusChain(_mainMenu)) {
setFocus();
}
_mainMenu->hide();
}
if (_specialLayer) {
if (Ui::InFocusChain(_specialLayer)) {
setFocus();
}
_specialLayer->hide();
}
if (const auto layer = currentLayer()) {
if (Ui::InFocusChain(layer)) {
setFocus();
}
layer->hide();
}
}
void LayerStackWidget::animationDone() {
auto &integration = base::Integration::Instance();
bool hidden = true;
if (_mainMenu) {
integration.setCrashAnnotation("ShowingWidget", u"MainMenu"_q);
_mainMenu->show();
hidden = false;
}
if (_specialLayer) {
integration.setCrashAnnotation("ShowingWidget", u"SpecialLayer"_q);
_specialLayer->show();
hidden = false;
}
if (auto layer = currentLayer()) {
integration.setCrashAnnotation("ShowingWidget", u"Box"_q);
layer->show();
hidden = false;
}
setAttribute(Qt::WA_OpaquePaintEvent, false);
if (hidden) {
_hideFinishStream.fire({});
} else {
integration.setCrashAnnotation("ShowingWidget", u"Finished"_q);
showFinished();
integration.setCrashAnnotation("ShowingWidget", QString());
}
}
rpl::producer<> LayerStackWidget::hideFinishEvents() const {
return _hideFinishStream.events();
}
void LayerStackWidget::showFinished() {
fixOrder();
sendFakeMouseEvent();
updateLayerBoxes();
if (_specialLayer) {
_specialLayer->showFinished();
}
if (_mainMenu) {
_mainMenu->showFinished();
}
if (auto layer = currentLayer()) {
layer->showFinished();
}
if (canSetFocus()) {
setInnerFocus();
}
}
void LayerStackWidget::showSpecialLayer(
object_ptr<LayerWidget> layer,
anim::type animated) {
startAnimation([&] {
_specialLayer.destroy();
_specialLayer = std::move(layer);
initChildLayer(_specialLayer);
}, [&] {
_mainMenu.destroy();
}, Action::ShowSpecialLayer, animated);
}
bool LayerStackWidget::showSectionInternal(
not_null<::Window::SectionMemento*> memento,
const ::Window::SectionShow &params) {
if (_specialLayer) {
return _specialLayer->showSectionInternal(memento, params);
}
return false;
}
void LayerStackWidget::hideSpecialLayer(anim::type animated) {
startAnimation([] {}, [&] {
clearSpecialLayer();
_mainMenu.destroy();
}, Action::HideSpecialLayer, animated);
}
void LayerStackWidget::showMainMenu(
object_ptr<LayerWidget> layer,
anim::type animated) {
startAnimation([&] {
_mainMenu = std::move(layer);
initChildLayer(_mainMenu);
_mainMenu->moveToLeft(0, 0);
}, [&] {
clearLayers();
_specialLayer.destroy();
}, Action::ShowMainMenu, animated);
}
void LayerStackWidget::showBox(
object_ptr<BoxContent> box,
LayerOptions options,
anim::type animated) {
showLayer(
std::make_unique<BoxLayerWidget>(this, std::move(box)),
options,
animated);
}
void LayerStackWidget::showLayer(
std::unique_ptr<LayerWidget> layer,
LayerOptions options,
anim::type animated) {
if (options & LayerOption::KeepOther) {
if (options & LayerOption::ShowAfterOther) {
prependLayer(std::move(layer), animated);
} else {
appendLayer(std::move(layer), animated);
}
} else {
replaceLayer(std::move(layer), animated);
}
}
LayerWidget *LayerStackWidget::pushLayer(
std::unique_ptr<LayerWidget> layer,
anim::type animated) {
const auto oldLayer = currentLayer();
if (oldLayer) {
if (Ui::InFocusChain(oldLayer)) {
setFocus();
}
oldLayer->hide();
}
_layers.push_back(std::move(layer));
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::appendLayer(
std::unique_ptr<LayerWidget> layer,
anim::type animated) {
pushLayer(std::move(layer), animated);
}
void LayerStackWidget::prependLayer(
std::unique_ptr<LayerWidget> layer,
anim::type animated) {
if (_layers.empty()) {
replaceLayer(std::move(layer), animated);
return;
}
_layers.insert(
begin(_layers),
std::move(layer));
const auto raw = _layers.front().get();
raw->hide();
initChildLayer(raw);
}
void LayerStackWidget::replaceLayer(
std::unique_ptr<LayerWidget> layer,
anim::type animated) {
const auto pointer = pushLayer(std::move(layer), 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();
}
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 = base::make_weak(this);
while (!_closingLayers.empty()) {
const auto index = _closingLayers.size() - 1;
const auto layer = _closingLayers.back().get();
if (Ui::InFocusChain(layer)) {
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

View File

@@ -0,0 +1,244 @@
// 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 style {
struct Box;
} // namespace style
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 Show;
using ShowPtr = std::shared_ptr<Show>;
using ShowFactory = Fn<ShowPtr()>;
class LayerWidget : public RpWidget {
public:
using RpWidget::RpWidget;
virtual void parentResized() = 0;
virtual void showFinished() {
}
void setInnerFocus();
bool setClosing() {
if (!_closing) {
_closing = true;
closeHook();
return true;
}
return false;
}
bool overlaps(const QRect &globalRect);
void setClosedCallback(Fn<void()> callback) {
_closedCallback = std::move(callback);
}
void setResizedCallback(Fn<void()> callback) {
_resizedCallback = std::move(callback);
}
virtual bool takeToThirdSection() {
return false;
}
virtual bool showSectionInternal(
not_null<::Window::SectionMemento*> memento,
const ::Window::SectionShow &params) {
return false;
}
virtual bool closeByOutsideClick() const {
return true;
}
void closeLayer() {
if (const auto callback = base::take(_closedCallback)) {
callback();
}
}
protected:
void mousePressEvent(QMouseEvent *e) override;
void resizeEvent(QResizeEvent *e) override;
bool focusNextPrevChild(bool next) override;
virtual void doSetInnerFocus() {
setFocus();
}
virtual void closeHook() {
}
private:
bool _closing = false;
Fn<void()> _closedCallback;
Fn<void()> _resizedCallback;
};
class LayerStackWidget : public RpWidget {
public:
LayerStackWidget(QWidget *parent, ShowFactory showFactory);
void finishAnimating();
rpl::producer<> hideFinishEvents() const;
void setStyleOverrides(
const style::Box *boxSt,
const style::Box *layerSt);
[[nodiscard]] const style::Box *boxStyleOverrideLayer() const {
return _layerSt;
}
[[nodiscard]] const style::Box *boxStyleOverride() const {
return _boxSt;
}
[[nodiscard]] ShowFactory showFactory() const {
return _showFactory;
}
void showBox(
object_ptr<BoxContent> box,
LayerOptions options,
anim::type animated);
void showLayer(
std::unique_ptr<LayerWidget> layer,
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();
// If you need to divide animated hideAll().
void hideAllAnimatedPrepare();
void hideAllAnimatedRun();
bool showSectionInternal(
not_null<::Window::SectionMemento*> memento,
const ::Window::SectionShow &params);
bool layerShown() const;
const LayerWidget *topShownLayer() const;
~LayerStackWidget();
protected:
void keyPressEvent(QKeyEvent *e) override;
void mousePressEvent(QMouseEvent *e) override;
void resizeEvent(QResizeEvent *e) override;
private:
void appendLayer(
std::unique_ptr<LayerWidget> layer,
anim::type animated);
void prependLayer(
std::unique_ptr<LayerWidget> layer,
anim::type animated);
void replaceLayer(
std::unique_ptr<LayerWidget> layer,
anim::type animated);
void backgroundClicked();
LayerWidget *pushLayer(
std::unique_ptr<LayerWidget> layer,
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>
bool prepareAnimation(
SetupNew &&setupNewWidgets,
ClearOld &&clearOldWidgets,
Action action,
anim::type animated);
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;
ShowFactory _showFactory;
const style::Box *_boxSt = nullptr;
const style::Box *_layerSt = nullptr;
bool _hideByBackgroundClick = true;
rpl::event_stream<> _hideFinishStream;
};
} // namespace Ui

View File

@@ -0,0 +1,235 @@
// 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;
}
Box {
buttonPadding: margins;
buttonHeight: pixels;
buttonWide: bool;
button: RoundButton;
margin: margins;
title: FlatLabel;
bg: color;
titleAdditionalFg: color;
shadowIgnoreTopSkip: bool;
shadowIgnoreBottomSkip: bool;
}
boxDuration: 200;
boxRadius: 8px;
boxButtonFont: font(boxFontSize semibold);
defaultBoxButtonTextStyle: TextStyle(semiboldTextStyle) {
font: font(14px semibold);
}
defaultBoxButton: RoundButton(defaultLightButton) {
width: -30px;
height: 34px;
textTop: 7px;
style: defaultBoxButtonTextStyle;
}
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: roundShadowRadius8px;
boxTitleFont: font(16px semibold);
boxTitle: FlatLabel(defaultFlatLabel) {
textFg: boxTitleFg;
maxHeight: 24px;
style: TextStyle(defaultTextStyle) {
font: boxTitleFont;
}
}
boxTitlePosition: point(24px, 13px);
boxTitleHeight: 48px;
boxTitleAdditionalSkip: 9px;
boxTitleAdditionalFont: normalFont;
boxScroll: defaultSolidScroll;
boxRowPadding: margins(24px, 0px, 24px, 0px);
boxTopMargin: 8px;
boxTitleCloseIcon: icon {{ "box_button_close", boxTitleCloseFg }};
boxTitleCloseIconOver: icon {{ "box_button_close", boxTitleCloseFgOver }};
boxTitleClose: IconButton(defaultIconButton) {
width: boxTitleHeight;
height: boxTitleHeight;
icon: boxTitleCloseIcon;
iconOver: boxTitleCloseIconOver;
rippleAreaPosition: point(4px, 4px);
rippleAreaSize: 40px;
ripple: defaultRippleAnimationBgOver;
}
boxTitleMenu: IconButton(boxTitleClose) {
icon: icon {{ "title_menu_dots", boxTitleCloseFg }};
iconOver: icon {{ "title_menu_dots", boxTitleCloseFgOver }};
}
boxLinkButton: LinkButton(defaultLinkButton) {
font: boxTextFont;
overFont: font(boxFontSize underline);
}
boxOptionListPadding: margins(0px, 0px, 0px, 0px);
boxOptionListSkip: 20px;
boxWidth: 320px;
boxWideWidth: 364px;
boxPadding: margins(24px, 14px, 24px, 8px);
boxMaxListHeight: 492px;
boxLittleSkip: 10px;
boxMediumSkip: 20px;
defaultBox: Box {
buttonPadding: margins(6px, 10px, 10px, 10px);
buttonHeight: 34px;
button: defaultBoxButton;
margin: margins(0px, 10px, 0px, 10px);
bg: boxBg;
title: boxTitle;
titleAdditionalFg: boxTitleAdditionalFg;
}
layerBox: Box(defaultBox) {
}
boxLabel: FlatLabel(defaultFlatLabel) {
// Keep minWidth <= boxWidth - boxPadding.left - boxPadding.right.
minWidth: 256px;
align: align(topleft);
style: boxLabelStyle;
}
boxLoadingAnimation: InfiniteRadialAnimation(defaultInfiniteRadialAnimation) {
color: windowSubTextFg;
thickness: 2px;
}
boxLoadingSize: 20px;
defaultSubsectionTitle: FlatLabel(defaultFlatLabel) {
style: TextStyle(semiboldTextStyle) {
font: font(boxFontSize semibold);
}
textFg: windowActiveTextFg;
minWidth: 240px;
}
defaultSubsectionTitlePadding: margins(22px, 7px, 10px, 9px);
separatePanelBorderCacheSize: 60px;
separatePanelTitleHeight: 62px;
separatePanelNoTitleHeight: 32px;
separatePanelTitleBadgeSkip: 6px;
separatePanelClose: IconButton(boxTitleClose) {
width: 60px;
height: 60px;
rippleAreaPosition: point(8px, 8px);
rippleAreaSize: 44px;
ripple: defaultRippleAnimationBgOver;
}
separatePanelMenu: IconButton(separatePanelClose) {
width: 44px;
icon: icon {{ "title_menu_dots", boxTitleCloseFg }};
iconOver: icon {{ "title_menu_dots", boxTitleCloseFgOver }};
rippleAreaPosition: point(0px, 8px);
}
separatePanelMenuPosition: point(0px, 52px);
separatePanelTitleFont: font(18px semibold);
separatePanelTitle: FlatLabel(defaultFlatLabel) {
textFg: boxTitleFg;
maxHeight: 26px;
style: TextStyle(defaultTextStyle) {
font: separatePanelTitleFont;
}
}
separatePanelTitleTop: 18px;
separatePanelTitleLeft: 22px;
separatePanelTitleSkip: 0px;
separatePanelTitleBadgeTop: 4px;
separatePanelSearch: IconButton(separatePanelClose) {
width: 44px;
icon: icon {{ "box_search", boxTitleCloseFg }};
iconOver: icon {{ "box_search", boxTitleCloseFgOver }};
rippleAreaPosition: point(0px, 8px);
}
separatePanelBack: IconButton(separatePanelClose) {
icon: icon {{ "box_button_back", boxTitleCloseFg }};
iconOver: icon {{ "box_button_back", boxTitleCloseFgOver }};
}
separatePanelDuration: 150;
fullScreenPanelClose: IconButton {
width: 44px;
height: 44px;
icon: icon {{ "box_button_close", radialFg }};
iconOver: icon {{ "box_button_close", radialFg }};
iconPosition: point(-1px, -1px);
rippleAreaPosition: point(0px, 0px);
rippleAreaSize: 44px;
ripple: RippleAnimation(defaultRippleAnimation) {
color: shadowFg;
}
}
fullScreenPanelBack: IconButton(fullScreenPanelClose) {
icon: icon {{ "box_button_back", radialFg }};
iconOver: icon {{ "box_button_back", radialFg }};
}
fullScreenPanelMenu: IconButton(fullScreenPanelClose) {
icon: icon {{ "title_menu_dots", radialFg }};
iconOver: icon {{ "title_menu_dots", radialFg }};
}
webviewDialogButton: defaultBoxButton;
webviewDialogDestructiveButton: attentionBoxButton;
webviewDialogSubmit: RoundButton(defaultActiveButton) {
width: -48px;
height: 34px;
textTop: 7px;
style: defaultBoxButtonTextStyle;
}
webviewDialogPadding: margins(8px, 12px, 15px, 12px);

View File

@@ -0,0 +1,61 @@
// 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/show.h"
#include "ui/toast/toast.h"
namespace Ui {
namespace {
using namespace Toast;
} // namespace
void Show::showBox(
object_ptr<BoxContent> content,
LayerOptions options,
anim::type animated) const {
return showOrHideBoxOrLayer(std::move(content), options, animated);
}
void Show::showLayer(
std::unique_ptr<LayerWidget> layer,
LayerOptions options,
anim::type animated) const {
return showOrHideBoxOrLayer(std::move(layer), options, animated);
}
void Show::hideLayer(anim::type animated) const {
return showOrHideBoxOrLayer(v::null, LayerOptions(), animated);
}
base::weak_ptr<Instance> Show::showToast(Config &&config) const {
if (const auto strong = _lastToast.get()) {
strong->hideAnimated();
}
_lastToast = valid()
? Toast::Show(toastParent(), std::move(config))
: base::weak_ptr<Instance>();
return _lastToast;
}
base::weak_ptr<Instance> Show::showToast(
TextWithEntities &&text,
crl::time duration) const {
return showToast({ .text = std::move(text), .duration = duration });
}
base::weak_ptr<Instance> Show::showToast(
const QString &text,
crl::time duration) const {
return showToast({
.text = TextWithEntities{ text },
.duration = duration,
});
}
} // namespace Ui

View File

@@ -0,0 +1,81 @@
// 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/weak_ptr.h"
#include "ui/layers/layer_widget.h"
struct TextWithEntities;
namespace anim {
enum class type : uchar;
} // namespace anim
namespace Ui::Toast {
struct Config;
class Instance;
} // namespace Ui::Toast
namespace Ui {
class BoxContent;
class LayerWidget;
inline constexpr auto kZOrderBasic = 0;
class Show {
public:
virtual ~Show() = 0;
virtual void showOrHideBoxOrLayer(
std::variant<
v::null_t,
object_ptr<BoxContent>,
std::unique_ptr<LayerWidget>> &&layer,
LayerOptions options,
anim::type animated) const = 0;
[[nodiscard]] virtual not_null<QWidget*> toastParent() const = 0;
[[nodiscard]] virtual bool valid() const = 0;
virtual operator bool() const = 0;
void showBox(
object_ptr<BoxContent> content,
LayerOptions options = LayerOption::KeepOther,
anim::type animated = anim::type()) const;
void showLayer(
std::unique_ptr<LayerWidget> layer,
LayerOptions options = LayerOption::KeepOther,
anim::type animated = anim::type()) const;
void hideLayer(anim::type animated = anim::type()) const;
base::weak_ptr<Toast::Instance> showToast(Toast::Config &&config) const;
base::weak_ptr<Toast::Instance> showToast(
TextWithEntities &&text,
crl::time duration = 0) const;
base::weak_ptr<Toast::Instance> showToast(
const QString &text,
crl::time duration = 0) const;
template <
typename BoxType,
typename = std::enable_if_t<std::is_base_of_v<BoxContent, BoxType>>>
base::weak_qptr<BoxType> show(
object_ptr<BoxType> content,
LayerOptions options = LayerOption::KeepOther,
anim::type animated = anim::type()) const {
auto result = base::weak_qptr<BoxType>(content.data());
showBox(std::move(content), options, animated);
return result;
}
private:
mutable base::weak_ptr<Toast::Instance> _lastToast;
};
inline Show::~Show() = default;
} // namespace Ui