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 <rpl/range.h>
#include "window/window_controller.h" #include "window/window_controller.h"
#include "ui/widgets/scroll_area.h" #include "ui/widgets/scroll_area.h"
#include "ui/wrap/padding_wrap.h"
#include "ui/search_field_controller.h" #include "ui/search_field_controller.h"
#include "lang/lang_keys.h" #include "lang/lang_keys.h"
#include "info/profile/info_profile_widget.h" #include "info/profile/info_profile_widget.h"
@ -51,6 +52,9 @@ ContentWidget::ContentWidget(
setAttribute(Qt::WA_OpaquePaintEvent); setAttribute(Qt::WA_OpaquePaintEvent);
_controller->wrapValue() _controller->wrapValue()
| rpl::start_with_next([this](Wrap value) { | rpl::start_with_next([this](Wrap value) {
if (value != Wrap::Layer) {
applyAdditionalScroll(0);
}
_bg = (value == Wrap::Layer) _bg = (value == Wrap::Layer)
? st::boxBg ? st::boxBg
: st::profileBg; : st::profileBg;
@ -76,7 +80,7 @@ void ContentWidget::resizeEvent(QResizeEvent *e) {
} }
void ContentWidget::updateControlsGeometry() { void ContentWidget::updateControlsGeometry() {
if (!_inner) { if (!_innerWrap) {
return; return;
} }
auto newScrollTop = _scroll->scrollTop() + _topDelta; auto newScrollTop = _scroll->scrollTop() + _topDelta;
@ -84,7 +88,7 @@ void ContentWidget::updateControlsGeometry() {
QMargins(0, _scrollTopSkip.current(), 0, 0)); QMargins(0, _scrollTopSkip.current(), 0, 0));
if (_scroll->geometry() != scrollGeometry) { if (_scroll->geometry() != scrollGeometry) {
_scroll->setGeometry(scrollGeometry); _scroll->setGeometry(scrollGeometry);
_inner->resizeToWidth(_scroll->width()); _innerWrap->resizeToWidth(_scroll->width());
} }
if (!_scroll->isHidden()) { if (!_scroll->isHidden()) {
@ -92,7 +96,7 @@ void ContentWidget::updateControlsGeometry() {
_scroll->scrollToY(newScrollTop); _scroll->scrollToY(newScrollTop);
} }
auto scrollTop = _scroll->scrollTop(); auto scrollTop = _scroll->scrollTop();
_inner->setVisibleTopBottom( _innerWrap->setVisibleTopBottom(
scrollTop, scrollTop,
scrollTop + _scroll->height()); scrollTop + _scroll->height());
} }
@ -128,22 +132,39 @@ Ui::RpWidget *ContentWidget::doSetInnerWidget(
object_ptr<RpWidget> inner) { object_ptr<RpWidget> inner) {
using namespace rpl::mappers; using namespace rpl::mappers;
_inner = _scroll->setOwnedWidget(std::move(inner)); _innerWrap = _scroll->setOwnedWidget(
_inner->move(0, 0); object_ptr<Ui::PaddingWrap<Ui::RpWidget>>(
this,
std::move(inner),
_innerWrap ? _innerWrap->padding() : style::margins()));
_innerWrap->move(0, 0);
rpl::combine( rpl::combine(
_scroll->scrollTopValue(), _scroll->scrollTopValue(),
_scroll->heightValue(), _scroll->heightValue(),
_inner->desiredHeightValue(), _innerWrap->entity()->desiredHeightValue(),
tuple(_1, _1 + _2, _3)) tuple(_1, _1 + _2, _3))
| rpl::start_with_next([inner = _inner]( | rpl::start_with_next([this](
int top, int top,
int bottom, int bottom,
int desired) { int desired) {
inner->setVisibleTopBottom(top, bottom); _innerDesiredHeight = desired;
}, _inner->lifetime()); _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) { void ContentWidget::setScrollTopSkip(int scrollTopSkip) {
@ -158,10 +179,16 @@ rpl::producer<int> ContentWidget::scrollHeightValue() const {
return _scroll->heightValue(); return _scroll->heightValue();
} }
void ContentWidget::applyAdditionalScroll(int additionalScroll) {
if (_innerWrap) {
_innerWrap->setPadding({ 0, 0, 0, additionalScroll });
}
}
rpl::producer<int> ContentWidget::desiredHeightValue() const { rpl::producer<int> ContentWidget::desiredHeightValue() const {
using namespace rpl::mappers; using namespace rpl::mappers;
return rpl::combine( return rpl::combine(
_inner->desiredHeightValue(), _innerWrap->entity()->desiredHeightValue(),
_scrollTopSkip.value()) _scrollTopSkip.value())
| rpl::map(_1 + _2); | rpl::map(_1 + _2);
} }
@ -178,6 +205,10 @@ bool ContentWidget::hasTopBarShadow() const {
return (_scroll->scrollTop() > 0); return (_scroll->scrollTop() > 0);
} }
void ContentWidget::setInnerFocus() {
_innerWrap->entity()->setFocus();
}
int ContentWidget::scrollTopSave() const { int ContentWidget::scrollTopSave() const {
return _scroll->scrollTop(); return _scroll->scrollTop();
} }

View File

@ -31,6 +31,8 @@ enum class SharedMediaType : char;
namespace Ui { namespace Ui {
class ScrollArea; class ScrollArea;
struct ScrollToRequest; struct ScrollToRequest;
template <typename Widget>
class PaddingWrap;
} // namespace Ui } // namespace Ui
namespace Info { namespace Info {
@ -57,9 +59,7 @@ public:
rpl::producer<bool> desiredShadowVisibility() const; rpl::producer<bool> desiredShadowVisibility() const;
bool hasTopBarShadow() const; bool hasTopBarShadow() const;
virtual void setInnerFocus() { virtual void setInnerFocus();
_inner->setFocus();
}
// When resizing the widget with top edge moved up or down and we // 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 // want to add this top movement to the scroll position, so inner
@ -67,6 +67,9 @@ public:
void setGeometryWithTopMoved( void setGeometryWithTopMoved(
const QRect &newGeometry, const QRect &newGeometry,
int topDelta); int topDelta);
void applyAdditionalScroll(int additionalScroll);
int scrollTillBottom(int forHeight) const;
rpl::producer<int> scrollTillBottomChanges() const;
// Float player interface. // Float player interface.
bool wheelEventFromFloatPlayer(QEvent *e); bool wheelEventFromFloatPlayer(QEvent *e);
@ -106,9 +109,11 @@ private:
style::color _bg; style::color _bg;
rpl::variable<int> _scrollTopSkip = -1; rpl::variable<int> _scrollTopSkip = -1;
rpl::event_stream<int> _scrollTillBottomChanges;
object_ptr<Ui::ScrollArea> _scroll; object_ptr<Ui::ScrollArea> _scroll;
Ui::RpWidget *_inner = nullptr; Ui::PaddingWrap<Ui::RpWidget> *_innerWrap = nullptr;
base::unique_qptr<Ui::RpWidget> _searchField = nullptr; base::unique_qptr<Ui::RpWidget> _searchField = nullptr;
int _innerDesiredHeight = 0;
// Saving here topDelta in setGeometryWithTopMoved() to get it passed to resizeEvent(). // Saving here topDelta in setGeometryWithTopMoved() to get it passed to resizeEvent().
int _topDelta = 0; int _topDelta = 0;

View File

@ -55,12 +55,17 @@ LayerWidget::LayerWidget(
} }
void LayerWidget::setupHeightConsumers() { void LayerWidget::setupHeightConsumers() {
_content->scrollTillBottomChanges()
| rpl::filter([this] { return !_inResize; })
| rpl::start_with_next([this] {
resizeToWidth(width());
}, lifetime());
_content->desiredHeightValue() _content->desiredHeightValue()
| rpl::start_with_next([this](int height) { | rpl::start_with_next([this](int height) {
if (!_content) return;
accumulate_max(_desiredHeight, height); accumulate_max(_desiredHeight, height);
resizeToWidth(width()); if (_content && !_inResize) {
_content->forceContentRepaint(); resizeToWidth(width());
}
}, lifetime()); }, lifetime());
} }
@ -129,35 +134,52 @@ int LayerWidget::resizeGetHeight(int newWidth) {
if (!parentWidget() || !_content) { if (!parentWidget() || !_content) {
return 0; return 0;
} }
_inResize = true;
auto guard = gsl::finally([&] { _inResize = false; });
auto parentSize = parentWidget()->size(); auto parentSize = parentWidget()->size();
auto windowWidth = parentSize.width(); auto windowWidth = parentSize.width();
auto windowHeight = parentSize.height(); auto windowHeight = parentSize.height();
auto newLeft = (windowWidth - newWidth) / 2;
auto newTop = snap( auto newTop = snap(
windowHeight / 24, windowHeight / 24,
st::infoLayerTopMinimal, st::infoLayerTopMinimal,
st::infoLayerTopMaximal); st::infoLayerTopMaximal);
auto newHeight = st::boxRadius + _desiredHeight + st::boxRadius; auto newBottom = newTop;
accumulate_min(newHeight, windowHeight - newTop); auto desiredHeight = st::boxRadius + _desiredHeight + st::boxRadius;
accumulate_min(desiredHeight, windowHeight - newTop - newBottom);
setRoundedCorners(newTop + newHeight < windowHeight);
// First resize content to new width and get the new desired height. // First resize content to new width and get the new desired height.
auto contentLeft = 0;
auto contentTop = st::boxRadius; auto contentTop = st::boxRadius;
auto contentHeight = newHeight - contentTop; auto contentBottom = st::boxRadius;
if (_roundedCorners) { auto contentWidth = newWidth;
contentHeight -= st::boxRadius; 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; return desiredHeight;
}
void LayerWidget::setRoundedCorners(bool rounded) {
_roundedCorners = rounded;
// setAttribute(Qt::WA_OpaquePaintEvent, !_roundedCorners);
} }
void LayerWidget::paintEvent(QPaintEvent *e) { void LayerWidget::paintEvent(QPaintEvent *e) {
@ -169,7 +191,7 @@ void LayerWidget::paintEvent(QPaintEvent *e) {
if (clip.intersects({ 0, 0, width(), r })) { if (clip.intersects({ 0, 0, width(), r })) {
parts |= RectPart::FullTop; parts |= RectPart::FullTop;
} }
if (_roundedCorners) { if (!_tillBottom) {
if (clip.intersects({ 0, height() - r, width(), r })) { if (clip.intersects({ 0, height() - r, width(), r })) {
parts |= RectPart::FullBottom; parts |= RectPart::FullBottom;
} }

View File

@ -60,13 +60,12 @@ protected:
private: private:
void setupHeightConsumers(); void setupHeightConsumers();
void setRoundedCorners(bool roundedCorners);
not_null<Window::Controller*> _controller; not_null<Window::Controller*> _controller;
object_ptr<WrapWidget> _content; object_ptr<WrapWidget> _content;
int _desiredHeight = 0; int _desiredHeight = 0;
bool _roundedCorners = false; bool _inResize = false;
bool _tillBottom = false;
}; };

View File

@ -49,10 +49,11 @@ SectionWidget::SectionWidget(
} }
void SectionWidget::init() { void SectionWidget::init() {
_content->move(0, 0);
sizeValue() sizeValue()
| rpl::start_with_next([wrap = _content.data()](QSize size) { | 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()); }, _content->lifetime());
} }

View File

@ -526,6 +526,7 @@ not_null<Ui::RpWidget*> WrapWidget::topWidget() const {
void WrapWidget::showContent(object_ptr<ContentWidget> content) { void WrapWidget::showContent(object_ptr<ContentWidget> content) {
_content = std::move(content); _content = std::move(content);
_content->show(); _content->show();
_additionalScroll = 0;
//_anotherTabMemento = nullptr; //_anotherTabMemento = nullptr;
finishShowContent(); finishShowContent();
} }
@ -536,6 +537,7 @@ void WrapWidget::finishShowContent() {
_desiredHeights.fire(desiredHeightForContent()); _desiredHeights.fire(desiredHeightForContent());
_desiredShadowVisibilities.fire(_content->desiredShadowVisibility()); _desiredShadowVisibilities.fire(_content->desiredShadowVisibility());
_selectedLists.fire(_content->selectedListValue()); _selectedLists.fire(_content->selectedListValue());
_scrollTillBottomChanges.fire(_content->scrollTillBottomChanges());
_topShadow->raise(); _topShadow->raise();
_topShadow->finishAnimating(); _topShadow->finishAnimating();
@ -754,9 +756,7 @@ std::unique_ptr<Window::SectionMemento> WrapWidget::createMemento() {
} }
rpl::producer<int> WrapWidget::desiredHeightValue() const { rpl::producer<int> WrapWidget::desiredHeightValue() const {
return return _desiredHeights.events_starting_with(desiredHeightForContent())
rpl::single(desiredHeightForContent())
| rpl::then(_desiredHeights.events())
| rpl::flatten_latest(); | rpl::flatten_latest();
} }
@ -764,7 +764,6 @@ QRect WrapWidget::contentGeometry() const {
return rect().marginsRemoved({ 0, topWidget()->height(), 0, 0 }); return rect().marginsRemoved({ 0, topWidget()->height(), 0, 0 });
} }
bool WrapWidget::returnToFirstStackFrame( bool WrapWidget::returnToFirstStackFrame(
not_null<ContentMemento*> memento, not_null<ContentMemento*> memento,
const Window::SectionShow &params) { const Window::SectionShow &params) {
@ -912,6 +911,37 @@ object_ptr<Ui::RpWidget> WrapWidget::createTopBarSurrogate(
return nullptr; 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; WrapWidget::~WrapWidget() = default;
} // namespace Info } // namespace Info

View File

@ -122,6 +122,10 @@ public:
object_ptr<Ui::RpWidget> createTopBarSurrogate(QWidget *parent); object_ptr<Ui::RpWidget> createTopBarSurrogate(QWidget *parent);
void updateGeometry(QRect newGeometry, int additionalScroll);
int scrollTillBottom(int forHeight) const;
rpl::producer<int> scrollTillBottomChanges() const;
~WrapWidget(); ~WrapWidget();
protected: protected:
@ -199,6 +203,7 @@ private:
rpl::variable<Wrap> _wrap; rpl::variable<Wrap> _wrap;
std::unique_ptr<Controller> _controller; std::unique_ptr<Controller> _controller;
object_ptr<ContentWidget> _content = { nullptr }; object_ptr<ContentWidget> _content = { nullptr };
int _additionalScroll = 0;
//object_ptr<Ui::PlainShadow> _topTabsBackground = { nullptr }; //object_ptr<Ui::PlainShadow> _topTabsBackground = { nullptr };
//object_ptr<Ui::SettingsSlider> _topTabs = { nullptr }; //object_ptr<Ui::SettingsSlider> _topTabs = { nullptr };
object_ptr<TopBar> _topBar = { nullptr }; object_ptr<TopBar> _topBar = { nullptr };
@ -216,6 +221,7 @@ private:
rpl::event_stream<rpl::producer<int>> _desiredHeights; rpl::event_stream<rpl::producer<int>> _desiredHeights;
rpl::event_stream<rpl::producer<bool>> _desiredShadowVisibilities; rpl::event_stream<rpl::producer<bool>> _desiredShadowVisibilities;
rpl::event_stream<rpl::producer<SelectedItems>> _selectedLists; 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, QWidget *parent,
object_ptr<RpWidget> &&child, object_ptr<RpWidget> &&child,
const style::margins &padding) const style::margins &padding)
: Parent(parent, std::move(child)) : Parent(parent, std::move(child)) {
, _padding(padding) { setPadding(padding);
if (auto weak = wrapped()) { }
wrappedSizeUpdated(weak->size());
auto margins = weak->getMargins(); void PaddingWrap<RpWidget>::setPadding(const style::margins &padding) {
weak->moveToLeft(_padding.left() + margins.left(), _padding.top() + margins.top()); 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, object_ptr<RpWidget> &&child,
const style::margins &padding); const style::margins &padding);
style::margins padding() const {
return _padding;
}
void setPadding(const style::margins &padding);
int naturalWidth() const override; int naturalWidth() const override;
protected: protected: