mirror of
https://github.com/telegramdesktop/tdesktop
synced 2025-01-11 09:19:35 +00:00
Add nice scroll to the bottom of the Info layer.
This commit is contained in:
parent
67d4eb688a
commit
981063596a
@ -25,6 +25,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
#include <rpl/range.h>
|
||||
#include "window/window_controller.h"
|
||||
#include "ui/widgets/scroll_area.h"
|
||||
#include "ui/wrap/padding_wrap.h"
|
||||
#include "ui/search_field_controller.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "info/profile/info_profile_widget.h"
|
||||
@ -51,6 +52,9 @@ ContentWidget::ContentWidget(
|
||||
setAttribute(Qt::WA_OpaquePaintEvent);
|
||||
_controller->wrapValue()
|
||||
| rpl::start_with_next([this](Wrap value) {
|
||||
if (value != Wrap::Layer) {
|
||||
applyAdditionalScroll(0);
|
||||
}
|
||||
_bg = (value == Wrap::Layer)
|
||||
? st::boxBg
|
||||
: st::profileBg;
|
||||
@ -76,7 +80,7 @@ void ContentWidget::resizeEvent(QResizeEvent *e) {
|
||||
}
|
||||
|
||||
void ContentWidget::updateControlsGeometry() {
|
||||
if (!_inner) {
|
||||
if (!_innerWrap) {
|
||||
return;
|
||||
}
|
||||
auto newScrollTop = _scroll->scrollTop() + _topDelta;
|
||||
@ -84,7 +88,7 @@ void ContentWidget::updateControlsGeometry() {
|
||||
QMargins(0, _scrollTopSkip.current(), 0, 0));
|
||||
if (_scroll->geometry() != scrollGeometry) {
|
||||
_scroll->setGeometry(scrollGeometry);
|
||||
_inner->resizeToWidth(_scroll->width());
|
||||
_innerWrap->resizeToWidth(_scroll->width());
|
||||
}
|
||||
|
||||
if (!_scroll->isHidden()) {
|
||||
@ -92,7 +96,7 @@ void ContentWidget::updateControlsGeometry() {
|
||||
_scroll->scrollToY(newScrollTop);
|
||||
}
|
||||
auto scrollTop = _scroll->scrollTop();
|
||||
_inner->setVisibleTopBottom(
|
||||
_innerWrap->setVisibleTopBottom(
|
||||
scrollTop,
|
||||
scrollTop + _scroll->height());
|
||||
}
|
||||
@ -128,22 +132,39 @@ Ui::RpWidget *ContentWidget::doSetInnerWidget(
|
||||
object_ptr<RpWidget> inner) {
|
||||
using namespace rpl::mappers;
|
||||
|
||||
_inner = _scroll->setOwnedWidget(std::move(inner));
|
||||
_inner->move(0, 0);
|
||||
_innerWrap = _scroll->setOwnedWidget(
|
||||
object_ptr<Ui::PaddingWrap<Ui::RpWidget>>(
|
||||
this,
|
||||
std::move(inner),
|
||||
_innerWrap ? _innerWrap->padding() : style::margins()));
|
||||
_innerWrap->move(0, 0);
|
||||
|
||||
rpl::combine(
|
||||
_scroll->scrollTopValue(),
|
||||
_scroll->heightValue(),
|
||||
_inner->desiredHeightValue(),
|
||||
_innerWrap->entity()->desiredHeightValue(),
|
||||
tuple(_1, _1 + _2, _3))
|
||||
| rpl::start_with_next([inner = _inner](
|
||||
| rpl::start_with_next([this](
|
||||
int top,
|
||||
int bottom,
|
||||
int desired) {
|
||||
inner->setVisibleTopBottom(top, bottom);
|
||||
}, _inner->lifetime());
|
||||
_innerDesiredHeight = desired;
|
||||
_innerWrap->setVisibleTopBottom(top, bottom);
|
||||
_scrollTillBottomChanges.fire_copy(std::max(desired - bottom, 0));
|
||||
}, _innerWrap->lifetime());
|
||||
|
||||
return _inner;
|
||||
return _innerWrap->entity();
|
||||
}
|
||||
|
||||
int ContentWidget::scrollTillBottom(int forHeight) const {
|
||||
auto scrollHeight = forHeight - _scrollTopSkip.current();
|
||||
auto scrollBottom = _scroll->scrollTop() + scrollHeight;
|
||||
auto desired = _innerDesiredHeight;
|
||||
return std::max(desired - scrollBottom, 0);
|
||||
}
|
||||
|
||||
rpl::producer<int> ContentWidget::scrollTillBottomChanges() const {
|
||||
return _scrollTillBottomChanges.events();
|
||||
}
|
||||
|
||||
void ContentWidget::setScrollTopSkip(int scrollTopSkip) {
|
||||
@ -158,10 +179,16 @@ rpl::producer<int> ContentWidget::scrollHeightValue() const {
|
||||
return _scroll->heightValue();
|
||||
}
|
||||
|
||||
void ContentWidget::applyAdditionalScroll(int additionalScroll) {
|
||||
if (_innerWrap) {
|
||||
_innerWrap->setPadding({ 0, 0, 0, additionalScroll });
|
||||
}
|
||||
}
|
||||
|
||||
rpl::producer<int> ContentWidget::desiredHeightValue() const {
|
||||
using namespace rpl::mappers;
|
||||
return rpl::combine(
|
||||
_inner->desiredHeightValue(),
|
||||
_innerWrap->entity()->desiredHeightValue(),
|
||||
_scrollTopSkip.value())
|
||||
| rpl::map(_1 + _2);
|
||||
}
|
||||
@ -178,6 +205,10 @@ bool ContentWidget::hasTopBarShadow() const {
|
||||
return (_scroll->scrollTop() > 0);
|
||||
}
|
||||
|
||||
void ContentWidget::setInnerFocus() {
|
||||
_innerWrap->entity()->setFocus();
|
||||
}
|
||||
|
||||
int ContentWidget::scrollTopSave() const {
|
||||
return _scroll->scrollTop();
|
||||
}
|
||||
|
@ -31,6 +31,8 @@ enum class SharedMediaType : char;
|
||||
namespace Ui {
|
||||
class ScrollArea;
|
||||
struct ScrollToRequest;
|
||||
template <typename Widget>
|
||||
class PaddingWrap;
|
||||
} // namespace Ui
|
||||
|
||||
namespace Info {
|
||||
@ -57,9 +59,7 @@ public:
|
||||
rpl::producer<bool> desiredShadowVisibility() const;
|
||||
bool hasTopBarShadow() const;
|
||||
|
||||
virtual void setInnerFocus() {
|
||||
_inner->setFocus();
|
||||
}
|
||||
virtual void setInnerFocus();
|
||||
|
||||
// When resizing the widget with top edge moved up or down and we
|
||||
// want to add this top movement to the scroll position, so inner
|
||||
@ -67,6 +67,9 @@ public:
|
||||
void setGeometryWithTopMoved(
|
||||
const QRect &newGeometry,
|
||||
int topDelta);
|
||||
void applyAdditionalScroll(int additionalScroll);
|
||||
int scrollTillBottom(int forHeight) const;
|
||||
rpl::producer<int> scrollTillBottomChanges() const;
|
||||
|
||||
// Float player interface.
|
||||
bool wheelEventFromFloatPlayer(QEvent *e);
|
||||
@ -106,9 +109,11 @@ private:
|
||||
|
||||
style::color _bg;
|
||||
rpl::variable<int> _scrollTopSkip = -1;
|
||||
rpl::event_stream<int> _scrollTillBottomChanges;
|
||||
object_ptr<Ui::ScrollArea> _scroll;
|
||||
Ui::RpWidget *_inner = nullptr;
|
||||
Ui::PaddingWrap<Ui::RpWidget> *_innerWrap = nullptr;
|
||||
base::unique_qptr<Ui::RpWidget> _searchField = nullptr;
|
||||
int _innerDesiredHeight = 0;
|
||||
|
||||
// Saving here topDelta in setGeometryWithTopMoved() to get it passed to resizeEvent().
|
||||
int _topDelta = 0;
|
||||
|
@ -55,12 +55,17 @@ LayerWidget::LayerWidget(
|
||||
}
|
||||
|
||||
void LayerWidget::setupHeightConsumers() {
|
||||
_content->scrollTillBottomChanges()
|
||||
| rpl::filter([this] { return !_inResize; })
|
||||
| rpl::start_with_next([this] {
|
||||
resizeToWidth(width());
|
||||
}, lifetime());
|
||||
_content->desiredHeightValue()
|
||||
| rpl::start_with_next([this](int height) {
|
||||
if (!_content) return;
|
||||
accumulate_max(_desiredHeight, height);
|
||||
resizeToWidth(width());
|
||||
_content->forceContentRepaint();
|
||||
if (_content && !_inResize) {
|
||||
resizeToWidth(width());
|
||||
}
|
||||
}, lifetime());
|
||||
}
|
||||
|
||||
@ -129,35 +134,52 @@ int LayerWidget::resizeGetHeight(int newWidth) {
|
||||
if (!parentWidget() || !_content) {
|
||||
return 0;
|
||||
}
|
||||
_inResize = true;
|
||||
auto guard = gsl::finally([&] { _inResize = false; });
|
||||
|
||||
auto parentSize = parentWidget()->size();
|
||||
auto windowWidth = parentSize.width();
|
||||
auto windowHeight = parentSize.height();
|
||||
auto newLeft = (windowWidth - newWidth) / 2;
|
||||
auto newTop = snap(
|
||||
windowHeight / 24,
|
||||
st::infoLayerTopMinimal,
|
||||
st::infoLayerTopMaximal);
|
||||
auto newHeight = st::boxRadius + _desiredHeight + st::boxRadius;
|
||||
accumulate_min(newHeight, windowHeight - newTop);
|
||||
|
||||
setRoundedCorners(newTop + newHeight < windowHeight);
|
||||
auto newBottom = newTop;
|
||||
auto desiredHeight = st::boxRadius + _desiredHeight + st::boxRadius;
|
||||
accumulate_min(desiredHeight, windowHeight - newTop - newBottom);
|
||||
|
||||
// First resize content to new width and get the new desired height.
|
||||
auto contentLeft = 0;
|
||||
auto contentTop = st::boxRadius;
|
||||
auto contentHeight = newHeight - contentTop;
|
||||
if (_roundedCorners) {
|
||||
contentHeight -= st::boxRadius;
|
||||
auto contentBottom = st::boxRadius;
|
||||
auto contentWidth = newWidth;
|
||||
auto contentHeight = desiredHeight - contentTop - contentBottom;
|
||||
auto scrollTillBottom = _content->scrollTillBottom(contentHeight);
|
||||
auto additionalScroll = std::min(scrollTillBottom, newBottom);
|
||||
|
||||
desiredHeight += additionalScroll;
|
||||
contentHeight += additionalScroll;
|
||||
_tillBottom = (newTop + desiredHeight >= windowHeight);
|
||||
if (_tillBottom) {
|
||||
contentHeight += contentBottom;
|
||||
additionalScroll += contentBottom;
|
||||
}
|
||||
_content->setGeometry(0, contentTop, newWidth, contentHeight);
|
||||
_content->updateGeometry({
|
||||
contentLeft,
|
||||
contentTop,
|
||||
contentWidth,
|
||||
contentHeight }, additionalScroll);
|
||||
|
||||
moveToLeft((windowWidth - newWidth) / 2, newTop);
|
||||
auto newGeometry = QRect(newLeft, newTop, newWidth, desiredHeight);
|
||||
if (newGeometry != geometry()) {
|
||||
_content->forceContentRepaint();
|
||||
}
|
||||
if (newGeometry.topLeft() != geometry().topLeft()) {
|
||||
move(newGeometry.topLeft());
|
||||
}
|
||||
|
||||
return newHeight;
|
||||
}
|
||||
|
||||
void LayerWidget::setRoundedCorners(bool rounded) {
|
||||
_roundedCorners = rounded;
|
||||
// setAttribute(Qt::WA_OpaquePaintEvent, !_roundedCorners);
|
||||
return desiredHeight;
|
||||
}
|
||||
|
||||
void LayerWidget::paintEvent(QPaintEvent *e) {
|
||||
@ -169,7 +191,7 @@ void LayerWidget::paintEvent(QPaintEvent *e) {
|
||||
if (clip.intersects({ 0, 0, width(), r })) {
|
||||
parts |= RectPart::FullTop;
|
||||
}
|
||||
if (_roundedCorners) {
|
||||
if (!_tillBottom) {
|
||||
if (clip.intersects({ 0, height() - r, width(), r })) {
|
||||
parts |= RectPart::FullBottom;
|
||||
}
|
||||
|
@ -60,13 +60,12 @@ protected:
|
||||
private:
|
||||
void setupHeightConsumers();
|
||||
|
||||
void setRoundedCorners(bool roundedCorners);
|
||||
|
||||
not_null<Window::Controller*> _controller;
|
||||
object_ptr<WrapWidget> _content;
|
||||
|
||||
int _desiredHeight = 0;
|
||||
bool _roundedCorners = false;
|
||||
bool _inResize = false;
|
||||
bool _tillBottom = false;
|
||||
|
||||
};
|
||||
|
||||
|
@ -49,10 +49,11 @@ SectionWidget::SectionWidget(
|
||||
}
|
||||
|
||||
void SectionWidget::init() {
|
||||
_content->move(0, 0);
|
||||
sizeValue()
|
||||
| rpl::start_with_next([wrap = _content.data()](QSize size) {
|
||||
wrap->resize(size);
|
||||
auto wrapGeometry = QRect{ { 0, 0 }, size };
|
||||
auto additionalScroll = 0;
|
||||
wrap->updateGeometry(wrapGeometry, additionalScroll);
|
||||
}, _content->lifetime());
|
||||
}
|
||||
|
||||
|
@ -526,6 +526,7 @@ not_null<Ui::RpWidget*> WrapWidget::topWidget() const {
|
||||
void WrapWidget::showContent(object_ptr<ContentWidget> content) {
|
||||
_content = std::move(content);
|
||||
_content->show();
|
||||
_additionalScroll = 0;
|
||||
//_anotherTabMemento = nullptr;
|
||||
finishShowContent();
|
||||
}
|
||||
@ -536,6 +537,7 @@ void WrapWidget::finishShowContent() {
|
||||
_desiredHeights.fire(desiredHeightForContent());
|
||||
_desiredShadowVisibilities.fire(_content->desiredShadowVisibility());
|
||||
_selectedLists.fire(_content->selectedListValue());
|
||||
_scrollTillBottomChanges.fire(_content->scrollTillBottomChanges());
|
||||
_topShadow->raise();
|
||||
_topShadow->finishAnimating();
|
||||
|
||||
@ -754,9 +756,7 @@ std::unique_ptr<Window::SectionMemento> WrapWidget::createMemento() {
|
||||
}
|
||||
|
||||
rpl::producer<int> WrapWidget::desiredHeightValue() const {
|
||||
return
|
||||
rpl::single(desiredHeightForContent())
|
||||
| rpl::then(_desiredHeights.events())
|
||||
return _desiredHeights.events_starting_with(desiredHeightForContent())
|
||||
| rpl::flatten_latest();
|
||||
}
|
||||
|
||||
@ -764,7 +764,6 @@ QRect WrapWidget::contentGeometry() const {
|
||||
return rect().marginsRemoved({ 0, topWidget()->height(), 0, 0 });
|
||||
}
|
||||
|
||||
|
||||
bool WrapWidget::returnToFirstStackFrame(
|
||||
not_null<ContentMemento*> memento,
|
||||
const Window::SectionShow ¶ms) {
|
||||
@ -912,6 +911,37 @@ object_ptr<Ui::RpWidget> WrapWidget::createTopBarSurrogate(
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void WrapWidget::updateGeometry(QRect newGeometry, int additionalScroll) {
|
||||
auto scrollChanged = (_additionalScroll != additionalScroll);
|
||||
auto geometryChanged = (geometry() != newGeometry);
|
||||
auto shrinkingContent = (additionalScroll < _additionalScroll);
|
||||
_additionalScroll = additionalScroll;
|
||||
|
||||
if (geometryChanged) {
|
||||
if (shrinkingContent) {
|
||||
setGeometry(newGeometry);
|
||||
}
|
||||
if (scrollChanged) {
|
||||
_content->applyAdditionalScroll(additionalScroll);
|
||||
}
|
||||
if (!shrinkingContent) {
|
||||
setGeometry(newGeometry);
|
||||
}
|
||||
} else if (scrollChanged) {
|
||||
_content->applyAdditionalScroll(additionalScroll);
|
||||
}
|
||||
}
|
||||
|
||||
int WrapWidget::scrollTillBottom(int forHeight) const {
|
||||
return _content->scrollTillBottom(forHeight - topWidget()->height());
|
||||
}
|
||||
|
||||
rpl::producer<int> WrapWidget::scrollTillBottomChanges() const {
|
||||
return _scrollTillBottomChanges.events_starting_with(
|
||||
_content->scrollTillBottomChanges()
|
||||
) | rpl::flatten_latest();
|
||||
}
|
||||
|
||||
WrapWidget::~WrapWidget() = default;
|
||||
|
||||
} // namespace Info
|
||||
|
@ -122,6 +122,10 @@ public:
|
||||
|
||||
object_ptr<Ui::RpWidget> createTopBarSurrogate(QWidget *parent);
|
||||
|
||||
void updateGeometry(QRect newGeometry, int additionalScroll);
|
||||
int scrollTillBottom(int forHeight) const;
|
||||
rpl::producer<int> scrollTillBottomChanges() const;
|
||||
|
||||
~WrapWidget();
|
||||
|
||||
protected:
|
||||
@ -199,6 +203,7 @@ private:
|
||||
rpl::variable<Wrap> _wrap;
|
||||
std::unique_ptr<Controller> _controller;
|
||||
object_ptr<ContentWidget> _content = { nullptr };
|
||||
int _additionalScroll = 0;
|
||||
//object_ptr<Ui::PlainShadow> _topTabsBackground = { nullptr };
|
||||
//object_ptr<Ui::SettingsSlider> _topTabs = { nullptr };
|
||||
object_ptr<TopBar> _topBar = { nullptr };
|
||||
@ -216,6 +221,7 @@ private:
|
||||
rpl::event_stream<rpl::producer<int>> _desiredHeights;
|
||||
rpl::event_stream<rpl::producer<bool>> _desiredShadowVisibilities;
|
||||
rpl::event_stream<rpl::producer<SelectedItems>> _selectedLists;
|
||||
rpl::event_stream<rpl::producer<int>> _scrollTillBottomChanges;
|
||||
|
||||
};
|
||||
|
||||
|
@ -26,13 +26,27 @@ PaddingWrap<RpWidget>::PaddingWrap(
|
||||
QWidget *parent,
|
||||
object_ptr<RpWidget> &&child,
|
||||
const style::margins &padding)
|
||||
: Parent(parent, std::move(child))
|
||||
, _padding(padding) {
|
||||
if (auto weak = wrapped()) {
|
||||
wrappedSizeUpdated(weak->size());
|
||||
: Parent(parent, std::move(child)) {
|
||||
setPadding(padding);
|
||||
}
|
||||
|
||||
auto margins = weak->getMargins();
|
||||
weak->moveToLeft(_padding.left() + margins.left(), _padding.top() + margins.top());
|
||||
void PaddingWrap<RpWidget>::setPadding(const style::margins &padding) {
|
||||
if (_padding != padding) {
|
||||
auto oldWidth = width() - _padding.left() - _padding.top();
|
||||
_padding = padding;
|
||||
|
||||
if (auto weak = wrapped()) {
|
||||
wrappedSizeUpdated(weak->size());
|
||||
|
||||
auto margins = weak->getMargins();
|
||||
weak->moveToLeft(
|
||||
_padding.left() + margins.left(),
|
||||
_padding.top() + margins.top());
|
||||
} else {
|
||||
resize(QSize(
|
||||
_padding.left() + oldWidth + _padding.right(),
|
||||
_padding.top() + _padding.bottom()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -37,6 +37,11 @@ public:
|
||||
object_ptr<RpWidget> &&child,
|
||||
const style::margins &padding);
|
||||
|
||||
style::margins padding() const {
|
||||
return _padding;
|
||||
}
|
||||
void setPadding(const style::margins &padding);
|
||||
|
||||
int naturalWidth() const override;
|
||||
|
||||
protected:
|
||||
|
Loading…
Reference in New Issue
Block a user