tdesktop/Telegram/SourceFiles/boxes/abstract_box.h

393 lines
10 KiB
C++

/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
#include "window/layer_widget.h"
#include "base/unique_qptr.h"
#include "ui/rp_widget.h"
namespace style {
struct RoundButton;
struct IconButton;
struct ScrollArea;
} // namespace style
namespace Ui {
class RoundButton;
class IconButton;
class ScrollArea;
class FlatLabel;
class FadeShadow;
} // namespace Ui
class BoxContent;
class BoxContentDelegate {
public:
virtual void setLayerType(bool layerType) = 0;
virtual void setTitle(Fn<TextWithEntities()> titleFactory) = 0;
virtual void setAdditionalTitle(Fn<QString()> additionalFactory) = 0;
virtual void setCloseByOutsideClick(bool close) = 0;
virtual void clearButtons() = 0;
virtual QPointer<Ui::RoundButton> addButton(Fn<QString()> textFactory, Fn<void()> clickCallback, const style::RoundButton &st) = 0;
virtual QPointer<Ui::RoundButton> addLeftButton(Fn<QString()> textFactory, Fn<void()> clickCallback, const style::RoundButton &st) = 0;
virtual QPointer<Ui::IconButton> addTopButton(const style::IconButton &st, Fn<void()> clickCallback) = 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) = 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 Ui::RpWidget, protected base::Subscriber {
Q_OBJECT
public:
BoxContent() {
setAttribute(Qt::WA_OpaquePaintEvent);
}
bool isBoxShown() const {
return getDelegate()->isBoxShown();
}
void closeBox() {
getDelegate()->closeBox();
}
void setTitle(Fn<QString()> titleFactory) {
if (titleFactory) {
getDelegate()->setTitle([titleFactory] { return TextWithEntities { titleFactory(), EntitiesInText() }; });
} else {
getDelegate()->setTitle(Fn<TextWithEntities()>());
}
}
void setTitle(Fn<TextWithEntities()> titleFactory) {
getDelegate()->setTitle(std::move(titleFactory));
}
void setAdditionalTitle(Fn<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<Ui::RoundButton> addButton(Fn<QString()> textFactory, Fn<void()> clickCallback = nullptr);
QPointer<Ui::RoundButton> addLeftButton(Fn<QString()> textFactory, Fn<void()> clickCallback = nullptr);
QPointer<Ui::IconButton> addTopButton(const style::IconButton &st, Fn<void()> clickCallback = nullptr) {
return getDelegate()->addTopButton(st, std::move(clickCallback));
}
QPointer<Ui::RoundButton> addButton(Fn<QString()> textFactory, const style::RoundButton &st) {
return getDelegate()->addButton(std::move(textFactory), nullptr, st);
}
QPointer<Ui::RoundButton> addButton(Fn<QString()> textFactory, Fn<void()> clickCallback, const style::RoundButton &st) {
return getDelegate()->addButton(std::move(textFactory), std::move(clickCallback), st);
}
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) {
getDelegate()->setDimensions(newWidth, maxHeight);
}
void setDimensionsToContent(
int newWidth,
not_null<Ui::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 static_object_cast<Widget>(doTakeInnerWidget());
}
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<Ui::ScrollArea> _scroll = { nullptr };
object_ptr<Ui::FadeShadow> _topShadow = { nullptr };
object_ptr<Ui::FadeShadow> _bottomShadow = { nullptr };
object_ptr<QTimer> _draggingScrollTimer = { nullptr };
int _draggingScrollDelta = 0;
rpl::event_stream<> _boxClosingStream;
};
class AbstractBox
: public Window::LayerWidget
, public BoxContentDelegate
, protected base::Subscriber {
public:
AbstractBox(
not_null<Window::LayerStackWidget*> layer,
object_ptr<BoxContent> content);
void parentResized() override;
void setLayerType(bool layerType) override;
void setTitle(Fn<TextWithEntities()> titleFactory) override;
void setAdditionalTitle(Fn<QString()> additionalFactory) override;
void showBox(
object_ptr<BoxContent> box,
LayerOptions options,
anim::type animated) override;
void clearButtons() override;
QPointer<Ui::RoundButton> addButton(Fn<QString()> textFactory, Fn<void()> clickCallback, const style::RoundButton &st) override;
QPointer<Ui::RoundButton> addLeftButton(Fn<QString()> textFactory, Fn<void()> clickCallback, const style::RoundButton &st) override;
QPointer<Ui::IconButton> addTopButton(const style::IconButton &st, Fn<void()> clickCallback) override;
void updateButtonsPositions() override;
QPointer<QWidget> outerContainer() override;
void setDimensions(int newWidth, int maxHeight) 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:
void paintAdditionalTitle(Painter &p);
void updateTitlePosition();
void refreshTitle();
void refreshAdditionalTitle();
void refreshLang();
bool hasTitle() const;
int titleHeight() const;
int buttonsHeight() const;
int buttonsTop() const;
int contentTop() const;
int countFullHeight() const;
int countRealHeight() const;
void updateSize();
not_null<Window::LayerStackWidget*> _layer;
int _fullHeight = 0;
bool _noContentMargin = false;
int _maxContentHeight = 0;
object_ptr<BoxContent> _content;
object_ptr<Ui::FlatLabel> _title = { nullptr };
Fn<TextWithEntities()> _titleFactory;
QString _additionalTitle;
Fn<QString()> _additionalTitleFactory;
int _titleLeft = 0;
int _titleTop = 0;
bool _layerType = false;
bool _closeByOutsideClick = true;
std::vector<object_ptr<Ui::RoundButton>> _buttons;
object_ptr<Ui::RoundButton> _leftButton = { nullptr };
base::unique_qptr<Ui::IconButton> _topButton = { nullptr };
};
class BoxContentDivider : public Ui::RpWidget {
public:
BoxContentDivider(QWidget *parent);
BoxContentDivider(QWidget *parent, int height);
protected:
void paintEvent(QPaintEvent *e) override;
};
enum CreatingGroupType {
CreatingGroupNone,
CreatingGroupGroup,
CreatingGroupChannel,
};
class BoxPointer {
public:
BoxPointer() = default;
BoxPointer(const BoxPointer &other) = default;
BoxPointer(BoxPointer &&other) : _value(base::take(other._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();
}
private:
void destroy() {
if (const auto value = base::take(_value)) {
value->closeBox();
}
}
QPointer<BoxContent> _value;
};