From 4424dbf64a3d8ba8453a4b18953bbcd6989bf47b Mon Sep 17 00:00:00 2001 From: John Preston Date: Sat, 14 Jan 2017 21:50:16 +0300 Subject: [PATCH] Allow to resize chats list. One more mode added (narrow chats list). --- Telegram/Resources/icons/install_update.png | Bin 0 -> 184 bytes .../Resources/icons/install_update@2x.png | Bin 0 -> 285 bytes Telegram/Resources/langs/lang.strings | 1 - Telegram/SourceFiles/core/observer.h | 61 +++- Telegram/SourceFiles/core/type_traits.h | 3 + Telegram/SourceFiles/dialogs/dialogs.style | 4 + .../SourceFiles/dialogs/dialogs_layout.cpp | 31 +- Telegram/SourceFiles/dialogswidget.cpp | 92 +++++- Telegram/SourceFiles/dialogswidget.h | 8 +- Telegram/SourceFiles/facades.cpp | 14 +- Telegram/SourceFiles/facades.h | 40 ++- Telegram/SourceFiles/history.cpp | 2 +- Telegram/SourceFiles/history/history.style | 2 + Telegram/SourceFiles/history/history_item.cpp | 2 +- .../SourceFiles/history/history_message.cpp | 12 +- .../history/history_service_layout.cpp | 2 +- Telegram/SourceFiles/historywidget.cpp | 9 +- Telegram/SourceFiles/intro/introwidget.cpp | 1 + Telegram/SourceFiles/localstorage.cpp | 12 +- Telegram/SourceFiles/mainwidget.cpp | 308 ++++++++++++++---- Telegram/SourceFiles/mainwidget.h | 13 +- Telegram/SourceFiles/mainwindow.cpp | 13 +- Telegram/SourceFiles/passcodewidget.cpp | 1 + .../profile/profile_common_groups_section.cpp | 2 +- .../profile/profile_common_groups_section.h | 5 +- .../SourceFiles/profile/profile_widget.cpp | 2 +- Telegram/SourceFiles/profile/profile_widget.h | 3 +- .../settings/settings_background_widget.cpp | 4 +- .../SourceFiles/ui/widgets/input_fields.cpp | 28 +- .../SourceFiles/ui/widgets/input_fields.h | 2 + .../SourceFiles/window/section_widget.cpp | 5 + Telegram/SourceFiles/window/section_widget.h | 13 +- Telegram/SourceFiles/window/window.style | 3 +- 33 files changed, 558 insertions(+), 140 deletions(-) create mode 100644 Telegram/Resources/icons/install_update.png create mode 100644 Telegram/Resources/icons/install_update@2x.png diff --git a/Telegram/Resources/icons/install_update.png b/Telegram/Resources/icons/install_update.png new file mode 100644 index 0000000000000000000000000000000000000000..56d040a4064685b2f2830f2cea2d2af42ec1d8f0 GIT binary patch literal 184 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=ffJdQTU}kcif|R~@++6ggM}=KhvH z8(hJ9J#jYYt*0vlnEyNPer4a()p_D&(!AMEff^hn6Vg)BQd`p7R{iQ)wd`Ak``SH> zf%^JNRj0T+Zny|qPFvsMA#uRFub62{64=R1H&81T z=N23E3tli}`J3wsUcRi9)Mt3D!a!elfhPklTv-{~yJ`|wa?knt10RD!UoTqKarpa< zkGx7hK3X{Se>8BK|53wf{YM3-{U6(vzsS@ZINkr)uJlE&Ucu@A$JxqvlK&jz_%69$ z`OqG%H)(7CubWw%XY^C`KVy``WCq;$^ACpObuGI-{ScpkUse7@wO+41yXt#@{$=oV L^>bP0l+XkK_}p)z literal 0 HcmV?d00001 diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index e450dd283..7b098da3f 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -771,7 +771,6 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org "lng_record_cancel" = "Release outside this field to cancel"; "lng_will_be_notified" = "Members will be notified when you post"; "lng_wont_be_notified" = "Members will not be notified when you post"; -"lng_empty_history" = ""; "lng_willbe_history" = "Please select a chat to start messaging"; "lng_from_you" = "You"; "lng_from_draft" = "Draft"; diff --git a/Telegram/SourceFiles/core/observer.h b/Telegram/SourceFiles/core/observer.h index 999118869..8278baa15 100644 --- a/Telegram/SourceFiles/core/observer.h +++ b/Telegram/SourceFiles/core/observer.h @@ -31,12 +31,9 @@ void RegisterPendingObservable(ObservableCallHandlers *handlers); void UnregisterActiveObservable(ObservableCallHandlers *handlers); void UnregisterObservable(ObservableCallHandlers *handlers); -template -using EventParamType = typename base::type_traits::parameter_type; - template struct SubscriptionHandlerHelper { - using type = base::lambda)>; + using type = base::lambda)>; }; template <> @@ -355,6 +352,52 @@ public: template > class Observable : public internal::BaseObservable::is_fast_copy_type::value> { +public: + Observable() = default; + Observable(const Observable &other) = delete; + Observable(Observable &&other) = default; + Observable &operator=(const Observable &other) = delete; + Observable &operator=(Observable &&other) = default; + +}; + +template +class Variable { +public: + Variable(parameter_type startValue = Type()) : _value(startValue) { + } + Variable(Variable &&other) = default; + Variable &operator=(Variable &&other) = default; + + parameter_type value() const { + return _value; + } + + void setForced(parameter_type newValue, bool sync = false) { + _value = newValue; + _observable.notify(_value, sync); + } + + void set(parameter_type newValue, bool sync = false) { + if (_value != newValue) { + setForced(newValue, sync); + } + } + + template + void process(Callback callback, bool sync = false) { + callback(_value); + _observable.notify(_value, sync); + } + + Observable &observable() { + return _observable; + } + +private: + Type _value; + Observable _observable; + }; class Subscriber { @@ -370,6 +413,16 @@ protected: return subscribe(*observable, std_::forward(handler)); } + template + int subscribe(base::Variable &variable, Lambda &&handler) { + return subscribe(variable.observable(), std_::forward(handler)); + } + + template + int subscribe(base::Variable *variable, Lambda &&handler) { + return subscribe(variable->observable(), std_::forward(handler)); + } + void unsubscribe(int index) { if (!index) return; t_assert(index > 0 && index <= _subscriptions.size()); diff --git a/Telegram/SourceFiles/core/type_traits.h b/Telegram/SourceFiles/core/type_traits.h index 71f073f81..3f8f7fc0e 100644 --- a/Telegram/SourceFiles/core/type_traits.h +++ b/Telegram/SourceFiles/core/type_traits.h @@ -126,4 +126,7 @@ struct type_traits { using pointed_type = internal::remove_pointer_t; }; +template +using parameter_type = typename type_traits::parameter_type; + } // namespace base diff --git a/Telegram/SourceFiles/dialogs/dialogs.style b/Telegram/SourceFiles/dialogs/dialogs.style index 954709b8f..f32e7581e 100644 --- a/Telegram/SourceFiles/dialogs/dialogs.style +++ b/Telegram/SourceFiles/dialogs/dialogs.style @@ -53,6 +53,7 @@ dialogsSkip: 8px; dialogsWidthMin: 260px; dialogsWidthMax: 540px; +dialogsWidthDuration: 120; dialogsTextWidthMin: 150px; dialogsScroll: ScrollArea(defaultScrollArea) { topsh: 0px; @@ -191,6 +192,9 @@ dialogsUpdateButton: FlatButton { } } +dialogsInstallUpdate: icon {{ "install_update", activeButtonFg }}; +dialogsInstallUpdateOver: icon {{ "install_update", activeButtonFgOver }}; + dialogsForwardHeight: 32px; dialogsForwardTextLeft: 35px; dialogsForwardTextTop: 6px; diff --git a/Telegram/SourceFiles/dialogs/dialogs_layout.cpp b/Telegram/SourceFiles/dialogs/dialogs_layout.cpp index a83374e87..b4710d6a9 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_layout.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_layout.cpp @@ -58,8 +58,8 @@ void paintRowDate(Painter &p, const QDateTime &date, QRect &rectForName, bool ac p.drawText(rectForName.left() + rectForName.width() + st::dialogsDateSkip, rectForName.top() + st::msgNameFont->height - st::msgDateFont->descent, dt); } -template -void paintRow(Painter &p, const RippleRow *row, History *history, HistoryItem *item, Data::Draft *draft, QDateTime date, int fullWidth, bool active, bool selected, bool onlyBackground, TimeMs ms, PaintItemCallback paintItemCallback) { +template +void paintRow(Painter &p, const RippleRow *row, History *history, HistoryItem *item, Data::Draft *draft, QDateTime date, int fullWidth, bool active, bool selected, bool onlyBackground, TimeMs ms, PaintItemCallback paintItemCallback, PaintCounterCallback paintCounterCallback) { QRect fullRect(0, 0, fullWidth, st::dialogsRowHeight); p.fillRect(fullRect, active ? st::dialogsBgActive : (selected ? st::dialogsBgOver : st::dialogsBg)); row->paintRipple(p, 0, 0, fullWidth, ms, &(active ? st::dialogsRippleBgActive : st::dialogsRippleBg)->c); @@ -69,6 +69,13 @@ void paintRow(Painter &p, const RippleRow *row, History *history, HistoryItem *i userpicPeer->paintUserpicLeft(p, st::dialogsPadding.x(), st::dialogsPadding.y(), fullWidth, st::dialogsPhotoSize); auto nameleft = st::dialogsPadding.x() + st::dialogsPhotoSize + st::dialogsPhotoPadding; + if (fullWidth <= nameleft) { + if (!draft && item && !item->isEmpty()) { + paintCounterCallback(); + } + return; + } + auto namewidth = fullWidth - nameleft - st::dialogsPadding.x(); QRect rectForName(nameleft, st::dialogsPadding.y() + st::dialogsNameTop, namewidth, st::msgNameFont->height); @@ -105,8 +112,7 @@ void paintRow(Painter &p, const RippleRow *row, History *history, HistoryItem *i auto &color = active ? st::dialogsTextFgServiceActive : (selected ? st::dialogsTextFgServiceOver : st::dialogsTextFgService); p.setFont(st::dialogsTextFont); if (!history->paintSendAction(p, nameleft, texttop, namewidth, fullWidth, color, ms)) { - p.setPen(color); - p.drawText(nameleft, texttop + st::msgNameFont->ascent, lang(lng_empty_history)); + // Empty history } } else if (!item->isEmpty()) { paintRowDate(p, date, rectForName, active, selected); @@ -299,6 +305,22 @@ void RowPainter::paint(Painter &p, const Row *row, int fullWidth, bool active, b if (!history->paintSendAction(p, nameleft, texttop, availableWidth, fullWidth, color, ms)) { item->drawInDialog(p, QRect(nameleft, texttop, availableWidth, st::dialogsTextFont->height), active, selected, history->textCachedFor, history->lastItemTextCache); } + }, [&p, fullWidth, active, selected, ms, history, unreadCount] { + if (unreadCount) { + auto counter = QString::number(unreadCount); + if (counter.size() > 4) { + counter = qsl("..") + counter.mid(counter.size() - 3); + } + auto mutedCounter = history->mute(); + auto unreadRight = st::dialogsPadding.x() + st::dialogsPhotoSize; + auto unreadTop = st::dialogsPadding.y() + st::dialogsPhotoSize - st::dialogsUnreadHeight; + auto unreadWidth = 0; + + UnreadBadgeStyle st; + st.active = active; + st.muted = history->mute(); + paintUnreadCount(p, counter, unreadRight, unreadTop, st, &unreadWidth); + } }); } @@ -308,6 +330,7 @@ void RowPainter::paint(Painter &p, const FakeRow *row, int fullWidth, bool activ paintRow(p, row, history, item, nullptr, item->date, fullWidth, active, selected, onlyBackground, ms, [&p, row, active, selected](int nameleft, int namewidth, HistoryItem *item) { int lastWidth = namewidth, texttop = st::dialogsPadding.y() + st::msgNameFont->height + st::dialogsSkip; item->drawInDialog(p, QRect(nameleft, texttop, lastWidth, st::dialogsTextFont->height), active, selected, row->_cacheFor, row->_cache); + }, [] { }); } diff --git a/Telegram/SourceFiles/dialogswidget.cpp b/Telegram/SourceFiles/dialogswidget.cpp index 337dc1ce6..ab9776adf 100644 --- a/Telegram/SourceFiles/dialogswidget.cpp +++ b/Telegram/SourceFiles/dialogswidget.cpp @@ -608,7 +608,8 @@ void DialogsInner::setSearchedPressed(int pressed) { void DialogsInner::resizeEvent(QResizeEvent *e) { _addContactLnk->move((width() - _addContactLnk->width()) / 2, (st::noContactsHeight + st::noContactsFont->height) / 2); - _cancelSearchInPeer->moveToRight(st::dialogsFilterSkip + st::dialogsFilterPadding.x() - otherWidth(), (st::dialogsRowHeight - st::dialogsCancelSearchInPeer.height) / 2); + auto widthForCancelButton = qMax(width() + otherWidth(), st::dialogsWidthMin); + _cancelSearchInPeer->moveToLeft(widthForCancelButton - st::dialogsFilterSkip - st::dialogsFilterPadding.x() - _cancelSearchInPeer->width(), (st::dialogsRowHeight - st::dialogsCancelSearchInPeer.height) / 2); } void DialogsInner::onDialogRowReplaced(Dialogs::Row *oldRow, Dialogs::Row *newRow) { @@ -1349,6 +1350,7 @@ void DialogsInner::refresh(bool toTop) { emit mustScrollTo(0, 0); loadPeerPhotos(0); } + Global::RefDialogsListDisplayForced().set(_searchInPeer || !_filter.isEmpty(), true); update(); } @@ -1396,6 +1398,7 @@ void DialogsInner::searchInPeer(PeerData *peer) { } else { _cancelSearchInPeer->hide(); } + Global::RefDialogsListDisplayForced().set(_searchInPeer || !_filter.isEmpty(), true); } void DialogsInner::clearFilter() { @@ -1889,6 +1892,52 @@ MsgId DialogsInner::lastSearchMigratedId() const { return _lastSearchMigratedId; } +class DialogsWidget::UpdateButton : public Ui::RippleButton { +public: + UpdateButton(QWidget *parent); + +protected: + void paintEvent(QPaintEvent *e) override; + + void onStateChanged(State was, StateChangeSource source) override; + +private: + QString _text; + const style::FlatButton &_st; + +}; + +DialogsWidget::UpdateButton::UpdateButton(QWidget *parent) : RippleButton(parent, st::dialogsUpdateButton.ripple) +, _text(lang(lng_update_telegram).toUpper()) +, _st(st::dialogsUpdateButton) { + resize(st::dialogsWidthMin, _st.height); +} + +void DialogsWidget::UpdateButton::onStateChanged(State was, StateChangeSource source) { + RippleButton::onStateChanged(was, source); + update(); +} + +void DialogsWidget::UpdateButton::paintEvent(QPaintEvent *e) { + QPainter p(this); + + QRect r(0, height() - _st.height, width(), _st.height); + p.fillRect(r, isOver() ? _st.overBgColor : _st.bgColor); + + paintRipple(p, 0, 0, getms()); + + p.setFont(isOver() ? _st.overFont : _st.font); + p.setRenderHint(QPainter::TextAntialiasing); + p.setPen(isOver() ? _st.overColor : _st.color); + + if (width() >= st::dialogsWidthMin) { + r.setTop(_st.textTop); + p.drawText(r, _text, style::al_top); + } else { + (isOver() ? st::dialogsInstallUpdateOver : st::dialogsInstallUpdate).paintInCenter(p, r); + } +} + DialogsWidget::DialogsWidget(QWidget *parent) : TWidget(parent) , _mainMenuToggle(this, st::dialogsMenuToggle) , _filter(this, st::dialogsFilter, lang(lng_dlg_filter)) @@ -1952,7 +2001,7 @@ DialogsWidget::DialogsWidget(QWidget *parent) : TWidget(parent) void DialogsWidget::onCheckUpdateStatus() { if (Sandbox::updatingState() == Application::UpdatingReady) { if (_updateTelegram) return; - _updateTelegram.create(this, lang(lng_update_telegram).toUpper(), st::dialogsUpdateButton); + _updateTelegram.create(this); _updateTelegram->show(); _updateTelegram->setClickedCallback([] { checkReadyUpdate(); @@ -1997,6 +2046,30 @@ void DialogsWidget::dialogsToUp() { } } +void DialogsWidget::startWidthAnimation() { + if (!_widthAnimationCache.isNull()) { + return; + } + auto scrollGeometry = _scroll->geometry(); + auto grabGeometry = QRect(scrollGeometry.x(), scrollGeometry.y(), st::dialogsWidthMin, scrollGeometry.height()); + _scroll->setGeometry(grabGeometry); + myEnsureResized(_scroll); + _widthAnimationCache = QPixmap(grabGeometry.size() * cIntRetinaFactor()); + _widthAnimationCache.setDevicePixelRatio(cRetinaFactor()); + _widthAnimationCache.fill(Qt::transparent); + _scroll->render(&_widthAnimationCache, QPoint(0, 0), QRect(QPoint(0, 0), grabGeometry.size()), QWidget::DrawChildren | QWidget::IgnoreMask); + _scroll->setGeometry(scrollGeometry); + _scroll->hide(); +} + +void DialogsWidget::stopWidthAnimation() { + _widthAnimationCache = QPixmap(); + if (!_a_show.animating()) { + _scroll->show(); + } + update(); +} + void DialogsWidget::showFast() { show(); updateForwardBar(); @@ -2621,12 +2694,12 @@ void DialogsWidget::updateControlsGeometry() { } auto filterLeft = st::dialogsFilterPadding.x() + _mainMenuToggle->width() + st::dialogsFilterPadding.x(); auto filterRight = (Global::LocalPasscode() ? (st::dialogsFilterPadding.x() + _lockUnlock->width()) : st::dialogsFilterSkip) + st::dialogsFilterPadding.x(); - auto filterWidth = width() - filterLeft - filterRight; + auto filterWidth = qMax(width(), st::dialogsWidthMin) - filterLeft - filterRight; auto filterAreaHeight = st::dialogsFilterPadding.y() + _mainMenuToggle->height() + st::dialogsFilterPadding.y(); auto filterTop = filterAreaTop + (filterAreaHeight - _filter->height()) / 2; _filter->setGeometryToLeft(filterLeft, filterTop, filterWidth, _filter->height()); _mainMenuToggle->moveToLeft(st::dialogsFilterPadding.x(), filterAreaTop + st::dialogsFilterPadding.y()); - _lockUnlock->moveToRight(st::dialogsFilterPadding.x(), filterAreaTop + st::dialogsFilterPadding.y()); + _lockUnlock->moveToLeft(filterLeft + filterWidth + st::dialogsFilterPadding.x(), filterAreaTop + st::dialogsFilterPadding.y()); _cancelSearch->moveToLeft(filterLeft + filterWidth - _cancelSearch->width(), _filter->y()); auto scrollTop = filterAreaTop + filterAreaHeight; @@ -2728,11 +2801,18 @@ void DialogsWidget::paintEvent(QPaintEvent *e) { p.drawTextLeft(st::dialogsForwardTextLeft, st::dialogsForwardTextTop, width(), lang(lng_forward_choose)); aboveTop += st::dialogsForwardHeight; } - auto above = QRect(0, aboveTop, width(), _scroll->y()); + auto above = QRect(0, aboveTop, width(), _scroll->y() - aboveTop); if (above.intersects(r)) { p.fillRect(above.intersected(r), st::dialogsBg); } - auto below = QRect(0, _scroll->y() + qMin(_scroll->height(), _inner->height()), width(), height()); + + auto belowTop = _scroll->y() + qMin(_scroll->height(), _inner->height()); + if (!_widthAnimationCache.isNull()) { + p.drawPixmapLeft(0, _scroll->y(), width(), _widthAnimationCache); + belowTop = _scroll->y() + (_widthAnimationCache.height() / cIntRetinaFactor()); + } + + auto below = QRect(0, belowTop, width(), height() - belowTop); if (below.intersects(r)) { p.fillRect(below.intersected(r), st::dialogsBg); } diff --git a/Telegram/SourceFiles/dialogswidget.h b/Telegram/SourceFiles/dialogswidget.h index e8645ac85..6b24aedd5 100644 --- a/Telegram/SourceFiles/dialogswidget.h +++ b/Telegram/SourceFiles/dialogswidget.h @@ -287,6 +287,9 @@ public: void dialogsToUp(); + void startWidthAnimation(); + void stopWidthAnimation(); + bool hasTopBarShadow() const { return true; } @@ -389,7 +392,8 @@ private: object_ptr _lockUnlock; object_ptr _scroll; QPointer _inner; - object_ptr _updateTelegram = { nullptr }; + class UpdateButton; + object_ptr _updateTelegram = { nullptr }; Animation _a_show; Window::SlideDirection _showDirection; @@ -421,4 +425,6 @@ private: using PeerSearchQueries = QMap; PeerSearchQueries _peerSearchQueries; + QPixmap _widthAnimationCache; + }; diff --git a/Telegram/SourceFiles/facades.cpp b/Telegram/SourceFiles/facades.cpp index c305e7c96..8cba2a5f3 100644 --- a/Telegram/SourceFiles/facades.cpp +++ b/Telegram/SourceFiles/facades.cpp @@ -620,7 +620,8 @@ struct Data { SingleDelayedCall HandleDelayedPeerUpdates = { App::app(), "call_handleDelayedPeerUpdates" }; SingleDelayedCall HandleObservables = { App::app(), "call_handleObservables" }; - Adaptive::Layout AdaptiveLayout = Adaptive::NormalLayout; + Adaptive::WindowLayout AdaptiveWindowLayout = Adaptive::WindowLayout::Normal; + Adaptive::ChatLayout AdaptiveChatLayout = Adaptive::ChatLayout::Normal; bool AdaptiveForWide = true; base::Observable AdaptiveChanged; @@ -708,6 +709,10 @@ struct Data { base::Observable UnreadCounterUpdate; base::Observable PeerChooseCancel; + float64 DialogsWidthRatio = 5. / 14; + base::Variable DialogsListFocused = { false }; + base::Variable DialogsListDisplayForced = { false }; + }; } // namespace internal @@ -739,7 +744,8 @@ DefineRefVar(Global, SingleDelayedCall, HandleFileDialogQueue); DefineRefVar(Global, SingleDelayedCall, HandleDelayedPeerUpdates); DefineRefVar(Global, SingleDelayedCall, HandleObservables); -DefineVar(Global, Adaptive::Layout, AdaptiveLayout); +DefineVar(Global, Adaptive::WindowLayout, AdaptiveWindowLayout); +DefineVar(Global, Adaptive::ChatLayout, AdaptiveChatLayout); DefineVar(Global, bool, AdaptiveForWide); DefineRefVar(Global, base::Observable, AdaptiveChanged); @@ -827,4 +833,8 @@ DefineRefVar(Global, base::Observable, ItemRemoved); DefineRefVar(Global, base::Observable, UnreadCounterUpdate); DefineRefVar(Global, base::Observable, PeerChooseCancel); +DefineVar(Global, float64, DialogsWidthRatio); +DefineRefVar(Global, base::Variable, DialogsListFocused); +DefineRefVar(Global, base::Variable, DialogsListDisplayForced); + } // namespace Global diff --git a/Telegram/SourceFiles/facades.h b/Telegram/SourceFiles/facades.h index faaa26628..51d3732db 100644 --- a/Telegram/SourceFiles/facades.h +++ b/Telegram/SourceFiles/facades.h @@ -241,11 +241,18 @@ DeclareVar(ProxyData, PreLaunchProxy); } // namespace Sandbox namespace Adaptive { -enum Layout { - OneColumnLayout, - NormalLayout, - WideLayout, + +enum class WindowLayout { + OneColumn, + SmallColumn, + Normal, }; + +enum class ChatLayout { + Normal, + Wide, +}; + } // namespace Adaptive namespace DebugLogging { @@ -306,7 +313,8 @@ DeclareRefVar(SingleDelayedCall, HandleFileDialogQueue); DeclareRefVar(SingleDelayedCall, HandleDelayedPeerUpdates); DeclareRefVar(SingleDelayedCall, HandleObservables); -DeclareVar(Adaptive::Layout, AdaptiveLayout); +DeclareVar(Adaptive::WindowLayout, AdaptiveWindowLayout); +DeclareVar(Adaptive::ChatLayout, AdaptiveChatLayout); DeclareVar(bool, AdaptiveForWide); DeclareRefVar(base::Observable, AdaptiveChanged); @@ -399,6 +407,10 @@ DeclareRefVar(base::Observable, ItemRemoved); DeclareRefVar(base::Observable, UnreadCounterUpdate); DeclareRefVar(base::Observable, PeerChooseCancel); +DeclareVar(float64, DialogsWidthRatio); +DeclareRefVar(base::Variable, DialogsListFocused); +DeclareRefVar(base::Variable, DialogsListDisplayForced); + } // namespace Global namespace Adaptive { @@ -408,13 +420,23 @@ inline base::Observable &Changed() { } inline bool OneColumn() { - return Global::AdaptiveLayout() == OneColumnLayout; + return Global::AdaptiveWindowLayout() == WindowLayout::OneColumn; } + +inline bool SmallColumn() { + return Global::AdaptiveWindowLayout() == WindowLayout::SmallColumn; +} + inline bool Normal() { - return Global::AdaptiveLayout() == NormalLayout; + return Global::AdaptiveWindowLayout() == WindowLayout::Normal; } -inline bool Wide() { - return Global::AdaptiveForWide() && (Global::AdaptiveLayout() == WideLayout); + +inline bool ChatNormal() { + return !Global::AdaptiveForWide() || (Global::AdaptiveChatLayout() == ChatLayout::Normal); +} + +inline bool ChatWide() { + return !ChatNormal(); } } // namespace Adaptive diff --git a/Telegram/SourceFiles/history.cpp b/Telegram/SourceFiles/history.cpp index 05d61a97b..5a0bd029b 100644 --- a/Telegram/SourceFiles/history.cpp +++ b/Telegram/SourceFiles/history.cpp @@ -92,7 +92,7 @@ void History::clearLastKeyboard() { } bool History::canHaveFromPhotos() const { - if (peer->isUser() && !Adaptive::Wide()) { + if (peer->isUser() && !Adaptive::ChatWide()) { return false; } else if (isChannel() && !peer->isMegagroup()) { return false; diff --git a/Telegram/SourceFiles/history/history.style b/Telegram/SourceFiles/history/history.style index 9fae52110..2434b636d 100644 --- a/Telegram/SourceFiles/history/history.style +++ b/Telegram/SourceFiles/history/history.style @@ -39,6 +39,8 @@ historyScroll: ScrollArea(defaultScrollArea) { bottomsh: -1px; } +historyResizeWidth: 6px; + historyPaddingBottom: 8px; historyToDownPosition: point(12px, 10px); diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp index 808d53508..67f515e47 100644 --- a/Telegram/SourceFiles/history/history_item.cpp +++ b/Telegram/SourceFiles/history/history_item.cpp @@ -517,7 +517,7 @@ void HistoryMessageUnreadBar::paint(Painter &p, int y, int w) const { int left = st::msgServiceMargin.left(); int maxwidth = w; - if (Adaptive::Wide()) { + if (Adaptive::ChatWide()) { maxwidth = qMin(maxwidth, int32(st::msgMaxWidth + 2 * st::msgPhotoSkip + 2 * st::msgMargin.left())); } w = maxwidth; diff --git a/Telegram/SourceFiles/history/history_message.cpp b/Telegram/SourceFiles/history/history_message.cpp index 2cfb7a2b6..6ef97a19d 100644 --- a/Telegram/SourceFiles/history/history_message.cpp +++ b/Telegram/SourceFiles/history/history_message.cpp @@ -814,7 +814,7 @@ void HistoryMessage::countPositionAndSize(int32 &left, int32 &width) const { maxwidth = qMax(_media->currentWidth(), qMin(maxwidth, plainMaxWidth())); } - left = (!isPost() && out() && !Adaptive::Wide()) ? st::msgMargin.right() : st::msgMargin.left(); + left = (!isPost() && out() && !Adaptive::ChatWide()) ? st::msgMargin.right() : st::msgMargin.left(); if (hasFromPhoto()) { left += st::msgPhotoSkip; // } else if (!Adaptive::Wide() && !out() && !fromChannel() && st::msgPhotoSkip - (hmaxwidth - hwidth) > 0) { @@ -823,7 +823,7 @@ void HistoryMessage::countPositionAndSize(int32 &left, int32 &width) const { width = hwidth - st::msgMargin.left() - st::msgMargin.right(); if (width > maxwidth) { - if (!isPost() && out() && !Adaptive::Wide()) { + if (!isPost() && out() && !Adaptive::ChatWide()) { left += width - maxwidth; } width = maxwidth; @@ -1291,7 +1291,7 @@ void HistoryMessage::draw(Painter &p, const QRect &r, TextSelection selection, T auto r = QRect(left, top, width, height - top - marginBottom()); auto skipTail = isAttachedToNext() || (_media && _media->skipBubbleTail()); - auto displayTail = skipTail ? HistoryLayout::BubbleTail::None : (outbg && !Adaptive::Wide()) ? HistoryLayout::BubbleTail::Right : HistoryLayout::BubbleTail::Left; + auto displayTail = skipTail ? HistoryLayout::BubbleTail::None : (outbg && !Adaptive::ChatWide()) ? HistoryLayout::BubbleTail::Right : HistoryLayout::BubbleTail::Left; HistoryLayout::paintBubble(p, r, _history->width, selected, outbg, displayTail); QRect trect(r.marginsAdded(-st::msgPadding)); @@ -1739,7 +1739,7 @@ bool HistoryMessage::displayFromPhoto() const { } bool HistoryMessage::hasFromPhoto() const { - return (Adaptive::Wide() || (!out() && !history()->peer->isUser())) && !isPost() && !isEmpty(); + return (Adaptive::ChatWide() || (!out() && !history()->peer->isUser())) && !isPost() && !isEmpty(); } HistoryMessage::~HistoryMessage() { @@ -2089,7 +2089,7 @@ bool HistoryService::updateDependencyItem() { void HistoryService::countPositionAndSize(int32 &left, int32 &width) const { left = st::msgServiceMargin.left(); int32 maxwidth = _history->width; - if (Adaptive::Wide()) { + if (Adaptive::ChatWide()) { maxwidth = qMin(maxwidth, int32(st::msgMaxWidth + 2 * st::msgPhotoSkip + 2 * st::msgMargin.left())); } width = maxwidth - st::msgServiceMargin.left() - st::msgServiceMargin.left(); @@ -2161,7 +2161,7 @@ int32 HistoryService::resizeGetHeight_(int32 width) { _textHeight = 0; } else { int32 maxwidth = _history->width; - if (Adaptive::Wide()) { + if (Adaptive::ChatWide()) { maxwidth = qMin(maxwidth, int32(st::msgMaxWidth + 2 * st::msgPhotoSkip + 2 * st::msgMargin.left())); } if (width > maxwidth) width = maxwidth; diff --git a/Telegram/SourceFiles/history/history_service_layout.cpp b/Telegram/SourceFiles/history/history_service_layout.cpp index 1819a5088..7ac63d961 100644 --- a/Telegram/SourceFiles/history/history_service_layout.cpp +++ b/Telegram/SourceFiles/history/history_service_layout.cpp @@ -167,7 +167,7 @@ void paintBubblePart(Painter &p, int x, int y, int width, int height, SideStyle void paintPreparedDate(Painter &p, const QString &dateText, int dateTextWidth, int y, int w) { int left = st::msgServiceMargin.left(); int maxwidth = w; - if (Adaptive::Wide()) { + if (Adaptive::ChatWide()) { maxwidth = qMin(maxwidth, int32(st::msgMaxWidth + 2 * st::msgPhotoSkip + 2 * st::msgMargin.left())); } w = maxwidth - st::msgServiceMargin.left() - st::msgServiceMargin.left(); diff --git a/Telegram/SourceFiles/historywidget.cpp b/Telegram/SourceFiles/historywidget.cpp index 27e5b060b..18f872e1c 100644 --- a/Telegram/SourceFiles/historywidget.cpp +++ b/Telegram/SourceFiles/historywidget.cpp @@ -1630,7 +1630,7 @@ void HistoryInner::recountHeight() { int32 descH = st::msgMargin.top() + st::msgPadding.top() + st::msgNameFont->height + st::botDescSkip + _botAbout->height + st::msgPadding.bottom() + st::msgMargin.bottom(); int32 descMaxWidth = _scroll->width(); - if (Adaptive::Wide()) { + if (Adaptive::ChatWide()) { descMaxWidth = qMin(descMaxWidth, int32(st::msgMaxWidth + 2 * st::msgPhotoSkip + 2 * st::msgMargin.left())); } int32 descAtX = (descMaxWidth - _botAbout->width) / 2 - st::msgPadding.left(); @@ -1796,7 +1796,7 @@ void HistoryInner::updateSize() { if (_botAbout && _botAbout->height > 0) { int32 descH = st::msgMargin.top() + st::msgPadding.top() + st::msgNameFont->height + st::botDescSkip + _botAbout->height + st::msgPadding.bottom() + st::msgMargin.bottom(); int32 descMaxWidth = _scroll->width(); - if (Adaptive::Wide()) { + if (Adaptive::ChatWide()) { descMaxWidth = qMin(descMaxWidth, int32(st::msgMaxWidth + 2 * st::msgPhotoSkip + 2 * st::msgMargin.left())); } int32 descAtX = (descMaxWidth - _botAbout->width) / 2 - st::msgPadding.left(); @@ -7071,6 +7071,11 @@ void HistoryWidget::notify_handlePendingHistoryUpdate() { } void HistoryWidget::resizeEvent(QResizeEvent *e) { + auto layout = (width() < st::adaptiveChatWideWidth) ? Adaptive::ChatLayout::Normal : Adaptive::ChatLayout::Wide; + if (layout != Global::AdaptiveChatLayout()) { + Global::SetAdaptiveChatLayout(layout); + Adaptive::Changed().notify(true); + } updateControlsGeometry(); } diff --git a/Telegram/SourceFiles/intro/introwidget.cpp b/Telegram/SourceFiles/intro/introwidget.cpp index 6dac36ec6..5edfa0e07 100644 --- a/Telegram/SourceFiles/intro/introwidget.cpp +++ b/Telegram/SourceFiles/intro/introwidget.cpp @@ -119,6 +119,7 @@ void Widget::changeLanguage(int32 languageId) { } void Widget::setInnerFocus() { + Global::RefDialogsListFocused().set(false, true); if (getStep()->animating()) { setFocus(); } else { diff --git a/Telegram/SourceFiles/localstorage.cpp b/Telegram/SourceFiles/localstorage.cpp index 0d325cba0..62ee61140 100644 --- a/Telegram/SourceFiles/localstorage.cpp +++ b/Telegram/SourceFiles/localstorage.cpp @@ -558,6 +558,7 @@ enum { dbiNotificationsCount = 0x45, dbiNotificationsCorner = 0x46, dbiTheme = 0x47, + dbiDialogsWidthRatio = 0x48, dbiEncryptedWithSalt = 333, dbiEncrypted = 444, @@ -1032,6 +1033,14 @@ bool _readSetting(quint32 blockId, QDataStream &stream, int version) { Global::SetNotificationsCorner(static_cast((v >= 0 && v < 4) ? v : 2)); } break; + case dbiDialogsWidthRatio: { + qint32 v; + stream >> v; + if (!_checkStreamStatus(stream)) return false; + + Global::SetDialogsWidthRatio(v / 1000000.); + } break; + case dbiWorkMode: { qint32 v; stream >> v; @@ -1608,7 +1617,7 @@ void _writeUserSettings() { _writeMap(WriteMapFast); } - uint32 size = 20 * (sizeof(quint32) + sizeof(qint32)); + uint32 size = 21 * (sizeof(quint32) + sizeof(qint32)); size += sizeof(quint32) + Serialize::stringSize(Global::AskDownloadPath() ? QString() : Global::DownloadPath()) + Serialize::bytearraySize(Global::AskDownloadPath() ? QByteArray() : Global::DownloadPathBookmark()); size += sizeof(quint32) + sizeof(qint32) + (cRecentEmojisPreload().isEmpty() ? cGetRecentEmojis().size() : cRecentEmojisPreload().size()) * (sizeof(uint64) + sizeof(ushort)); size += sizeof(quint32) + sizeof(qint32) + cEmojiVariants().size() * (sizeof(uint32) + sizeof(uint64)); @@ -1644,6 +1653,7 @@ void _writeUserSettings() { data.stream << quint32(dbiDialogsMode) << qint32(Global::DialogsModeEnabled() ? 1 : 0) << static_cast(Global::DialogsMode()); data.stream << quint32(dbiModerateMode) << qint32(Global::ModerateModeEnabled() ? 1 : 0); data.stream << quint32(dbiAutoPlay) << qint32(cAutoPlayGif() ? 1 : 0); + data.stream << quint32(dbiDialogsWidthRatio) << qint32(snap(qRound(Global::DialogsWidthRatio() * 1000000), 0, 1000000)); { RecentEmojisPreload v(cRecentEmojisPreload()); diff --git a/Telegram/SourceFiles/mainwidget.cpp b/Telegram/SourceFiles/mainwidget.cpp index 69b61513a..eb7d2b7e7 100644 --- a/Telegram/SourceFiles/mainwidget.cpp +++ b/Telegram/SourceFiles/mainwidget.cpp @@ -71,6 +71,7 @@ StackItemSection::~StackItemSection() { MainWidget::MainWidget(QWidget *parent) : TWidget(parent) , _dialogsWidth(st::dialogsWidthMin) , _sideShadow(this, st::shadowFg) +, _sideResizeArea(this) , _dialogs(this) , _history(this) , _topBar(this) @@ -107,12 +108,23 @@ MainWidget::MainWidget(QWidget *parent) : TWidget(parent) }); } + subscribe(Global::RefDialogsListFocused(), [this](bool) { + updateDialogsWidthAnimated(); + }); + subscribe(Global::RefDialogsListDisplayForced(), [this](bool) { + updateDialogsWidthAnimated(); + }); + + Sandbox::installEventFilter(this); + connect(&_updateMutedTimer, SIGNAL(timeout()), this, SLOT(onUpdateMuted())); connect(&_viewsIncrementTimer, SIGNAL(timeout()), this, SLOT(onViewsIncrement())); _webPageOrGameUpdater.setSingleShot(true); connect(&_webPageOrGameUpdater, SIGNAL(timeout()), this, SLOT(webPagesOrGamesUpdate())); + _sideResizeArea->setCursor(style::cur_sizehor); + using Update = Window::Theme::BackgroundUpdate; subscribe(Window::Theme::Background(), [this](const Update &update) { if (update.type == Update::Type::New || update.type == Update::Type::Changed) { @@ -146,7 +158,7 @@ MainWidget::MainWidget(QWidget *parent) : TWidget(parent) }); } - subscribe(Adaptive::Changed(), [this]() { updateAdaptiveLayout(); }); + subscribe(Adaptive::Changed(), [this]() { handleAdaptiveLayoutUpdate(); }); auto observeEvents = Notify::PeerUpdate::Flag::SharedMediaChanged; subscribe(Notify::PeerUpdated(), Notify::PeerUpdatedHandler(observeEvents, [this](const Notify::PeerUpdate &update) { @@ -168,9 +180,7 @@ MainWidget::MainWidget(QWidget *parent) : TWidget(parent) _mediaType->hide(); _mediaType->setOrigin(Ui::PanelAnimation::Origin::TopRight); _topBar->mediaTypeButton()->installEventFilter(_mediaType); - - show(); - setFocus(); + _sideResizeArea->installEventFilter(this); _api->init(); @@ -601,14 +611,14 @@ void MainWidget::noHider(HistoryHider *destroyed) { } onHistoryShown(_history->history(), _history->msgId()); if (_wideSection || _overview || (_history->peer() && _history->peer()->id)) { - Window::SectionSlideParams animationParams; - if (_overview) { - animationParams = prepareOverviewAnimation(); - } else if (_wideSection) { - animationParams = prepareWideSectionAnimation(_wideSection); - } else { - animationParams = prepareHistoryAnimation(_history->peer() ? _history->peer()->id : 0); - } + auto animationParams = ([this] { + if (_overview) { + return prepareOverviewAnimation(); + } else if (_wideSection) { + return prepareWideSectionAnimation(_wideSection); + } + return prepareHistoryAnimation(_history->peer() ? _history->peer()->id : 0); + })(); _dialogs->hide(); if (_overview) { _overview->showAnimated(Window::SlideDirection::FromRight, animationParams); @@ -2176,7 +2186,7 @@ void MainWidget::ctrlEnterSubmitUpdated() { } void MainWidget::ui_showPeerHistory(quint64 peerId, qint32 showAtMsgId, Ui::ShowWay way) { - if (PeerData *peer = App::peerLoaded(peerId)) { + if (auto peer = App::peerLoaded(peerId)) { if (peer->migrateTo()) { peer = peer->migrateTo(); peerId = peer->id; @@ -2190,6 +2200,9 @@ void MainWidget::ui_showPeerHistory(quint64 peerId, qint32 showAtMsgId, Ui::Show } } + Global::RefDialogsListFocused().set(false, true); + _a_dialogsWidth.finish(); + bool back = (way == Ui::ShowWay::Backward || !peerId); bool foundInStack = !peerId; if (foundInStack || (way == Ui::ShowWay::ClearStack)) { @@ -2228,7 +2241,7 @@ void MainWidget::ui_showPeerHistory(quint64 peerId, qint32 showAtMsgId, Ui::Show } dlgUpdated(); - PeerData *wasActivePeer = activePeer(); + auto wasActivePeer = activePeer(); Ui::hideSettingsAndLayer(); if (_hider) { @@ -2236,16 +2249,34 @@ void MainWidget::ui_showPeerHistory(quint64 peerId, qint32 showAtMsgId, Ui::Show _hider = nullptr; } - Window::SectionSlideParams animationParams; - if (!_a_show.animating() && !App::passcoded() && ((_history->isHidden() && (_wideSection || _overview)) || (Adaptive::OneColumn() && (_history->isHidden() || !peerId)) || back || (way == Ui::ShowWay::Forward))) { - animationParams = prepareHistoryAnimation(peerId); - } + auto animatedShow = [this, peerId, back, way] { + if (_a_show.animating() || App::passcoded()) { + return false; + } + if (!peerId) { + if (Adaptive::OneColumn()) { + return true; + } else { + return false; + } + } + if (back || way == Ui::ShowWay::Forward) { + return true; + } + if (_history->isHidden() && (_wideSection || _overview || Adaptive::OneColumn())) { + return true; + } + return false; + }; + auto animationParams = animatedShow() ? prepareHistoryAnimation(peerId) : Window::SectionSlideParams(); + if (_history->peer() && _history->peer()->id != peerId && way != Ui::ShowWay::Forward) { clearBotStartToken(_history->peer()); } _history->showHistory(peerId, showAtMsgId); - bool noPeer = (!_history->peer() || !_history->peer()->id), onlyDialogs = noPeer && Adaptive::OneColumn(); + auto noPeer = !_history->peer(); + auto onlyDialogs = noPeer && Adaptive::OneColumn(); if (_wideSection || _overview) { if (_wideSection) { _wideSection->hide(); @@ -2264,8 +2295,9 @@ void MainWidget::ui_showPeerHistory(quint64 peerId, qint32 showAtMsgId, Ui::Show _topBar->hide(); _history->hide(); if (!_a_show.animating()) { - if (!animationParams.oldContentCache.isNull()) { - _dialogs->showAnimated(back ? Window::SlideDirection::FromLeft : Window::SlideDirection::FromRight, animationParams); + if (animationParams) { + auto direction = back ? Window::SlideDirection::FromLeft : Window::SlideDirection::FromRight; + _dialogs->showAnimated(direction, animationParams); } else { _dialogs->showFast(); } @@ -2385,10 +2417,19 @@ void MainWidget::showMediaOverview(PeerData *peer, MediaOverviewType type, bool return; } - Window::SectionSlideParams animationParams; - if (!_a_show.animating() && (Adaptive::OneColumn() || _wideSection || _overview || _history->peer())) { - animationParams = prepareOverviewAnimation(); - } + Global::RefDialogsListFocused().set(false, true); + _a_dialogsWidth.finish(); + + auto animatedShow = [this] { + if (_a_show.animating() || App::passcoded()) { + return false; + } + if (Adaptive::OneColumn() || isSectionShown()) { + return true; + } + return false; + }; + auto animationParams = animatedShow() ? prepareOverviewAnimation() : Window::SectionSlideParams(); if (!back) { saveSectionInStack(); } @@ -2436,7 +2477,7 @@ void MainWidget::showWideSection(const Window::SectionMemento &memento) { if (_wideSection && _wideSection->showInternal(&memento)) { return; } - showWideSectionAnimated(&memento, false, true); + showNewWideSection(&memento, false, true); } Window::SectionSlideParams MainWidget::prepareShowAnimation(bool willHaveTopBarShadow) { @@ -2523,12 +2564,24 @@ Window::SectionSlideParams MainWidget::prepareDialogsAnimation() { return prepareShowAnimation(false); } -void MainWidget::showWideSectionAnimated(const Window::SectionMemento *memento, bool back, bool saveInStack) { +void MainWidget::showNewWideSection(const Window::SectionMemento *memento, bool back, bool saveInStack) { QPixmap animCache; + Global::RefDialogsListFocused().set(false, true); + _a_dialogsWidth.finish(); + auto newWideGeometry = QRect(_history->x(), _playerHeight, _history->width(), height() - _playerHeight); auto newWideSection = memento->createWidget(this, newWideGeometry); - Window::SectionSlideParams animationParams = prepareWideSectionAnimation(newWideSection); + auto animatedShow = [this] { + if (_a_show.animating() || App::passcoded()) { + return false; + } + if (Adaptive::OneColumn() || isSectionShown()) { + return true; + } + return false; + }; + auto animationParams = animatedShow() ? prepareWideSectionAnimation(newWideSection) : Window::SectionSlideParams(); if (saveInStack) { saveSectionInStack(); @@ -2553,12 +2606,20 @@ void MainWidget::showWideSectionAnimated(const Window::SectionMemento *memento, _history->hide(); if (Adaptive::OneColumn()) _dialogs->hide(); - auto direction = back ? Window::SlideDirection::FromLeft : Window::SlideDirection::FromRight; - _wideSection->showAnimated(direction, animationParams); + if (animationParams) { + auto direction = back ? Window::SlideDirection::FromLeft : Window::SlideDirection::FromRight; + _wideSection->showAnimated(direction, animationParams); + } else { + _wideSection->showFast(); + } orderWidgets(); } +bool MainWidget::isSectionShown() const { + return _wideSection || _overview || _history->peer(); +} + bool MainWidget::stackIsEmpty() const { return _stack.isEmpty(); } @@ -2579,23 +2640,24 @@ void MainWidget::showBackFromStack() { dlgUpdated(); _peerInStack = nullptr; _msgIdInStack = 0; - for (int32 i = _stack.size(); i > 0;) { - if (_stack.at(--i)->type() == HistoryStackItem) { - _peerInStack = static_cast(_stack.at(i).get())->peer; - _msgIdInStack = static_cast(_stack.at(i).get())->msgId; + for (auto i = _stack.size(); i > 0;) { + if (_stack[--i]->type() == HistoryStackItem) { + auto historyItem = static_cast(_stack[i].get()); + _peerInStack = historyItem->peer; + _msgIdInStack = historyItem->msgId; dlgUpdated(); break; } } - StackItemHistory *histItem = static_cast(item.get()); - Ui::showPeerHistory(histItem->peer->id, ShowAtUnreadMsgId, Ui::ShowWay::Backward); - _history->setReplyReturns(histItem->peer->id, histItem->replyReturns); + auto historyItem = static_cast(item.get()); + Ui::showPeerHistory(historyItem->peer->id, ShowAtUnreadMsgId, Ui::ShowWay::Backward); + _history->setReplyReturns(historyItem->peer->id, historyItem->replyReturns); } else if (item->type() == SectionStackItem) { - StackItemSection *sectionItem = static_cast(item.get()); - showWideSectionAnimated(sectionItem->memento(), true, false); + auto sectionItem = static_cast(item.get()); + showNewWideSection(sectionItem->memento(), true, false); } else if (item->type() == OverviewStackItem) { - StackItemOverview *overItem = static_cast(item.get()); - showMediaOverview(overItem->peer, overItem->mediaType, true, overItem->lastScrollTop); + auto overviewItem = static_cast(item.get()); + showMediaOverview(overviewItem->peer, overviewItem->mediaType, true, overviewItem->lastScrollTop); } } @@ -2610,6 +2672,7 @@ void MainWidget::orderWidgets() { } _mediaType->raise(); _sideShadow->raise(); + _sideResizeArea->raise(); _playerPlaylist->raise(); _playerPanel->raise(); if (_hider) _hider->raise(); @@ -2837,7 +2900,7 @@ void MainWidget::showAll() { if (_wideSection) { _topBar->hide(); _dialogs->hide(); - } else if (_overview || _history->peer()) { + } else if (isSectionShown()) { _topBar->show(); _dialogs->hide(); } @@ -2865,7 +2928,7 @@ void MainWidget::showAll() { } if (_wideSection) { _topBar->hide(); - } else if (_overview || _history->peer()) { + } else if (isSectionShown()) { _topBar->show(); } } @@ -2878,46 +2941,56 @@ void MainWidget::showAll() { App::wnd()->checkHistoryActivation(); } -namespace { - -inline int chatsListWidth(int windowWidth) { - return snap((windowWidth * 5) / 14, st::dialogsWidthMin, st::dialogsWidthMax); -} - -} // namespace - void MainWidget::resizeEvent(QResizeEvent *e) { updateControlsGeometry(); } void MainWidget::updateControlsGeometry() { - auto tbh = _topBar->isHidden() ? 0 : st::topBarHeight; + updateWindowAdaptiveLayout(); + auto topBarHeight = _topBar->isHidden() ? 0 : st::topBarHeight; + if (!Adaptive::SmallColumn()) { + _a_dialogsWidth.finish(); + } + if (!_a_dialogsWidth.animating()) { + _dialogs->stopWidthAnimation(); + } + auto dialogsWidth = qRound(_a_dialogsWidth.current(_dialogsWidth)); if (Adaptive::OneColumn()) { - _dialogsWidth = width(); if (_player) { - _player->resizeToWidth(_dialogsWidth); + _player->resizeToWidth(dialogsWidth); _player->moveToLeft(0, 0); } - _dialogs->setGeometry(0, _playerHeight, _dialogsWidth, height() - _playerHeight); - _topBar->setGeometry(0, _playerHeight, _dialogsWidth, st::topBarHeight); - _history->setGeometry(0, _playerHeight + tbh, _dialogsWidth, height() - _playerHeight - tbh); - if (_hider) _hider->setGeometry(0, 0, _dialogsWidth, height()); + _dialogs->setGeometry(0, _playerHeight, dialogsWidth, height() - _playerHeight); + _topBar->setGeometry(0, _playerHeight, dialogsWidth, st::topBarHeight); + _history->setGeometry(0, _playerHeight + topBarHeight, dialogsWidth, height() - _playerHeight - topBarHeight); + if (_hider) _hider->setGeometry(0, 0, dialogsWidth, height()); } else { - _dialogsWidth = chatsListWidth(width()); - auto sectionWidth = width() - _dialogsWidth; + accumulate_min(dialogsWidth, width() - st::windowMinWidth); + auto sectionWidth = width() - dialogsWidth; - _dialogs->setGeometryToLeft(0, 0, _dialogsWidth, height()); - _sideShadow->setGeometryToLeft(_dialogsWidth, 0, st::lineWidth, height()); + _dialogs->setGeometryToLeft(0, 0, dialogsWidth, height()); + _sideShadow->setGeometryToLeft(dialogsWidth, 0, st::lineWidth, height()); if (_player) { _player->resizeToWidth(sectionWidth); - _player->moveToLeft(_dialogsWidth, 0); + _player->moveToLeft(dialogsWidth, 0); } - _topBar->setGeometryToLeft(_dialogsWidth, _playerHeight, sectionWidth, st::topBarHeight); - _history->setGeometryToLeft(_dialogsWidth, _playerHeight + tbh, sectionWidth, height() - _playerHeight - tbh); + _topBar->setGeometryToLeft(dialogsWidth, _playerHeight, sectionWidth, st::topBarHeight); + _history->setGeometryToLeft(dialogsWidth, _playerHeight + topBarHeight, sectionWidth, height() - _playerHeight - topBarHeight); if (_hider) { - _hider->setGeometryToLeft(_dialogsWidth, 0, sectionWidth, height()); + _hider->setGeometryToLeft(dialogsWidth, 0, sectionWidth, height()); } } + _sideResizeArea->setGeometryToLeft(_history->x(), 0, st::historyResizeWidth, height()); + auto isSideResizeAreaVisible = [this] { + if (width() < st::windowMinWidth + st::dialogsWidthMin) { + return false; + } + if (Adaptive::OneColumn() && !isSectionShown()) { + return false; + } + return true; + }; + _sideResizeArea->setVisible(isSideResizeAreaVisible()); _mediaType->moveToLeft(width() - _mediaType->width(), _playerHeight + st::topBarHeight); if (_wideSection) { QRect wideSectionGeometry(_history->x(), _playerHeight, _history->width(), height() - _playerHeight); @@ -2929,6 +3002,19 @@ void MainWidget::updateControlsGeometry() { _contentScrollAddToY = 0; } +void MainWidget::updateDialogsWidthAnimated() { + if (!Adaptive::SmallColumn()) { + return; + } + auto dialogsWidth = _dialogsWidth; + updateWindowAdaptiveLayout(); + if (Adaptive::SmallColumn() && (_dialogsWidth != dialogsWidth || _a_dialogsWidth.animating())) { + _dialogs->startWidthAnimation(); + _a_dialogsWidth.start([this] { updateControlsGeometry(); }, dialogsWidth, _dialogsWidth, st::dialogsWidthDuration, anim::easeOutCirc); + updateControlsGeometry(); + } +} + void MainWidget::updateMediaPlayerPosition() { _playerPanel->moveToRight(0, 0); if (_player && _playerVolume) { @@ -2960,7 +3046,40 @@ int MainWidget::contentScrollAddToY() const { void MainWidget::keyPressEvent(QKeyEvent *e) { } -void MainWidget::updateAdaptiveLayout() { +bool MainWidget::eventFilter(QObject *o, QEvent *e) { + if (o == _sideResizeArea) { + auto mouseLeft = [this, e] { + return mapFromGlobal(static_cast(e)->globalPos()).x(); + }; + if (e->type() == QEvent::MouseButtonPress && static_cast(e)->button() == Qt::LeftButton) { + _resizingSide = true; + _resizingSideShift = mouseLeft() - (Adaptive::OneColumn() ? 0 : _dialogsWidth); + } else if (e->type() == QEvent::MouseButtonRelease) { + _resizingSide = false; + if (!Adaptive::OneColumn()) { + Global::SetDialogsWidthRatio(float64(_dialogsWidth) / width()); + } + Local::writeUserSettings(); + } else if (e->type() == QEvent::MouseMove && _resizingSide) { + auto newWidth = mouseLeft() - _resizingSideShift; + Global::SetDialogsWidthRatio(float64(newWidth) / width()); + updateControlsGeometry(); + } + } else if (e->type() == QEvent::FocusIn) { + if (auto widget = qobject_cast(o)) { + if (_history == widget || _history->isAncestorOf(widget) + || (_overview && (_overview == widget || _overview->isAncestorOf(widget))) + || (_wideSection && (_wideSection == widget || _wideSection->isAncestorOf(widget)))) { + Global::RefDialogsListFocused().set(false, false); + } else if (_dialogs == widget || _dialogs->isAncestorOf(widget)) { + Global::RefDialogsListFocused().set(true, false); + } + } + } + return TWidget::eventFilter(o, e); +} + +void MainWidget::handleAdaptiveLayoutUpdate() { showAll(); _sideShadow->setVisible(!Adaptive::OneColumn()); if (_player) { @@ -2968,8 +3087,61 @@ void MainWidget::updateAdaptiveLayout() { } } +void MainWidget::updateWindowAdaptiveLayout() { + auto layout = Adaptive::WindowLayout::OneColumn; + + auto dialogsWidth = qRound(width() * Global::DialogsWidthRatio()); + auto historyWidth = width() - dialogsWidth; + accumulate_max(historyWidth, st::windowMinWidth); + dialogsWidth = width() - historyWidth; + + auto useOneColumnLayout = [this, dialogsWidth] { + auto someSectionShown = !selectingPeer() && isSectionShown(); + if (dialogsWidth < st::dialogsPadding.x() && (Adaptive::OneColumn() || someSectionShown)) { + return true; + } + if (width() < st::windowMinWidth + st::dialogsWidthMin) { + return true; + } + return false; + }; + auto useSmallColumnLayout = [this, dialogsWidth] { + // used if useOneColumnLayout() == false. + if (dialogsWidth < st::dialogsWidthMin / 2) { + return true; + } + return false; + }; + if (useOneColumnLayout()) { + dialogsWidth = width(); + } else if (useSmallColumnLayout()) { + layout = Adaptive::WindowLayout::SmallColumn; + auto forceWideDialogs = [this] { + if (Global::DialogsListDisplayForced().value()) { + return true; + } else if (Global::DialogsListFocused().value()) { + return true; + } + return !isSectionShown(); + }; + if (forceWideDialogs()) { + dialogsWidth = st::dialogsWidthMin; + } else { + dialogsWidth = st::dialogsPadding.x() + st::dialogsPhotoSize + st::dialogsPadding.x(); + } + } else { + layout = Adaptive::WindowLayout::Normal; + accumulate_max(dialogsWidth, st::dialogsWidthMin); + } + _dialogsWidth = dialogsWidth; + if (layout != Global::AdaptiveWindowLayout()) { + Global::SetAdaptiveWindowLayout(layout); + Adaptive::Changed().notify(true); + } +} + bool MainWidget::needBackButton() { - return _overview || _wideSection || _history->peer(); + return isSectionShown(); } bool MainWidget::paintTopBar(Painter &p, int decreaseWidth, TimeMs ms) { diff --git a/Telegram/SourceFiles/mainwidget.h b/Telegram/SourceFiles/mainwidget.h index 6ede51c81..f175b95c5 100644 --- a/Telegram/SourceFiles/mainwidget.h +++ b/Telegram/SourceFiles/mainwidget.h @@ -477,14 +477,17 @@ protected: void paintEvent(QPaintEvent *e) override; void resizeEvent(QResizeEvent *e) override; void keyPressEvent(QKeyEvent *e) override; + bool eventFilter(QObject *o, QEvent *e) override; private: void animationCallback(); - void updateAdaptiveLayout(); + void handleAdaptiveLayoutUpdate(); + void updateWindowAdaptiveLayout(); void handleAudioUpdate(const AudioMsgId &audioId); void updateMediaPlayerPosition(); void updateMediaPlaylistPosition(int x); void updateControlsGeometry(); + void updateDialogsWidthAnimated(); void updateForwardingTexts(); void updateForwardingItemRemovedSubscription(); @@ -506,7 +509,8 @@ private: void mediaOverviewUpdated(const Notify::PeerUpdate &update); Window::SectionSlideParams prepareShowAnimation(bool willHaveTopBarShadow); - void showWideSectionAnimated(const Window::SectionMemento *memento, bool back, bool saveInStack); + void showNewWideSection(const Window::SectionMemento *memento, bool back, bool saveInStack); + bool isSectionShown() const; // All this methods use the prepareShowAnimation(). Window::SectionSlideParams prepareWideSectionAnimation(Window::SectionWidget *section); @@ -586,8 +590,10 @@ private: QPixmap _cacheUnder, _cacheOver; int _dialogsWidth; + Animation _a_dialogsWidth; object_ptr _sideShadow; + object_ptr _sideResizeArea; object_ptr _dialogs; object_ptr _history; object_ptr _wideSection = { nullptr }; @@ -691,4 +697,7 @@ private: std_::unique_ptr _api; + bool _resizingSide = false; + int _resizingSideShift = 0; + }; diff --git a/Telegram/SourceFiles/mainwindow.cpp b/Telegram/SourceFiles/mainwindow.cpp index 3c42d47ba..deb38beed 100644 --- a/Telegram/SourceFiles/mainwindow.cpp +++ b/Telegram/SourceFiles/mainwindow.cpp @@ -359,6 +359,7 @@ void MainWindow::setupMain(const MTPUser *self) { clearWidgets(); _main.create(bodyWidget()); + _main->show(); updateControlsGeometry(); if (animated) { @@ -835,18 +836,6 @@ void MainWindow::closeEvent(QCloseEvent *e) { void MainWindow::resizeEvent(QResizeEvent *e) { Platform::MainWindow::resizeEvent(e); - - Adaptive::Layout layout = Adaptive::OneColumnLayout; - if (width() > st::adaptiveWideWidth) { - layout = Adaptive::WideLayout; - } else if (width() >= st::adaptiveNormalWidth) { - layout = Adaptive::NormalLayout; - } - if (layout != Global::AdaptiveLayout()) { - Global::SetAdaptiveLayout(layout); - Adaptive::Changed().notify(true); - } - updateControlsGeometry(); } diff --git a/Telegram/SourceFiles/passcodewidget.cpp b/Telegram/SourceFiles/passcodewidget.cpp index 0f1eea6de..bf4001f97 100644 --- a/Telegram/SourceFiles/passcodewidget.cpp +++ b/Telegram/SourceFiles/passcodewidget.cpp @@ -187,5 +187,6 @@ void PasscodeWidget::resizeEvent(QResizeEvent *e) { } void PasscodeWidget::setInnerFocus() { + Global::RefDialogsListFocused().set(false, true); _passcode->setFocusFast(); } diff --git a/Telegram/SourceFiles/profile/profile_common_groups_section.cpp b/Telegram/SourceFiles/profile/profile_common_groups_section.cpp index 7b825e0ea..a327bfb00 100644 --- a/Telegram/SourceFiles/profile/profile_common_groups_section.cpp +++ b/Telegram/SourceFiles/profile/profile_common_groups_section.cpp @@ -369,7 +369,7 @@ QPixmap Widget::grabForShowAnimation(const Window::SectionSlideParams ¶ms) { return result; } -void Widget::setInnerFocus() { +void Widget::doSetInnerFocus() { _inner->setFocus(); } diff --git a/Telegram/SourceFiles/profile/profile_common_groups_section.h b/Telegram/SourceFiles/profile/profile_common_groups_section.h index eef5e449e..4ac88c17b 100644 --- a/Telegram/SourceFiles/profile/profile_common_groups_section.h +++ b/Telegram/SourceFiles/profile/profile_common_groups_section.h @@ -184,8 +184,6 @@ public: QPixmap grabForShowAnimation(const Window::SectionSlideParams ¶ms) override; - void setInnerFocus() override; - bool showInternal(const Window::SectionMemento *memento) override; std_::unique_ptr createMemento() const override; @@ -196,8 +194,9 @@ protected: void showAnimatedHook() override; void showFinishedHook() override; + void doSetInnerFocus() override; - private slots: +private slots: void onScroll(); private: diff --git a/Telegram/SourceFiles/profile/profile_widget.cpp b/Telegram/SourceFiles/profile/profile_widget.cpp index f7f01e464..58533e8b4 100644 --- a/Telegram/SourceFiles/profile/profile_widget.cpp +++ b/Telegram/SourceFiles/profile/profile_widget.cpp @@ -73,7 +73,7 @@ QPixmap Widget::grabForShowAnimation(const Window::SectionSlideParams ¶ms) { return result; } -void Widget::setInnerFocus() { +void Widget::doSetInnerFocus() { _inner->setFocus(); } diff --git a/Telegram/SourceFiles/profile/profile_widget.h b/Telegram/SourceFiles/profile/profile_widget.h index 6d8371b8f..7b87c6c4f 100644 --- a/Telegram/SourceFiles/profile/profile_widget.h +++ b/Telegram/SourceFiles/profile/profile_widget.h @@ -49,8 +49,6 @@ public: QPixmap grabForShowAnimation(const Window::SectionSlideParams ¶ms) override; - void setInnerFocus() override; - bool showInternal(const Window::SectionMemento *memento) override; std_::unique_ptr createMemento() const override; @@ -61,6 +59,7 @@ protected: void showAnimatedHook() override; void showFinishedHook() override; + void doSetInnerFocus() override; private slots: void onScroll(); diff --git a/Telegram/SourceFiles/settings/settings_background_widget.cpp b/Telegram/SourceFiles/settings/settings_background_widget.cpp index f9a4a654c..a0e606d35 100644 --- a/Telegram/SourceFiles/settings/settings_background_widget.cpp +++ b/Telegram/SourceFiles/settings/settings_background_widget.cpp @@ -205,7 +205,7 @@ BackgroundWidget::BackgroundWidget(QWidget *parent, UserData *self) : BlockWidge } }); subscribe(Adaptive::Changed(), [this]() { - if (Global::AdaptiveLayout() == Adaptive::WideLayout) { + if (Global::AdaptiveChatLayout() == Adaptive::ChatLayout::Wide) { _adaptive->slideDown(); } else { _adaptive->slideUp(); @@ -224,7 +224,7 @@ void BackgroundWidget::createControls() { addChildRow(_tile, margin, lang(lng_settings_bg_tile), SLOT(onTile()), Window::Theme::Background()->tile()); addChildRow(_adaptive, margin, slidedPadding, lang(lng_settings_adaptive_wide), SLOT(onAdaptive()), Global::AdaptiveForWide()); - if (Global::AdaptiveLayout() != Adaptive::WideLayout) { + if (Global::AdaptiveChatLayout() != Adaptive::ChatLayout::Wide) { _adaptive->hideFast(); } } diff --git a/Telegram/SourceFiles/ui/widgets/input_fields.cpp b/Telegram/SourceFiles/ui/widgets/input_fields.cpp index 5649a99d3..ffdda652d 100644 --- a/Telegram/SourceFiles/ui/widgets/input_fields.cpp +++ b/Telegram/SourceFiles/ui/widgets/input_fields.cpp @@ -339,7 +339,7 @@ void FlatTextarea::paintEvent(QPaintEvent *e) { p.save(); p.setClipRect(r); p.setFont(_st.font); - p.setPen(anim::pen(_st.phColor, _st.phFocusColor, _a_placeholderFocused.current(ms, hasFocus() ? 1. : 0.))); + p.setPen(anim::pen(_st.phColor, _st.phFocusColor, _a_placeholderFocused.current(ms, _focused ? 1. : 0.))); if (_st.phAlign == style::al_topleft && _phAfter > 0) { int skipWidth = placeholderSkipWidth(); p.drawText(_st.textMrg.left() - _fakeMargin + placeholderLeft + skipWidth, _st.textMrg.top() - _fakeMargin - st::lineWidth + _st.font->ascent, _ph); @@ -366,12 +366,20 @@ int FlatTextarea::placeholderSkipWidth() const { } void FlatTextarea::focusInEvent(QFocusEvent *e) { - _a_placeholderFocused.start([this] { update(); }, 0., 1., _st.phDuration); + if (!_focused) { + _focused = true; + _a_placeholderFocused.start([this] { update(); }, 0., 1., _st.phDuration); + update(); + } QTextEdit::focusInEvent(e); } void FlatTextarea::focusOutEvent(QFocusEvent *e) { - _a_placeholderFocused.start([this] { update(); }, 1., 0., _st.phDuration); + if (_focused) { + _focused = false; + _a_placeholderFocused.start([this] { update(); }, 1., 0., _st.phDuration); + update(); + } QTextEdit::focusOutEvent(e); } @@ -1559,7 +1567,7 @@ void FlatInput::paintEvent(QPaintEvent *e) { Painter p(this); auto ms = getms(); - auto placeholderFocused = _a_placeholderFocused.current(ms, hasFocus() ? 1. : 0.); + auto placeholderFocused = _a_placeholderFocused.current(ms, _focused ? 1. : 0.); auto pen = anim::pen(_st.borderColor, _st.borderActive, placeholderFocused); pen.setWidth(_st.borderWidth); @@ -1592,13 +1600,21 @@ void FlatInput::paintEvent(QPaintEvent *e) { } void FlatInput::focusInEvent(QFocusEvent *e) { - _a_placeholderFocused.start([this] { update(); }, 0., 1., _st.phDuration); + if (!_focused) { + _focused = true; + _a_placeholderFocused.start([this] { update(); }, 0., 1., _st.phDuration); + update(); + } QLineEdit::focusInEvent(e); emit focused(); } void FlatInput::focusOutEvent(QFocusEvent *e) { - _a_placeholderFocused.start([this] { update(); }, 1., 0., _st.phDuration); + if (_focused) { + _focused = false; + _a_placeholderFocused.start([this] { update(); }, 1., 0., _st.phDuration); + update(); + } QLineEdit::focusOutEvent(e); emit blurred(); } diff --git a/Telegram/SourceFiles/ui/widgets/input_fields.h b/Telegram/SourceFiles/ui/widgets/input_fields.h index aed243ce6..3e833decc 100644 --- a/Telegram/SourceFiles/ui/widgets/input_fields.h +++ b/Telegram/SourceFiles/ui/widgets/input_fields.h @@ -191,6 +191,7 @@ private: QString _ph, _phelided; int _phAfter = 0; + bool _focused = false; bool _placeholderVisible = true; Animation _a_placeholderFocused; Animation _a_placeholderVisible; @@ -313,6 +314,7 @@ private: bool _customUpDown = false; + bool _focused = false; bool _placeholderVisible = true; Animation _a_placeholderFocused; Animation _a_placeholderVisible; diff --git a/Telegram/SourceFiles/window/section_widget.cpp b/Telegram/SourceFiles/window/section_widget.cpp index 85aa930e0..3516dc910 100644 --- a/Telegram/SourceFiles/window/section_widget.cpp +++ b/Telegram/SourceFiles/window/section_widget.cpp @@ -59,6 +59,11 @@ void SectionWidget::showAnimated(SlideDirection direction, const SectionSlidePar show(); } +void SectionWidget::showFast() { + show(); + showFinished(); +} + void SectionWidget::paintEvent(QPaintEvent *e) { if (Ui::skipPaintEvent(this, e)) return; diff --git a/Telegram/SourceFiles/window/section_widget.h b/Telegram/SourceFiles/window/section_widget.h index 699197a64..464c3784d 100644 --- a/Telegram/SourceFiles/window/section_widget.h +++ b/Telegram/SourceFiles/window/section_widget.h @@ -30,6 +30,10 @@ class SectionMemento; struct SectionSlideParams { QPixmap oldContentCache; bool withTopBarShadow = false; + + explicit operator bool() const { + return !oldContentCache.isNull(); + } }; class SectionWidget : public TWidget, protected base::Subscriber { @@ -52,6 +56,7 @@ public: return false; } void showAnimated(SlideDirection direction, const SectionSlideParams ¶ms); + void showFast(); // This can be used to grab with or without top bar shadow. // This will be protected when animation preparation will be done inside. @@ -67,8 +72,8 @@ public: // Create a memento of that section to store it in the history stack. virtual std_::unique_ptr createMemento() const = 0; - virtual void setInnerFocus() { - setFocus(); + void setInnerFocus() { + doSetInnerFocus(); } protected: @@ -88,6 +93,10 @@ protected: virtual void showFinishedHook() { } + virtual void doSetInnerFocus() { + setFocus(); + } + private: void showFinished(); diff --git a/Telegram/SourceFiles/window/window.style b/Telegram/SourceFiles/window/window.style index bfa0f6e97..3a5b5da0f 100644 --- a/Telegram/SourceFiles/window/window.style +++ b/Telegram/SourceFiles/window/window.style @@ -30,8 +30,7 @@ windowDefaultHeight: 600px; windowShadow: icon {{ "window_shadow", windowShadowFg }}; windowShadowShift: 1px; -adaptiveNormalWidth: 640px; -adaptiveWideWidth: 1366px; +adaptiveChatWideWidth: 860px; notifyBorder: windowShadowFgFallback; notifyBorderWidth: 1px;