Add nice scroll to the bottom of the Info layer.

This commit is contained in:
John Preston 2017-11-23 16:48:56 +04:00
parent 67d4eb688a
commit 981063596a
9 changed files with 162 additions and 49 deletions

View File

@ -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();
}

View File

@ -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;

View File

@ -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;
}

View File

@ -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;
};

View File

@ -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());
}

View File

@ -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 &params) {
@ -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

View File

@ -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;
};

View File

@ -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()));
}
}
}

View File

@ -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: